From 2319157db394140edb25e288f86f63b0512121eb Mon Sep 17 00:00:00 2001 From: garrytan Date: Sat, 31 Oct 2026 22:19:33 +0000 Subject: [PATCH 01/14] fix(dx): CAP finding titles carry a one-line description (GBRA-223 claim 2) The DX error contract told both autoplan DX voices to title capped findings exactly CAP4::, so gates, tasks and reconcile saw a bare code. The title is now CAP4:: ; the grader already parses that shape. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- bin/gstack-autoplan-snapshot.ts | 2 +- docs/agent-ready-errors.md | 4 ++-- lib/dx-error-contract.ts | 6 +++--- plan-devex-review/dx-hall-of-fame.md | 4 ++-- plan-devex-review/sections/review-sections.md | 2 +- test/dx-error-contract-grader.test.ts | 4 ++-- test/dx-error-contract.test.ts | 2 ++ 7 files changed, 13 insertions(+), 11 deletions(-) diff --git a/bin/gstack-autoplan-snapshot.ts b/bin/gstack-autoplan-snapshot.ts index 93877f418..f9461f409 100644 --- a/bin/gstack-autoplan-snapshot.ts +++ b/bin/gstack-autoplan-snapshot.ts @@ -55,7 +55,7 @@ For each finding: what's wrong, severity (critical/high/medium), and the fix.`, reviewing this plan. You have NOT seen any prior review. Evaluate: 1. Getting started: how many steps from zero to hello world? What's the TTHW? 2. API/CLI ergonomics: naming consistency, sensible defaults, progressive disclosure? -3. Error handling: Score every error path against the agent-ready error contract (https://github.com/garrytan/gstack/blob/main/docs/agent-ready-errors.md): what (operation and actual values), fix (a safe, authorized command or edit on the first or last line, or the precise thing a human must provide), recovery (retry, fix and retry, do X first, or ask a human; side effects none/partial/complete/unknown, reconcile before retrying an unknown outcome, a retry bound), code (stable, with a doc anchor), context (versions, config, env var names, the underlying cause; caller-safe only for remote services), trace (unexpected failures only: a compact trace or its file path locally, a correlation id remotely), redaction (no secret values anywhere), channel (the surface's native failure signal: non-zero exit and stderr for a CLI, status plus RFC 9457 body for an API, a typed error for an SDK, isError for an MCP tool). Usage and validation errors rejected before any work need only what, fix, code, redaction and channel; items apply per surface, N/A needs a reason, an item the plan never specifies for a path it introduces is a gap, and unknown is reserved for existing behavior you could not inspect. Report a leaked secret, a failure signalled as success, an unsafe fix, or a block on a terminal (a hang, or a no-terminal failure that names no non-interactive alternative such as --yes) as a Critical finding titled "CAP4::" (caps the Error Messages score at 4/10); report a missing applicable fix, recovery, context or trace as a High finding titled "CAP6::" (caps it at 6/10). +3. Error handling: Score every error path against the agent-ready error contract (https://github.com/garrytan/gstack/blob/main/docs/agent-ready-errors.md): what (operation and actual values), fix (a safe, authorized command or edit on the first or last line, or the precise thing a human must provide), recovery (retry, fix and retry, do X first, or ask a human; side effects none/partial/complete/unknown, reconcile before retrying an unknown outcome, a retry bound), code (stable, with a doc anchor), context (versions, config, env var names, the underlying cause; caller-safe only for remote services), trace (unexpected failures only: a compact trace or its file path locally, a correlation id remotely), redaction (no secret values anywhere), channel (the surface's native failure signal: non-zero exit and stderr for a CLI, status plus RFC 9457 body for an API, a typed error for an SDK, isError for an MCP tool). Usage and validation errors rejected before any work need only what, fix, code, redaction and channel; items apply per surface, N/A needs a reason, an item the plan never specifies for a path it introduces is a gap, and unknown is reserved for existing behavior you could not inspect. Report a leaked secret, a failure signalled as success, an unsafe fix, or a block on a terminal (a hang, or a no-terminal failure that names no non-interactive alternative such as --yes) as a Critical finding titled "CAP4:: " (caps the Error Messages score at 4/10); report a missing applicable fix, recovery, context or trace as a High finding titled "CAP6:: " (caps it at 6/10). 4. Documentation: copy-paste examples? Information architecture? Interactive elements? 5. Escape hatches: can developers override every opinionated default? For each finding: what's wrong, severity (critical/high/medium), and the fix.`, diff --git a/docs/agent-ready-errors.md b/docs/agent-ready-errors.md index 3da3f8140..3be4b3431 100644 --- a/docs/agent-ready-errors.md +++ b/docs/agent-ready-errors.md @@ -37,8 +37,8 @@ unknown is reserved for existing behavior you could not inspect. In a live audit you never triggered are unknown, not extrapolated. **Caps.** Name the path and item behind every cap and show the corrected error line. -- **4/10 (Critical finding, title `CAP4::`):** a path leaks a secret, signals failure as success, gives an unsafe fix, or blocks waiting for a terminal (hangs, or fails for lack of a terminal without naming a non-interactive alternative such as `--yes`). -- **6/10 (High finding, title `CAP6::`):** a path lacks an applicable fix, recovery, context or trace item. +- **4/10 (Critical finding, title `CAP4:: `):** a path leaks a secret, signals failure as success, gives an unsafe fix, or blocks waiting for a terminal (hangs, or fails for lack of a terminal without naming a non-interactive alternative such as `--yes`). +- **6/10 (High finding, title `CAP6:: `):** a path lacks an applicable fix, recovery, context or trace item. Every mode, DX TRIAGE included, reports every CAP4 finding. diff --git a/lib/dx-error-contract.ts b/lib/dx-error-contract.ts index d4c920110..348e44d12 100644 --- a/lib/dx-error-contract.ts +++ b/lib/dx-error-contract.ts @@ -16,7 +16,7 @@ export const DX_ERROR_DOC = 'https://github.com/garrytan/gstack/blob/main/docs/a export const DX_ERROR_COMPACT = `Score every error path against the agent-ready error contract (${DX_ERROR_DOC}): what (operation and actual values), fix (a safe, authorized command or edit on the first or last line, or the precise thing a human must provide), recovery (retry, fix and retry, do X first, or ask a human; side effects none/partial/complete/unknown, reconcile before retrying an unknown outcome, a retry bound), code (stable, with a doc anchor), context (versions, config, env var names, the underlying cause; caller-safe only for remote services), trace (unexpected failures only: a compact trace or its file path locally, a correlation id remotely), redaction (no secret values anywhere), channel (the surface's native failure signal: non-zero exit and stderr for a CLI, status plus RFC 9457 body for an API, a typed error for an SDK, isError for an MCP tool). ` + 'Usage and validation errors rejected before any work need only what, fix, code, redaction and channel; items apply per surface, N/A needs a reason, an item the plan never specifies for a path it introduces is a gap, and unknown is reserved for existing behavior you could not inspect. ' + - 'Report a leaked secret, a failure signalled as success, an unsafe fix, or a block on a terminal (a hang, or a no-terminal failure that names no non-interactive alternative such as --yes) as a Critical finding titled "CAP4::" (caps the Error Messages score at 4/10); report a missing applicable fix, recovery, context or trace as a High finding titled "CAP6::" (caps it at 6/10).'; + 'Report a leaked secret, a failure signalled as success, an unsafe fix, or a block on a terminal (a hang, or a no-terminal failure that names no non-interactive alternative such as --yes) as a Critical finding titled "CAP4:: " (caps the Error Messages score at 4/10); report a missing applicable fix, recovery, context or trace as a High finding titled "CAP6:: " (caps it at 6/10).'; export const DX_ERROR_CONTRACT = `#### The agent-ready error contract (${DX_ERROR_RUBRIC}) @@ -53,7 +53,7 @@ unknown is reserved for existing behavior you could not inspect. In a live audit you never triggered are unknown, not extrapolated. **Caps.** Name the path and item behind every cap and show the corrected error line. -- **4/10 (Critical finding, title \`CAP4::\`):** a path leaks a secret, signals failure as success, gives an unsafe fix, or blocks waiting for a terminal (hangs, or fails for lack of a terminal without naming a non-interactive alternative such as \`--yes\`). -- **6/10 (High finding, title \`CAP6::\`):** a path lacks an applicable fix, recovery, context or trace item. +- **4/10 (Critical finding, title \`CAP4:: \`):** a path leaks a secret, signals failure as success, gives an unsafe fix, or blocks waiting for a terminal (hangs, or fails for lack of a terminal without naming a non-interactive alternative such as \`--yes\`). +- **6/10 (High finding, title \`CAP6:: \`):** a path lacks an applicable fix, recovery, context or trace item. Every mode, DX TRIAGE included, reports every CAP4 finding.`; diff --git a/plan-devex-review/dx-hall-of-fame.md b/plan-devex-review/dx-hall-of-fame.md index e44f4ddea..2c73199a6 100644 --- a/plan-devex-review/dx-hall-of-fame.md +++ b/plan-devex-review/dx-hall-of-fame.md @@ -76,8 +76,8 @@ unknown is reserved for existing behavior you could not inspect. In a live audit you never triggered are unknown, not extrapolated. **Caps.** Name the path and item behind every cap and show the corrected error line. -- **4/10 (Critical finding, title `CAP4::`):** a path leaks a secret, signals failure as success, gives an unsafe fix, or blocks waiting for a terminal (hangs, or fails for lack of a terminal without naming a non-interactive alternative such as `--yes`). -- **6/10 (High finding, title `CAP6::`):** a path lacks an applicable fix, recovery, context or trace item. +- **4/10 (Critical finding, title `CAP4:: `):** a path leaks a secret, signals failure as success, gives an unsafe fix, or blocks waiting for a terminal (hangs, or fails for lack of a terminal without naming a non-interactive alternative such as `--yes`). +- **6/10 (High finding, title `CAP6:: `):** a path lacks an applicable fix, recovery, context or trace item. Every mode, DX TRIAGE included, reports every CAP4 finding. diff --git a/plan-devex-review/sections/review-sections.md b/plan-devex-review/sections/review-sections.md index 359964393..a6e1c020e 100644 --- a/plan-devex-review/sections/review-sections.md +++ b/plan-devex-review/sections/review-sections.md @@ -162,7 +162,7 @@ persona from 0A. Load reference: Read the "## Pass 3" section from `~/.claude/skills/gstack/plan-devex-review/dx-hall-of-fame.md` before scoring: the full contract, calibration examples and the standards mapping. -Score every error path against the agent-ready error contract (https://github.com/garrytan/gstack/blob/main/docs/agent-ready-errors.md): what (operation and actual values), fix (a safe, authorized command or edit on the first or last line, or the precise thing a human must provide), recovery (retry, fix and retry, do X first, or ask a human; side effects none/partial/complete/unknown, reconcile before retrying an unknown outcome, a retry bound), code (stable, with a doc anchor), context (versions, config, env var names, the underlying cause; caller-safe only for remote services), trace (unexpected failures only: a compact trace or its file path locally, a correlation id remotely), redaction (no secret values anywhere), channel (the surface's native failure signal: non-zero exit and stderr for a CLI, status plus RFC 9457 body for an API, a typed error for an SDK, isError for an MCP tool). Usage and validation errors rejected before any work need only what, fix, code, redaction and channel; items apply per surface, N/A needs a reason, an item the plan never specifies for a path it introduces is a gap, and unknown is reserved for existing behavior you could not inspect. Report a leaked secret, a failure signalled as success, an unsafe fix, or a block on a terminal (a hang, or a no-terminal failure that names no non-interactive alternative such as --yes) as a Critical finding titled "CAP4::" (caps the Error Messages score at 4/10); report a missing applicable fix, recovery, context or trace as a High finding titled "CAP6::" (caps it at 6/10). +Score every error path against the agent-ready error contract (https://github.com/garrytan/gstack/blob/main/docs/agent-ready-errors.md): what (operation and actual values), fix (a safe, authorized command or edit on the first or last line, or the precise thing a human must provide), recovery (retry, fix and retry, do X first, or ask a human; side effects none/partial/complete/unknown, reconcile before retrying an unknown outcome, a retry bound), code (stable, with a doc anchor), context (versions, config, env var names, the underlying cause; caller-safe only for remote services), trace (unexpected failures only: a compact trace or its file path locally, a correlation id remotely), redaction (no secret values anywhere), channel (the surface's native failure signal: non-zero exit and stderr for a CLI, status plus RFC 9457 body for an API, a typed error for an SDK, isError for an MCP tool). Usage and validation errors rejected before any work need only what, fix, code, redaction and channel; items apply per surface, N/A needs a reason, an item the plan never specifies for a path it introduces is a gap, and unknown is reserved for existing behavior you could not inspect. Report a leaked secret, a failure signalled as success, an unsafe fix, or a block on a terminal (a hang, or a no-terminal failure that names no non-interactive alternative such as --yes) as a Critical finding titled "CAP4:: " (caps the Error Messages score at 4/10); report a missing applicable fix, recovery, context or trace as a High finding titled "CAP6:: " (caps it at 6/10). The "## Pass 3" Hall of Fame section loaded above holds the full contract: the item table, the per-surface channel rules and the evidence states. diff --git a/test/dx-error-contract-grader.test.ts b/test/dx-error-contract-grader.test.ts index 4b601604c..1aa923325 100644 --- a/test/dx-error-contract-grader.test.ts +++ b/test/dx-error-contract-grader.test.ts @@ -21,8 +21,8 @@ describe('dx-error-contract-review grader (free controls for the paid case)', () }); test('the grader keys on the title and severity shapes the product prompt asks for', () => { - expect(DX_ERROR_COMPACT).toContain('as a Critical finding titled "CAP4::"'); - expect(DX_ERROR_COMPACT).toContain('as a High finding titled "CAP6::"'); + expect(DX_ERROR_COMPACT).toContain('as a Critical finding titled "CAP4:: "'); + expect(DX_ERROR_COMPACT).toContain('as a High finding titled "CAP6:: "'); const rows = DX_ERROR_ITEMS.map((item, index) => ({ id: `F${index}`, severity: 'High', title: `CAP6:P1:${item} gap` })); expect(capFindings(fence(rows)).map(cap => cap.item)).toEqual([...DX_ERROR_ITEMS]); }); diff --git a/test/dx-error-contract.test.ts b/test/dx-error-contract.test.ts index be98a59d9..b72b4f4c0 100644 --- a/test/dx-error-contract.test.ts +++ b/test/dx-error-contract.test.ts @@ -20,6 +20,8 @@ describe('agent-ready error contract reaches every DX reviewer', () => { for (const text of [DX_ERROR_CONTRACT, DX_ERROR_COMPACT]) { expect(text).toContain('CAP4::'); expect(text).toContain('CAP6::'); + expect(text).toContain('CAP4:: '); + expect(text).toContain('CAP6:: '); expect(text).toContain('4/10'); expect(text).toContain('6/10'); expect(text).toMatch(/[Uu]sage (and|or) validation error/); From 6ac7e073e5450dcc8049de45f97dcec7315706a1 Mon Sep 17 00:00:00 2001 From: garrytan Date: Sat, 31 Oct 2026 22:22:16 +0000 Subject: [PATCH 02/14] fix(autoplan): reconcile pairs same-file findings that carry no line (GBRA-223 claim 1) Plan reviews name files, rarely lines, and reconcile associated by location only when both findings had a line, so findings both voices raised on one file were filed new and native-only. A same-file pair with a missing line is now associated as unverified (basis file), never confirmed; a CAP code no longer counts in title comparison. DRIVER.md names submit --reconcile. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- autoplan/DRIVER.md | 4 ++-- lib/autoplan-reconcile.ts | 19 +++++++++++++++--- scripts/resolvers/autoplan-driver.ts | 2 +- test/autoplan-pairing.test.ts | 30 ++++++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 6 deletions(-) diff --git a/autoplan/DRIVER.md b/autoplan/DRIVER.md index 875ea1068..6bbdc824a 100644 --- a/autoplan/DRIVER.md +++ b/autoplan/DRIVER.md @@ -65,8 +65,8 @@ line yourself; gstack never writes the plan. bind a free-form review record (canonical fence, one findings table or list, or `No findings.`). - `gstack-autoplan submit ... --record --attest "" --attempt ` bind a record with no INPUT: line as receipt: attested (a wrong INPUT: always refuses). -- `gstack-autoplan submit --out --phase

--voice native|outside --result --model --attempt ` - bind a canonical result (the RESULT FORMAT below). +- `gstack-autoplan submit --out

--phase

--voice native|outside --result --model --attempt [--reconcile ]` + bind a canonical result (the RESULT FORMAT below); on the submit that closes the phase, `--reconcile` takes your own pairing of findings both voices raised, one `{"outside_id","native_id","disposition","resolution"}` row each, over the structural one. - `gstack-autoplan submit --out

--phase

--voice outside --unavailable|--failed ""` close the phase native-only when no outside voice can run. - `gstack-autoplan answer --out

--gate-rev --reply "" --by human|agent: [--owner-reply |-] [--rec-hash ]` diff --git a/lib/autoplan-reconcile.ts b/lib/autoplan-reconcile.ts index 48b64ff19..c92da9e9b 100644 --- a/lib/autoplan-reconcile.ts +++ b/lib/autoplan-reconcile.ts @@ -196,14 +196,15 @@ export interface ReconciliationRow { outside_id: string; native_id: string | null; disposition: ReconciliationDisposition; /** Why this disposition: the matched location or title, or the parent's override reason. */ resolution: string; - basis: 'file_line' | 'title' | 'override' | 'none'; + basis: 'file_line' | 'file' | 'title' | 'override' | 'none'; severity: { outside: Severity; native: Severity | null }; /** A parent override's one shared proposal for both voices' user challenge; the gate renders it as option b. */ proposal?: string; } export interface ReconciliationOverride { outside_id: string; native_id?: string | null; disposition: ReconciliationDisposition; resolution: string; proposal?: string } -export const tokens = (title: string) => new Set(title.toLowerCase().replace(/[^a-z0-9 ]+/g, ' ').split(/\s+/).filter(t => t.length > 2)); +/** A DX cap code (`CAP4::`) names a location, not the problem, so title comparison skips it. */ +export const tokens = (title: string) => new Set(title.replace(/^\s*CAP[46]\s*:[^:]*:\s*/i, '').toLowerCase().replace(/[^a-z0-9 ]+/g, ' ').split(/\s+/).filter(t => t.length > 2)); export function jaccard(a: Set, b: Set): number { if (!a.size || !b.size) return 0; let inter = 0; @@ -225,7 +226,8 @@ function contradictory(a: string, b: string): boolean { /** * Structural reconciliation. Findings associate by a title overlap of at least - * half the words, or by file:line (±3 lines). Association alone never + * half the words (a leading CAP code ignored), or by file:line (±3 lines), or, + * when either finding has no line, by the same file. Association alone never * confirms: a title association is `confirmed` only when both voices state the * same remedy, `disagree` when severities differ by two or more levels or the * remedies contradict (one keeps, drops or negates what the other changes), @@ -270,8 +272,19 @@ export function reconcile(native: FindingRow[], outside: FindingRow[], overrides } const titled = !!match; match ??= native.find(n => !used.has(n.id) && near(n)); + let fileOnly = false; + if (!match && o.file) { + // Plan reviews name a file without a line: same file, a line missing on either side. + const sameFile = native.filter(n => !used.has(n.id) && n.file === o.file && (o.line === undefined || n.line === undefined)); + match = sameFile.reduce((b, n) => (!b || jaccard(ot, tokens(n.title)) > jaccard(ot, tokens(b.title)) ? n : b), undefined); + fileOnly = !!match; + } if (!match) { rows.push({ outside_id: o.id, native_id: null, disposition: 'new', resolution: 'not raised by the native voice', basis: 'none', severity: { outside: o.severity, native: null } }); continue; } used.add(match.id); + if (fileOnly) { + rows.push({ outside_id: o.id, native_id: match.id, disposition: 'unverified', resolution: `same file, agreement unverified (${o.file}): ${match.title}`, basis: 'file', severity: { outside: o.severity, native: match.severity } }); + continue; + } const basis: ReconciliationRow['basis'] = near(match) ? 'file_line' : 'title'; const where = near(match) ? `${o.file}:${o.line}` : 'same title'; if (!titled) { diff --git a/scripts/resolvers/autoplan-driver.ts b/scripts/resolvers/autoplan-driver.ts index d84d63b95..51cce1eba 100644 --- a/scripts/resolvers/autoplan-driver.ts +++ b/scripts/resolvers/autoplan-driver.ts @@ -21,7 +21,7 @@ export const AUTOPLAN_DRIVER_COMMANDS: Array<{ cmd: string; does: string }> = [ { cmd: 'gstack-autoplan next --out [--redispatch ]', does: 'later calls: open the next phase or reprint open attempts; never redispatches on its own' }, { cmd: 'gstack-autoplan submit --out --phase

--voice native|outside --record --model [--attempt ]', does: 'bind a free-form review record (canonical fence, one findings table or list, or `No findings.`)' }, { cmd: 'gstack-autoplan submit ... --record --attest "" --attempt ', does: 'bind a record with no INPUT: line as receipt: attested (a wrong INPUT: always refuses)' }, - { cmd: 'gstack-autoplan submit --out

--phase

--voice native|outside --result --model --attempt ', does: 'bind a canonical result (the RESULT FORMAT below)' }, + { cmd: 'gstack-autoplan submit --out

--phase

--voice native|outside --result --model --attempt [--reconcile ]', does: 'bind a canonical result (the RESULT FORMAT below); on the submit that closes the phase, `--reconcile` takes your own pairing of findings both voices raised, one `{"outside_id","native_id","disposition","resolution"}` row each, over the structural one' }, { cmd: 'gstack-autoplan submit --out

--phase

--voice outside --unavailable|--failed ""', does: 'close the phase native-only when no outside voice can run' }, { cmd: 'gstack-autoplan answer --out

--gate-rev --reply "" --by human|agent: [--owner-reply |-] [--rec-hash ]', does: 'answer the gate with the reply grammar below; an agent answer stores the owner\'s words verbatim' }, { cmd: 'gstack-autoplan recommend --out --gate-rev --item --option --basis ""', does: 'change one recommendation before relay; then render and relay again (a `rec` needs the new `--rec-hash`)' }, diff --git a/test/autoplan-pairing.test.ts b/test/autoplan-pairing.test.ts index 71e9ab67f..85e4872e6 100644 --- a/test/autoplan-pairing.test.ts +++ b/test/autoplan-pairing.test.ts @@ -61,3 +61,33 @@ describe('pairing negative control from a real run', () => { expect(challengeDefault(ok!, challenge(same.native, 'scope')[0], challenge(same.outside, 'scope')[0])).toMatchObject({ recommended: 'b', basis: 'both voices; same remedy' }); }); }); + +describe('plan reviews name a file without a line (GBRA-223 claim 1)', () => { + const row = (voice: 'native' | 'outside', n: number, f: Record) => ({ schema_version: 1, run: 'r', id: `r-dx-${voice}-${n}`, phase: 'dx', voice, disposition: 'open', ...f }) as any; + const native = row('native', 1, { severity: 'Critical', title: 'per-turn resolver call has no hard timeout or fail-open', file: 'src/resolver.ts', resolution: 'add a 2s timeout and fail open' }); + + test('a CAP-coded outside title on the same file is associated, unverified, never new', () => { + const outside = row('outside', 1, { severity: 'Critical', title: 'CAP4:src/resolver.ts:recovery', file: 'src/resolver.ts', resolution: 'bound the call' }); + const { rows, native_only } = reconcile([native], [outside]); + expect(rows[0]).toMatchObject({ disposition: 'unverified', basis: 'file', native_id: native.id }); + expect(rows[0]!.resolution).toStartWith('same file, agreement unverified (src/resolver.ts)'); + expect(native_only).toEqual([]); + }); + + test('differently worded titles on the same file associate; the best title among same-file natives wins', () => { + const other = row('native', 2, { severity: 'Medium', title: 'resolver output is not cached between turns', file: 'src/resolver.ts' }); + const outside = row('outside', 1, { severity: 'Critical', title: 'Resolver call has no timeout and can hang the turn', file: 'src/resolver.ts' }); + expect(reconcile([other, native], [outside]).rows[0]).toMatchObject({ disposition: 'unverified', basis: 'file', native_id: native.id }); + }); + + test('a CAP code is not part of the title comparison', () => { + const outside = row('outside', 1, { severity: 'Critical', title: 'CAP4:src/resolver.ts:recovery per-turn resolver call has no hard timeout or fail-open', file: 'src/resolver.ts', resolution: 'add a 2s timeout and fail open' }); + expect(reconcile([native], [outside]).rows[0]).toMatchObject({ disposition: 'confirmed', basis: 'title' }); + }); + + test('controls: two lines far apart, or different files, stay new', () => { + const lined = row('native', 1, { ...native, id: 'r-dx-native-1', line: 10 }); + expect(reconcile([lined], [row('outside', 1, { severity: 'High', title: 'retry loop has no bound', file: 'src/resolver.ts', line: 90 })]).rows[0]!.disposition).toBe('new'); + expect(reconcile([native], [row('outside', 1, { severity: 'High', title: 'CAP6:src/cli.ts:fix', file: 'src/cli.ts' })]).rows[0]!.disposition).toBe('new'); + }); +}); From a2aea43fdff2496a23891cbb72cfc2e33b6de5a1 Mon Sep 17 00:00:00 2001 From: garrytan Date: Sat, 31 Oct 2026 22:43:39 +0000 Subject: [PATCH 03/14] fix(autoplan): open Critical and High findings reach the gate as apply items (GBRA-223 claim 3) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- autoplan/DRIVER.md | 2 +- docs/unattended.md | 8 +++- lib/autoplan-confirm.ts | 6 ++- lib/autoplan-gate.ts | 26 +++++++++--- lib/gate-list.ts | 14 +++++-- scripts/resolvers/autoplan-driver.ts | 2 +- test/autoplan-answer-invalidation.test.ts | 30 +++++++------- test/autoplan-capy-e2e.test.ts | 18 ++++---- test/autoplan-gate-cost.test.ts | 4 +- test/autoplan-gate-judgment.test.ts | 50 +++++++++++++++++++---- test/autoplan-run-recovery.test.ts | 3 +- test/helpers/autoplan-run-fixture.ts | 10 +++++ 12 files changed, 128 insertions(+), 45 deletions(-) diff --git a/autoplan/DRIVER.md b/autoplan/DRIVER.md index 6bbdc824a..b8ed9b8c2 100644 --- a/autoplan/DRIVER.md +++ b/autoplan/DRIVER.md @@ -111,7 +111,7 @@ A reply binds to the `gate_rev` you read (`--gate-rev`). Bare `yes`/`no` is unpa - `rec`: every item takes its recommendation, plan changes included (each prints APPLY:); `all` and `do it` are aliases. - `as-is`: keep every direction; no plan change. -- `d3b uc1a`: one