fix(ship): Step 11 saves the targeted record and the receipt writes carry it; Step 9 clears it (GST-464)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-10-31 15:16:11 +00:00
1 parent 6a0938a29d
commit c5e04a74ee
12 files changed
+57 -16

No files matched your search

+5 -5
View File
@@ -29,7 +29,7 @@ file, unchanged; its findings reach `merged.jsonl` only via `collect`; `judge` r
small model at low effort; their findings go to the PR body.
**Calls.** One pass with no fixes is 9 calls: Phases 1-8 and the report; at most 12 with a STOP's
section read or a fix-check, plus a keep-alive per 200 s waited. A fix repeats Phases 3-4.
section read or a fix-check, plus a keep-alive per 200 s waited.
**Budget: observe.** `gstack-ship-budget start --mode observe` records, never admits: each program ends
with `gstack-ship-budget note --step <last step> --reading R --credits C --record <rec>`, R and C read in that program
@@ -89,8 +89,8 @@ Steps 8.1 and 8.2 (`Scope Check:`) on the todos and commits files, then the core
`JUDGMENT: ok` before any fix), fixes and persistence in
one program. Prints `Pre-Landing Review:`, `Gate edits:`. `NEXT: stop` stops; `full` or
`scoped`: one program runs the failing test, fix, `--tests-only` and Phase 3, each gated; `done`: the same program runs Step 11 from
`sections/adversarial.md` as written (above), only the voices the last full-pass plan lists (neither
prints `ADVERSARIAL: skipped (review plan)`).
`sections/adversarial.md` as written (above), only the voices the last full-pass plan lists (none:
`ADVERSARIAL: skipped (review plan)`).
## 5. Voices, bind and version: Steps 11, 11.5, 12
@@ -102,7 +102,7 @@ approvals; `VERSION: skipped|kept`: no write.
## 6. Notes and documentation: Steps 13, 14, 14.5
Step 13: one user-facing entry from the commits file, where `NEXT:` says;
Step 13: one user-facing entry from the commits file where `NEXT:` says;
`gstack-prose-lint <it> --base origin/<base> --gate` until `PROSE_LINT: ok`. Step 14: TODOS.md deferrals and
completions, edited from the todos file. Step 14.5, every ship: read `sections/documentation.md` and run its audit as written, as
one program (above), then `gstack-ship-receipt docs-caps --base <base>`; print `Documentation:`.
@@ -119,7 +119,7 @@ STALE or MISSING reruns once), `PREGATE:`; print them, the last `Gate edits:` an
## 8. Publish: Steps 18, 19, 19-C, 20, 21, 22
- Step 18: the open-PR lookup; a title file starting `v<NEW_VERSION> `. `UNATTENDED_SHIP:` with no open PR at HEAD takes Step 18-H.
- Step 19 body: the receipt block, then the sections `sections/pr-body.md` lists (Pre-Landing Review: `REVIEW_LOOP:`, `Gate edits:`, deferred, notes), led under `CAPY_PR: on` (Step 19-C) by What changed, Why, Blast radius and Approvals (`gstack-approvals render`). `gstack-prose-lint <top> --sections 'Why,Summary,Blast radius' --gate`; `gstack-issue-links`; `gstack-review-scope summary --since "$REVIEW_SINCE" && gstack-ship-receipt write --pr <n> --base <base> --gate @<f> --pregate <f> --issues @<f> --review-loop @<f> --docs @<f> --body <body>`; `gstack-post pr-body` and `pr-title` (a new PR via the host's PR tool; a draft while a gate finding is `open`). `SHIP_RECEIPT:` opens your report.
- Step 19 body: the receipt block, then the sections `sections/pr-body.md` lists (Pre-Landing Review: `REVIEW_LOOP:`, `Gate edits:`, deferred, notes), led under `CAPY_PR: on` (Step 19-C) by What changed, Why, Blast radius and Approvals (`gstack-approvals render`). `gstack-prose-lint <top> --sections 'Why,Summary,Blast radius' --gate`; `gstack-issue-links`; `gstack-review-scope summary --since "$REVIEW_SINCE" && gstack-ship-receipt write --pr <n> --base <base> --gate @<f> --pregate <f> --issues @<f> --review-loop @<f> --docs @<f> [--review-targeted @.gstack/tmp/targeted.json] --body <body>`; `gstack-post pr-body` and `pr-title` (new PR: the host's PR tool; draft while a gate finding is `open`). `SHIP_RECEIPT:` opens your report.
- Step 20: the ship metrics row via `gstack-review-log`.
- Step 22: `gstack-ship-budget close --reading R --credits C` (`BUDGET_FINAL:` and `BUDGET_TIMELINE:` carry tokens and credits; report both), then `gstack-ship-receipt result --pr <n>`: `GSTACK_RESULT: skill=ship status=complete run=<dir>`. Under a merge queue, `gstack-ship-queue ready --pr <n> [--screen|--no-screen]` at the pushed head; paste its output.
+5 -5
View File
@@ -27,7 +27,7 @@ file, unchanged; its findings reach `merged.jsonl` only via `collect`; `judge` r
small model at low effort; their findings go to the PR body.
**Calls.** One pass with no fixes is 9 calls: Phases 1-8 and the report; at most 12 with a STOP's
section read or a fix-check, plus a keep-alive per 200 s waited. A fix repeats Phases 3-4.
section read or a fix-check, plus a keep-alive per 200 s waited.
**Budget: observe.** `gstack-ship-budget start --mode observe` records, never admits: each program ends
with `gstack-ship-budget note --step <last step> --reading R --credits C --record <rec>`, R and C read in that program
@@ -87,8 +87,8 @@ Steps 8.1 and 8.2 (`Scope Check:`) on the todos and commits files, then the core
`JUDGMENT: ok` before any fix), fixes and persistence in
one program. Prints `Pre-Landing Review:`, `Gate edits:`. `NEXT: stop` stops; `full` or
`scoped`: one program runs the failing test, fix, `--tests-only` and Phase 3, each gated; `done`: the same program runs Step 11 from
`sections/adversarial.md` as written (above), only the voices the last full-pass plan lists (neither
prints `ADVERSARIAL: skipped (review plan)`).
`sections/adversarial.md` as written (above), only the voices the last full-pass plan lists (none:
`ADVERSARIAL: skipped (review plan)`).
## 5. Voices, bind and version: Steps 11, 11.5, 12
@@ -100,7 +100,7 @@ approvals; `VERSION: skipped|kept`: no write.
## 6. Notes and documentation: Steps 13, 14, 14.5
Step 13: one user-facing entry from the commits file, where `NEXT:` says;
Step 13: one user-facing entry from the commits file where `NEXT:` says;
`gstack-prose-lint <it> --base origin/<base> --gate` until `PROSE_LINT: ok`. Step 14: TODOS.md deferrals and
completions, edited from the todos file. Step 14.5, every ship: read `sections/documentation.md` and run its audit as written, as
one program (above), then `gstack-ship-receipt docs-caps --base <base>`; print `Documentation:`.
@@ -117,7 +117,7 @@ STALE or MISSING reruns once), `PREGATE:`; print them, the last `Gate edits:` an
## 8. Publish: Steps 18, 19, 19-C, 20, 21, 22
- Step 18: the open-PR lookup; a title file starting `v<NEW_VERSION> `. `UNATTENDED_SHIP:` with no open PR at HEAD takes Step 18-H.
- Step 19 body: the receipt block, then the sections `sections/pr-body.md` lists (Pre-Landing Review: `REVIEW_LOOP:`, `Gate edits:`, deferred, notes), led under `CAPY_PR: on` (Step 19-C) by What changed, Why, Blast radius and Approvals (`gstack-approvals render`). `gstack-prose-lint <top> --sections 'Why,Summary,Blast radius' --gate`; `gstack-issue-links`; `gstack-review-scope summary --since "$REVIEW_SINCE" && gstack-ship-receipt write --pr <n> --base <base> --gate @<f> --pregate <f> --issues @<f> --review-loop @<f> --docs @<f> --body <body>`; `gstack-post pr-body` and `pr-title` (a new PR via the host's PR tool; a draft while a gate finding is `open`). `SHIP_RECEIPT:` opens your report.
- Step 19 body: the receipt block, then the sections `sections/pr-body.md` lists (Pre-Landing Review: `REVIEW_LOOP:`, `Gate edits:`, deferred, notes), led under `CAPY_PR: on` (Step 19-C) by What changed, Why, Blast radius and Approvals (`gstack-approvals render`). `gstack-prose-lint <top> --sections 'Why,Summary,Blast radius' --gate`; `gstack-issue-links`; `gstack-review-scope summary --since "$REVIEW_SINCE" && gstack-ship-receipt write --pr <n> --base <base> --gate @<f> --pregate <f> --issues @<f> --review-loop @<f> --docs @<f> [--review-targeted @.gstack/tmp/targeted.json] --body <body>`; `gstack-post pr-body` and `pr-title` (new PR: the host's PR tool; draft while a gate finding is `open`). `SHIP_RECEIPT:` opens your report.
- Step 20: the ship metrics row via `gstack-review-log`.
- Step 22: `gstack-ship-budget close --reading R --credits C` (`BUDGET_FINAL:` and `BUDGET_TIMELINE:` carry tokens and credits; report both), then `gstack-ship-receipt result --pr <n>`: `GSTACK_RESULT: skill=ship status=complete run=<dir>`. Under a merge queue, `gstack-ship-queue ready --pr <n> [--screen|--no-screen]` at the pushed head; paste its output.
+1 -1
View File
@@ -5,7 +5,7 @@
`DISPATCH: outside-voice`. Without the native line (light depth at runtime 0;
above 0 the plan lists it beside the batched specialist), print `ADVERSARIAL: skipped (review plan)`;
with neither line, take decision 3 of Finish the adversarial phase. On `DISPATCH: targeted`, also run the targeted reader
(`gstack-review-dispatch render --role targeted --classes <TARGETED classes> --base <base> --diff-base <merge base>`, the merge base on every pass, never RANGE_BASE) like the voices; `verify --targeted-out` feeds the receipt's `--review-targeted`. Under a token cap,
(`gstack-review-dispatch render --role targeted --classes <TARGETED classes> --base <base> --diff-base <merge base>`, the merge base on every pass, never RANGE_BASE) like the voices; `verify --targeted-out .gstack/tmp/targeted.json` feeds the receipt's `--review-targeted`. Under a token cap,
reserve the planned voices as one batch (`gstack-ship-budget check --next dispatch:<roles>`)
and add `--reservation <id>` to the `--start adversarial-review` call below.
+1 -1
View File
@@ -3,7 +3,7 @@
`DISPATCH: outside-voice`. Without the native line (light depth at runtime 0;
above 0 the plan lists it beside the batched specialist), print `ADVERSARIAL: skipped (review plan)`;
with neither line, take decision 3 of Finish the adversarial phase. On `DISPATCH: targeted`, also run the targeted reader
(`gstack-review-dispatch render --role targeted --classes <TARGETED classes> --base <base> --diff-base <merge base>`, the merge base on every pass, never RANGE_BASE) like the voices; `verify --targeted-out` feeds the receipt's `--review-targeted`. Under a token cap,
(`gstack-review-dispatch render --role targeted --classes <TARGETED classes> --base <base> --diff-base <merge base>`, the merge base on every pass, never RANGE_BASE) like the voices; `verify --targeted-out .gstack/tmp/targeted.json` feeds the receipt's `--review-targeted`. Under a token cap,
reserve the planned voices as one batch (`gstack-ship-budget check --next dispatch:<roles>`)
and add `--reservation <id>` to the `--start adversarial-review` call below.
+1
View File
@@ -13,6 +13,7 @@ unattended run may do is docs/unattended.md#what-an-unattended-session-may-do.
~/.claude/skills/gstack/bin/gstack-issue-links --base <base> --out .gstack/tmp/issues.json
~/.claude/skills/gstack/bin/gstack-ship-receipt write --base <base> --gate @.gstack/tmp/ship-gate.json --issues @.gstack/tmp/issues.json --out .gstack/tmp/ship-receipt.md
```
When Step 11 saved `.gstack/tmp/targeted.json`, add `--review-targeted @.gstack/tmp/targeted.json` to that write.
2. Send the completion message, identifiers first, in this order: the `SHIP_RECEIPT:`
line and receipt block; `branch:` and `sha:` (`git rev-parse HEAD`),
pushed or `not pushed`; `inputs:` the plan or issue, the base SHA and the spend
+1
View File
@@ -11,6 +11,7 @@ unattended run may do is docs/unattended.md#what-an-unattended-session-may-do.
~/.claude/skills/gstack/bin/gstack-issue-links --base <base> --out .gstack/tmp/issues.json
~/.claude/skills/gstack/bin/gstack-ship-receipt write --base <base> --gate @.gstack/tmp/ship-gate.json --issues @.gstack/tmp/issues.json --out .gstack/tmp/ship-receipt.md
```
When Step 11 saved `.gstack/tmp/targeted.json`, add `--review-targeted @.gstack/tmp/targeted.json` to that write.
2. Send the completion message, identifiers first, in this order: the `SHIP_RECEIPT:`
line and receipt block; `branch:` and `sha:` (`git rev-parse HEAD`),
pushed or `not pushed`; `inputs:` the plan or issue, the base SHA and the spend
+1 -1
View File
@@ -221,7 +221,7 @@ per-lane counts (`{"<lane>":{"pass":N,"fail":N,"skip":N}}`) to
```bash
rm -f .gstack/tmp/review-loop.json
~/.claude/skills/gstack/bin/gstack-review-scope summary --since "$REVIEW_SINCE" --out .gstack/tmp/review-loop.json &&
~/.claude/skills/gstack/bin/gstack-ship-receipt write --pr <pr-number> --base <base> --gate @.gstack/tmp/ship-gate.json --pregate .gstack/tmp/pregate.json --issues @.gstack/tmp/issues.json --review-loop @.gstack/tmp/review-loop.json --docs @.gstack/tmp/docs-audit.json --body "$PR_BODY_FILE"
~/.claude/skills/gstack/bin/gstack-ship-receipt write --pr <pr-number> --base <base> --gate @.gstack/tmp/ship-gate.json --pregate .gstack/tmp/pregate.json --issues @.gstack/tmp/issues.json --review-loop @.gstack/tmp/review-loop.json --docs @.gstack/tmp/docs-audit.json $([ -s .gstack/tmp/targeted.json ] && echo --review-targeted @.gstack/tmp/targeted.json) --body "$PR_BODY_FILE"
```
`--docs` reads the record Step 14.5 saved (`{status, scope[, exception]}`): the
+1 -1
View File
@@ -204,7 +204,7 @@ per-lane counts (`{"<lane>":{"pass":N,"fail":N,"skip":N}}`) to
```bash
rm -f .gstack/tmp/review-loop.json
~/.claude/skills/gstack/bin/gstack-review-scope summary --since "$REVIEW_SINCE" --out .gstack/tmp/review-loop.json &&
~/.claude/skills/gstack/bin/gstack-ship-receipt write --pr <pr-number> --base <base> --gate @.gstack/tmp/ship-gate.json --pregate .gstack/tmp/pregate.json --issues @.gstack/tmp/issues.json --review-loop @.gstack/tmp/review-loop.json --docs @.gstack/tmp/docs-audit.json --body "$PR_BODY_FILE"
~/.claude/skills/gstack/bin/gstack-ship-receipt write --pr <pr-number> --base <base> --gate @.gstack/tmp/ship-gate.json --pregate .gstack/tmp/pregate.json --issues @.gstack/tmp/issues.json --review-loop @.gstack/tmp/review-loop.json --docs @.gstack/tmp/docs-audit.json $([ -s .gstack/tmp/targeted.json ] && echo --review-targeted @.gstack/tmp/targeted.json) --body "$PR_BODY_FILE"
```
`--docs` reads the record Step 14.5 saved (`{status, scope[, exception]}`): the
+1 -1
View File
@@ -3,7 +3,7 @@
## Step 9: Pre-Landing Review
Set CYCLES to 0 on first entry only, with PASS set to 1,
`REVIEW_SINCE=$(date -u +%Y-%m-%dT%H:%M:%SZ)` saved and the `ship: review_scope`
`REVIEW_SINCE=$(date -u +%Y-%m-%dT%H:%M:%SZ)` saved, `.gstack/tmp/targeted.json` removed, and the `ship: review_scope`
policy line (Step 3) kept. Every entry saves `PASS_HEAD=$(git rev-parse HEAD)`; from pass 2 on,
`RANGE_BASE` is the previous pass's PASS_HEAD. Keep existing approvals; changed
finding scope needs a new decision. Order: checklist/design, specialists (9.1),
+1 -1
View File
@@ -1,7 +1,7 @@
## Step 9: Pre-Landing Review
Set CYCLES to 0 on first entry only, with PASS set to 1,
`REVIEW_SINCE=$(date -u +%Y-%m-%dT%H:%M:%SZ)` saved and the `ship: review_scope`
`REVIEW_SINCE=$(date -u +%Y-%m-%dT%H:%M:%SZ)` saved, `.gstack/tmp/targeted.json` removed, and the `ship: review_scope`
policy line (Step 3) kept. Every entry saves `PASS_HEAD=$(git rev-parse HEAD)`; from pass 2 on,
`RANGE_BASE` is the previous pass's PASS_HEAD. Keep existing approvals; changed
finding scope needs a new decision. Order: checklist/design, specialists (9.1),
+35
View File
@@ -320,6 +320,41 @@ describe('the targeted role end to end on a fixture repository', () => {
}, 180_000);
});
describe('the /ship commands carry the targeted record to the receipt (GST-464)', () => {
// Value: protects=when the targeted reader ran, the receipt /ship writes carries its record, so ready prints verdict=ok instead
// of receipt=legacy (gstack-evals#95, #3682, #3612); fails_when=Step 11's verify writes the record nowhere Step 19 reads, the
// Step 19 or handoff write command omits --review-targeted, or a record from an earlier run survives Step 9; seam=the generated
// sections' own command text, expanded by bash and passed to gstack-ship-receipt write; no_claim=enforcement (report-only)
test('Step 11 verify writes the record where Step 19 write reads it, a run without the reader writes none, and Step 9 clears it', () => {
const section = (name: string) => fs.readFileSync(path.join(ROOT, 'ship', 'sections', `${name}.md`), 'utf8');
const recordPath = /verify --targeted-out (\S+?)`/.exec(section('adversarial'))?.[1];
expect(recordPath).toBe('.gstack/tmp/targeted.json');
const writeLine = section('pr-body').split('\n').find(l => l.includes('gstack-ship-receipt write --pr'))!;
const optional = /\$\(\[ -s (\S+) \] && echo (--review-targeted @\S+)\)/.exec(writeLine);
expect(optional?.slice(1)).toEqual([recordPath!, `--review-targeted @${recordPath}`]);
expect(section('handoff')).toContain(`When Step 11 saved \`${recordPath}\`, add \`--review-targeted @${recordPath}\` to that write.`);
expect(fs.readFileSync(path.join(ROOT, 'ship', 'DRIVER.md'), 'utf8')).toContain(`[--review-targeted @${recordPath}]`);
expect(section('review-army')).toContain(`\`${recordPath}\` removed`);
const repo = makeFixtureRepo({ prefix: 'targeted-wire-' });
repos.push(repo);
repo.git('checkout', '-q', '-b', 'feat');
fs.mkdirSync(path.join(repo.work, 'src'), { recursive: true });
fs.writeFileSync(path.join(repo.work, 'src', 'charge.ts'), 'export const spend_cap_usd = 5;\n');
repo.git('add', '-A'); repo.git('commit', '-q', '-m', 'charge');
const expand = () => spawnSync('bash', ['-c', `printf '%s\\n' ${optional![0]}`], { cwd: repo.work, encoding: 'utf8', timeout: 10_000 }).stdout.split('\n').filter(Boolean);
expect(expand()).toEqual([]);
fs.mkdirSync(path.join(repo.work, '.gstack', 'tmp'), { recursive: true });
fs.writeFileSync(path.join(repo.work, recordPath!), JSON.stringify({ classes: ['money'], rules: TARGETED_RULES, brief_sha: targetedBriefSha(), status: 'done', findings: 0, raised_by: 'targeted' }) + '\n');
const args = expand();
expect(args).toEqual(['--review-targeted', `@${recordPath}`]);
const write = runBin(repo, 'gstack-ship-receipt', ['write', '--base', 'main', '--session-kind', 'unattended', ...args, '--json']);
expect(write.status, write.err).toBe(0);
const receipt = JSON.parse(write.out) as ShipReceipt;
expect(receiptTargetedLine(['money'], receipt)).toBe('REVIEW_TARGETED: required=money receipt=money verdict=ok');
});
});
describe('the receipt record and the queue line', () => {
const record = { classes: ['gate', 'money'], rules: TARGETED_RULES, brief_sha: 'a'.repeat(64), status: 'done', findings: 0, raised_by: 'targeted' };
test('parseReviewTargeted binds the receipt\'s product patch, orders the classes and refuses a malformed record', () => {
@@ -0,0 +1,4 @@
{
"bytes": 270,
"why": "GST-464: Step 11 saves the targeted record and the Step 19 and handoff receipt writes pass it; Step 9 clears it"
}