mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-01 14:34:42 +01:00
classifyOutsideReview() returns separate execution, findings and verdict fields from text, stderr and exit status; reason codes, anchors and fix actions live in lib/gate-outcomes.ts. validateOutsideReview() stays as the text-only wrapper and the two-argument CLI keeps exit 0/1/2 for importers and installed skills that predate a re-render. The --verdict form (implied by --stderr/--exit) prints VERDICT/FINDINGS/REASON and exits 0/3/4/1; the generated outside-voice caller moves to it and branches on each status (unverified prints OUTSIDE_STATUS: unverified, never completed). Adds docs/troubleshooting.md with one anchored section per reason code, the anchor test, and the gate-tail lint (query scripts may not swallow failures, gate calls may not end in || true, other best-effort tails say why).
93 lines
4.3 KiB
TypeScript
93 lines
4.3 KiB
TypeScript
/**
|
|
* INV-1 gate outcome contract: every gate or outside review ends as
|
|
* `ran` (with findings or a verdict), `not_run` (gstack chose not to run it) or
|
|
* `unavailable` (it tried and could not). Each reason code maps to one state,
|
|
* one stable anchor in docs/troubleshooting.md and one fix action; the
|
|
* validator prints these lines and test/troubleshooting-anchors.test.ts checks
|
|
* every anchor exists. Add a row here before printing a new reason anywhere.
|
|
*/
|
|
export type GateState = 'ran' | 'not_run' | 'unavailable';
|
|
|
|
export interface GateOutcome {
|
|
state: GateState;
|
|
/** Stable `<a id>` in docs/troubleshooting.md. */
|
|
anchor: string;
|
|
/** What happened, in plain words; `<detail>` adds the real value (exit code, stderr line). */
|
|
summary: string;
|
|
fix: string;
|
|
}
|
|
|
|
export const GATE_OUTCOMES = {
|
|
sandbox_unavailable: {
|
|
state: 'unavailable', anchor: 'codex-sandbox-unavailable',
|
|
summary: "Codex's sandbox could not start here",
|
|
fix: 'enable unprivileged user namespaces for this container, or set GSTACK_CODEX_NO_SANDBOX=1',
|
|
},
|
|
commands_failed: {
|
|
state: 'unavailable', anchor: 'outside-review-commands-failed',
|
|
summary: 'the reviewer could not run commands or read the diff',
|
|
fix: 'read the reviewer stderr above, repair the environment it names, then re-run the review',
|
|
},
|
|
execution_failed: {
|
|
state: 'unavailable', anchor: 'outside-review-execution-failed',
|
|
summary: 'the reviewer process failed',
|
|
fix: 'read the provider diagnosis above (auth, model, network), repair it, then re-run the review',
|
|
},
|
|
timeout: {
|
|
state: 'unavailable', anchor: 'outside-review-timeout',
|
|
summary: 'the reviewer hit its time limit and was stopped',
|
|
fix: 're-run the review with a smaller scope, or check the provider status and network',
|
|
},
|
|
empty_response: {
|
|
state: 'unavailable', anchor: 'outside-review-empty-response',
|
|
summary: 'the reviewer returned no response',
|
|
fix: 'read the reviewer stderr above, then re-run the review',
|
|
},
|
|
review_refused: {
|
|
state: 'unavailable', anchor: 'outside-review-refused',
|
|
summary: 'the reviewer declined to review',
|
|
fix: 're-run the review; if it declines again, review the change natively',
|
|
},
|
|
missing_markers: {
|
|
state: 'unavailable', anchor: 'outside-review-missing-markers',
|
|
summary: 'the response lacks the markers this gate requires',
|
|
fix: 're-run the review; a response without its required markers never counts as a pass',
|
|
},
|
|
untagged_review: {
|
|
state: 'ran', anchor: 'outside-review-unverified',
|
|
summary: 'the review completed without severity tags or an explicit no-findings line',
|
|
fix: 'read the output above and decide; it is not a pass',
|
|
},
|
|
model_unusable: {
|
|
state: 'unavailable', anchor: 'codex-model-unusable',
|
|
summary: 'Codex could not use the selected model',
|
|
fix: 'set GSTACK_CODEX_MODEL=<supported-model> or fix model (and base_url for a custom provider) in your Codex config.toml',
|
|
},
|
|
probe_inconclusive: {
|
|
state: 'ran', anchor: 'codex-mode-unverified',
|
|
summary: 'the Codex model check timed out or hit a network error, so readiness is unverified',
|
|
fix: 'nothing to do now; the review itself is still checked, and a failure there is reported',
|
|
},
|
|
auth_failed: {
|
|
state: 'unavailable', anchor: 'codex-auth-failed',
|
|
summary: 'no Codex credentials were found',
|
|
fix: 'run codex login, or set the API key your Codex config.toml provider names in env_key',
|
|
},
|
|
disabled: {
|
|
state: 'not_run', anchor: 'outside-review-disabled',
|
|
summary: 'outside reviews are turned off (codex_reviews=disabled)',
|
|
fix: 'gstack-config set codex_reviews enabled',
|
|
},
|
|
} as const satisfies Record<string, GateOutcome>;
|
|
|
|
export type GateReason = keyof typeof GATE_OUTCOMES;
|
|
|
|
/** One user-facing line for an outcome that is not a clean `ran`. */
|
|
export function gateOutcomeLine(gate: string, reason: GateReason, detail?: string): string {
|
|
const outcome: GateOutcome = GATE_OUTCOMES[reason];
|
|
const what = detail ? `${outcome.summary} (${detail})` : outcome.summary;
|
|
if (outcome.state === 'not_run') return `${gate}: not run (${what}). Fix: ${outcome.fix}.`;
|
|
if (outcome.state === 'ran') return `${gate}: ran, verdict unverified (${what}). Fix: ${outcome.fix}.`;
|
|
return `${gate} unavailable: ${what}. No review ran; this is missing coverage, not a pass. Fix: ${outcome.fix}.`;
|
|
}
|