fix(GSTA-131): ship review pass-2 repairs: Step 11 follows the last full-pass plan, Important Rules restored, ship goldens regenerated, true per-class medians, review-policy transitions and once-per-invocation voices, docs/** assets stay docs-class, CLI and fix-check binding tests

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-28 00:17:02 +00:00
1 parent 9c34fda1f3
commit 0da1b82912
17 files changed
+134 -87

No files matched your search

+7 -4
View File
@@ -112,7 +112,7 @@ incomplete coverage, which blocks until the reviewer is rerun.
| a full pass whose fix touched a gate-class path, below the cap | full pass on the range |
| a full pass that fixed only below-floor findings | one fix-check |
| a full pass at the cap that fixed a blocking finding | one fix-check |
| a full pass at the cap, or with no fix, leaving a blocking finding open | stop: name it for the owner |
| a full pass that leaves a blocking finding open (fixed or not) | stop: name it for the owner |
| a fix-check that found nothing blocking | done |
| a fix-check that found a blocking finding | stop: name it for the owner |
@@ -134,7 +134,7 @@ to code, and code with the gate scan unavailable fails closed to gate.
|---|---|---|
| **docs** | markdown no agent or gate reads: README, CHANGELOG, TODOS, how-to and explanation docs | the parent's core checklist only |
| **tests** | test files and fixtures only | core plus the testing specialist |
| **code** | everything else, including every `SKILL.md` and template, `*/sections/**`, `scripts/resolvers/**`, `docs/*-policy.md`, checklists, specialists and bins | specialists by scope (as before), the native adversarial pass, the outside voice; the Red Team past 200 lines or after a CRITICAL |
| **code** | everything else, including every `SKILL.md` and template, any markdown in a skill directory, `*/sections/**`, `scripts/resolvers/**`, `docs/*-policy.md`, `docs/captain.md`, `docs/unattended.md`, `docs/pregate.md`, checklists, specialists and bins | specialists by scope (as before), the native adversarial pass, the outside voice; the Red Team past 200 lines or after a CRITICAL |
| **gate** | code plus auth or migration scope, a hunk the gate scan tags (an RH rule: a relaxed threshold, a skipped test), a deleted code file, CI workflows, credential or state-root paths | code's set, plus the Red Team always, at full effort, and no hit-rate gating |
**Numbers keep the outside voice.** On a docs or tests diff the outside voice is
@@ -145,8 +145,11 @@ claim always dispatches it. The claims' fingerprint (`fingerprint=`) is passed
back as `--reviewed-numbers`; when a later fix changes a claim, only the
outside voice reruns, on the numbers.
**The adversarial and outside voices run once**, on pass 1's diff (Step 11).
Later full passes rerun the specialists on the range only.
**The adversarial and outside voices run once per invocation** (Step 11, on the
final tree), as the last full-pass plan lists them; after they ran, decision 2
passes `--adversarial-ran` so a repeat lists them no more. Later full passes rerun the
specialists on the range only. Step 11.5 binds a planned skip through the Step 9
record's `CURRENT` freshness instead of a native record.
**Effort.** Every reviewer that can block a ship (the core checklist,
specialists, the Red Team, the native adversarial pass, fix-checks) runs at
+1 -1
View File
@@ -50,7 +50,7 @@ export function detectUiScope(implementationPlan: string): UiScope {
/** Docs-class: what /ship's review classifies as docs (lib/review-depth.ts classifyPath: README, CHANGELOG, TODOS and prose markdown outside agent-read and skill-directory paths), plus a docs directory named without a file. */
export function docsClassPath(file: string, root: string): boolean {
const p = file.replace(/^\.\//, '');
if (/^docs\/[^.]*$/.test(p)) return true;
if (p.startsWith('docs/') && !/\.mdx?$/i.test(p)) return true;
return classifyPath(p, root) === 'docs';
}
+2 -2
View File
@@ -128,8 +128,8 @@ export interface NextStep { step: Step; reason: string }
/**
* The transition table (docs/review-policy.md#transitions). Pass 1 is full. A fixed
* blocking finding earns a second full pass on the range; any other fix earns one
* fix-check; two full passes is the cap; a blocker still open after the last
* allowed step stops for the owner.
* fix-check; two full passes is the cap; a blocker still open after any step
* stops for the owner.
*/
export function nextStep(h: History): NextStep {
if (h.last === 'none') return { step: 'full', reason: 'pass 1 reviews the whole diff' };
+3 -4
View File
@@ -162,13 +162,12 @@ function main(argv: string[]): number {
for (const r of rows) lines.push(`| ${r.fixture} | ${r.class} | ${r.scenario} | ${r.legacy_dispatches} → ${r.new_dispatches} | ${r.legacy_tokens} → ${r.new_tokens} | ${r.ratio} | ${r.ending} |`);
lines.push('', '| fixture | /autoplan scenario | voice dispatches legacy → new |', '|---|---|---|');
for (const a of ap) for (const r of a.runs) lines.push(`| ${a.fixture} | ${r.scenario} | ${r.legacy} → ${r.next} |`);
const median = (v: number[]) => { const m = Math.floor(v.length / 2); return v.length % 2 ? v[m]! : Math.round(((v[m - 1]! + v[m]!) / 2) * 100) / 100; };
const ratios = rows.map(r => Number(r.ratio)).sort((a, b) => a - b);
const mid = Math.floor(ratios.length / 2);
const median = ratios.length % 2 ? ratios[mid]! : Math.round(((ratios[mid - 1]! + ratios[mid]!) / 2) * 100) / 100;
lines.push('', `SHIP_TOKEN_RATIO: median=${median} min=${ratios[0]} max=${ratios[ratios.length - 1]} (n=${ratios.length})`);
lines.push('', `SHIP_TOKEN_RATIO: median=${median(ratios)} min=${ratios[0]} max=${ratios[ratios.length - 1]} (n=${ratios.length})`);
const byClass = new Map<string, number[]>();
for (const r of rows) byClass.set(String(r.class), [...(byClass.get(String(r.class)) ?? []), Number(r.ratio)].sort((x, y) => x - y));
lines.push(`SHIP_TOKEN_RATIO_BY_CLASS: ${[...byClass].map(([c, v]) => `${c} median=${v[Math.floor(v.length / 2)]} (n=${v.length})`).join('; ')}`);
lines.push(`SHIP_TOKEN_RATIO_BY_CLASS: ${[...byClass].map(([c, v]) => `${c} median=${median(v)} (n=${v.length})`).join('; ')}`);
process.stdout.write(lines.join('\n') + '\n');
return 0;
}
+18 -12
View File
@@ -206,8 +206,8 @@ not a commit ID. Use `git diff <old-tree> <new-tree>` to compare these snapshots
only when it would fail a test, break production, or lose data or secrets;
everything below that floor ships as a named note in the PR body. Pass 1 reviews
the diff; a later pass reads only the range; two full passes plus one fix-check
is the cap. `NO FINDINGS` is a complete reviewer result. The ship receipt's
`review_loop` counters record the outcome; `ship: review_scope` (Step 3) is the knob.
is the cap. `NO FINDINGS` is a complete reviewer result. `ship: review_scope` (Step 3)
is the knob; the receipt's `review_loop` records the outcome.
### Ship control flow
@@ -329,11 +329,10 @@ repository-landing asks, including on Apple repos.
4. **Carry check.** If `gh pr list --head <branch-name> --state open --json number`
names a PR, run `~/.claude/skills/gstack/bin/gstack-ship-receipt carry --pr <n> --base <base> --dry-run`.
`CARRY: would carry` (product patch-id unchanged; only a base merge, tests or release
metadata moved): skip review, fix only what the merge broke (CHANGELOG, size caps), run
`gstack-pregate --allow-repo-commands`, then carry without `--dry-run`, adding `--pregate
.gstack/tmp/pregate.json`. `CARRY: carried` binds HEAD: stop at its `GSTACK_RESULT` line.
A failed check, a moved HEAD, `refused` or `current` re-runs Steps 2–16.
`CARRY: would carry` (same product patch-id): skip review, fix what the merge broke
(CHANGELOG, size caps), run `gstack-pregate --allow-repo-commands`, then carry without
`--dry-run` plus `--pregate .gstack/tmp/pregate.json`. `CARRY: carried` binds HEAD: stop.
A failed check, HEAD moving after pregate, `refused` or `current` re-runs Steps 2–16.
5. Display historical readiness using the dashboard below, then finish Step 1.
Prior CLEAR reviews or dashboard skips never replace Step 9's gates.
@@ -506,13 +505,13 @@ repeat Step 2 (decisions included) on the merged content, then Step 4.
## Step 11.5: Bind the reviews
1. **Select the two reviews.** Run `~/.claude/skills/gstack/bin/gstack-review-read`.
Select this invocation's final Step 9 record (`skill:"review"`, `via:"ship"`, or
3.5's `review-fix-check` when it ended the loop) and Step 11 native record
(`skill:"adversarial-review"`; none after `ADVERSARIAL: skipped`). Match each to its saved
Select this invocation's final Step 9 record (`skill:"review"`, `via:"ship"`, or a
last 3.5 `review-fix-check`) and Step 11 native record (`skill:"adversarial-review"`;
none after `ADVERSARIAL: skipped`). Match each to its saved
handle, original token and source; reject outside-provider or older records.
2. **Compare their content.** Require the native record's `review_binding.state`
to be `verified`. All three snapshots must match: its `wtree` (with no native record,
`review_freshness` `CURRENT`), the Step 9 record's
to be `verified`. All three snapshots must match: its `wtree` (none: `review_freshness`
`CURRENT`), the Step 9 record's
`review_binding.start_wtree` and `review_binding.end_wtree`. A mismatch or missing
record/field blocks release preparation: report **Review records missing or mismatched**
and insert `9 → 10 → 11 → 11.5` before Step 12. Bind the new records at 11.5.
@@ -998,3 +997,10 @@ hand-roll VERSION/package.json writes.
## Important Rules
Follow the numbered gates and their explicit exceptions.
- **Never force push.** Use regular `git push` only.
- **An early Greptile PR stays up.** A stop after Step 6.5 opened it leaves the draft
open with one comment saying why (greptile-early section); never close it.
- **Use the configured version file's format** (4-digit for VERSION); under NO_VERSION,
never invent one.
- **Step 7 generates coverage tests.** They must pass before committing. Never commit failing tests.
+18 -12
View File
@@ -72,8 +72,8 @@ not a commit ID. Use `git diff <old-tree> <new-tree>` to compare these snapshots
only when it would fail a test, break production, or lose data or secrets;
everything below that floor ships as a named note in the PR body. Pass 1 reviews
the diff; a later pass reads only the range; two full passes plus one fix-check
is the cap. `NO FINDINGS` is a complete reviewer result. The ship receipt's
`review_loop` counters record the outcome; `ship: review_scope` (Step 3) is the knob.
is the cap. `NO FINDINGS` is a complete reviewer result. `ship: review_scope` (Step 3)
is the knob; the receipt's `review_loop` records the outcome.
### Ship control flow
@@ -136,11 +136,10 @@ repository-landing asks, including on Apple repos.
4. **Carry check.** If `gh pr list --head <branch-name> --state open --json number`
names a PR, run `~/.claude/skills/gstack/bin/gstack-ship-receipt carry --pr <n> --base <base> --dry-run`.
`CARRY: would carry` (product patch-id unchanged; only a base merge, tests or release
metadata moved): skip review, fix only what the merge broke (CHANGELOG, size caps), run
`gstack-pregate --allow-repo-commands`, then carry without `--dry-run`, adding `--pregate
.gstack/tmp/pregate.json`. `CARRY: carried` binds HEAD: stop at its `GSTACK_RESULT` line.
A failed check, a moved HEAD, `refused` or `current` re-runs Steps 2–16.
`CARRY: would carry` (same product patch-id): skip review, fix what the merge broke
(CHANGELOG, size caps), run `gstack-pregate --allow-repo-commands`, then carry without
`--dry-run` plus `--pregate .gstack/tmp/pregate.json`. `CARRY: carried` binds HEAD: stop.
A failed check, HEAD moving after pregate, `refused` or `current` re-runs Steps 2–16.
5. Display historical readiness using the dashboard below, then finish Step 1.
Prior CLEAR reviews or dashboard skips never replace Step 9's gates.
@@ -229,13 +228,13 @@ repeat Step 2 (decisions included) on the merged content, then Step 4.
## Step 11.5: Bind the reviews
1. **Select the two reviews.** Run `~/.claude/skills/gstack/bin/gstack-review-read`.
Select this invocation's final Step 9 record (`skill:"review"`, `via:"ship"`, or
3.5's `review-fix-check` when it ended the loop) and Step 11 native record
(`skill:"adversarial-review"`; none after `ADVERSARIAL: skipped`). Match each to its saved
Select this invocation's final Step 9 record (`skill:"review"`, `via:"ship"`, or a
last 3.5 `review-fix-check`) and Step 11 native record (`skill:"adversarial-review"`;
none after `ADVERSARIAL: skipped`). Match each to its saved
handle, original token and source; reject outside-provider or older records.
2. **Compare their content.** Require the native record's `review_binding.state`
to be `verified`. All three snapshots must match: its `wtree` (with no native record,
`review_freshness` `CURRENT`), the Step 9 record's
to be `verified`. All three snapshots must match: its `wtree` (none: `review_freshness`
`CURRENT`), the Step 9 record's
`review_binding.start_wtree` and `review_binding.end_wtree`. A mismatch or missing
record/field blocks release preparation: report **Review records missing or mismatched**
and insert `9 → 10 → 11 → 11.5` before Step 12. Bind the new records at 11.5.
@@ -717,3 +716,10 @@ hand-roll VERSION/package.json writes.
## Important Rules
Follow the numbered gates and their explicit exceptions.
- **Never force push.** Use regular `git push` only.
- **An early Greptile PR stays up.** A stop after Step 6.5 opened it leaves the draft
open with one comment saying why (greptile-early section); never close it.
- **Use the configured version file's format** (4-digit for VERSION); under NO_VERSION,
never invent one.
- **Step 7 generates coverage tests.** They must pass before committing. Never commit failing tests.
+1 -1
View File
@@ -1,6 +1,6 @@
<!-- AUTO-GENERATED from adversarial.md.tmpl — do not edit directly -->
<!-- Regenerate: bun run gen:skill-docs -->
**Step 9's latest plan governs this step:** run the native pass only on
**Step 9's last full-pass plan governs this step:** run the native pass only on
`DISPATCH: adversarial-native` and the outside voice only on
`DISPATCH: outside-voice`; otherwise print `ADVERSARIAL: skipped (review plan)`.
+1 -1
View File
@@ -1,4 +1,4 @@
**Step 9's latest plan governs this step:** run the native pass only on
**Step 9's last full-pass plan governs this step:** run the native pass only on
`DISPATCH: adversarial-native` and the outside voice only on
`DISPATCH: outside-voice`; otherwise print `ADVERSARIAL: skipped (review plan)`.
+1 -1
View File
@@ -182,7 +182,7 @@ describe('ESCALATE at the round cap', () => {
describe('docs-only', () => {
test('docs-class paths', () => {
for (const p of ['README.md', 'CHANGELOG.md', 'TODOS.md', 'docs/howto-retry.md', 'docs/designs/x/PLAN.md', 'notes.md', './README.md']) expect(docsClassPath(p, ROOT), p).toBe(true);
for (const p of ['README.md', 'CHANGELOG.md', 'TODOS.md', 'docs/howto-retry.md', 'docs/designs/x/PLAN.md', 'notes.md', './README.md', 'docs/images/flow.png', 'docs/designs']) expect(docsClassPath(p, ROOT), p).toBe(true);
for (const p of ['docs/review-policy.md', 'autoplan/SKILL.md.tmpl', 'autoplan/SKILL.md', 'review/sections/army.md', 'scripts/resolvers/autoplan-driver.ts', 'autoplan/DRIVER.md',
'captain/templates/brief.md', 'autoplan/notes.md', 'AGENTS.md', 'CLAUDE.md', 'lib/worker.ts', 'bin/gstack-autoplan']) expect(docsClassPath(p, ROOT), p).toBe(false);
expect(detectDocsOnly('Rewrite README.md and docs/howto.md.', ROOT)).toMatchObject({ docsOnly: true, code: [] });
+12 -11
View File
@@ -206,8 +206,8 @@ not a commit ID. Use `git diff <old-tree> <new-tree>` to compare these snapshots
only when it would fail a test, break production, or lose data or secrets;
everything below that floor ships as a named note in the PR body. Pass 1 reviews
the diff; a later pass reads only the range; two full passes plus one fix-check
is the cap. `NO FINDINGS` is a complete reviewer result. The ship receipt's
`review_loop` counters record the outcome; `ship: review_scope` (Step 3) is the knob.
is the cap. `NO FINDINGS` is a complete reviewer result. `ship: review_scope` (Step 3)
is the knob; the receipt's `review_loop` records the outcome.
### Ship control flow
@@ -328,12 +328,11 @@ repository-landing asks, including on Apple repos.
`git log origin/<base>..HEAD --oneline`.
4. **Carry check.** If `gh pr list --head <branch-name> --state open --json number`
names a PR, run `~/.gemini/antigravity-cli/skills/gstack/bin/gstack-ship-receipt carry --pr <n> --base <base>`.
`CARRY: carried` means the product diff against `origin/<base>` has the same
patch-id as the reviewed head's (only a base merge, tests or release metadata
changed; the receipt's `carry` object holds the range-diff evidence): the receipt
now binds HEAD and the run is complete at its `GSTACK_RESULT` line, so stop.
`CARRY: refused` or `current` continues; a product change always re-runs Steps 2–16.
names a PR, run `~/.gemini/antigravity-cli/skills/gstack/bin/gstack-ship-receipt carry --pr <n> --base <base> --dry-run`.
`CARRY: would carry` (same product patch-id): skip review, fix what the merge broke
(CHANGELOG, size caps), run `gstack-pregate --allow-repo-commands`, then carry without
`--dry-run` plus `--pregate .gstack/tmp/pregate.json`. `CARRY: carried` binds HEAD: stop.
A failed check, HEAD moving after pregate, `refused` or `current` re-runs Steps 2–16.
5. Display historical readiness using the dashboard below, then finish Step 1.
Prior CLEAR reviews or dashboard skips never replace Step 9's gates.
@@ -506,11 +505,13 @@ repeat Step 2 (decisions included) on the merged content, then Step 4.
## Step 11.5: Bind the reviews
1. **Select the two reviews.** Run `~/.gemini/antigravity-cli/skills/gstack/bin/gstack-review-read`.
Select this invocation's final Step 9.4 record (`skill:"review"`, `via:"ship"`)
and Step 11 native record (`skill:"adversarial-review"`). Match each to its saved
Select this invocation's final Step 9 record (`skill:"review"`, `via:"ship"`, or a
last 3.5 `review-fix-check`) and Step 11 native record (`skill:"adversarial-review"`;
none after `ADVERSARIAL: skipped`). Match each to its saved
handle, original token and source; reject outside-provider or older records.
2. **Compare their content.** Require the native record's `review_binding.state`
to be `verified`. All three snapshots must match: its `wtree`, Step 9.4's
to be `verified`. All three snapshots must match: its `wtree` (none: `review_freshness`
`CURRENT`), the Step 9 record's
`review_binding.start_wtree` and `review_binding.end_wtree`. A mismatch or missing
record/field blocks release preparation: report **Review records missing or mismatched**
and insert `9 → 10 → 11 → 11.5` before Step 12. Bind the new records at 11.5.
+12 -11
View File
@@ -206,8 +206,8 @@ not a commit ID. Use `git diff <old-tree> <new-tree>` to compare these snapshots
only when it would fail a test, break production, or lose data or secrets;
everything below that floor ships as a named note in the PR body. Pass 1 reviews
the diff; a later pass reads only the range; two full passes plus one fix-check
is the cap. `NO FINDINGS` is a complete reviewer result. The ship receipt's
`review_loop` counters record the outcome; `ship: review_scope` (Step 3) is the knob.
is the cap. `NO FINDINGS` is a complete reviewer result. `ship: review_scope` (Step 3)
is the knob; the receipt's `review_loop` records the outcome.
### Ship control flow
@@ -328,12 +328,11 @@ repository-landing asks, including on Apple repos.
`git log origin/<base>..HEAD --oneline`.
4. **Carry check.** If `gh pr list --head <branch-name> --state open --json number`
names a PR, run `~/.claude/skills/gstack/bin/gstack-ship-receipt carry --pr <n> --base <base>`.
`CARRY: carried` means the product diff against `origin/<base>` has the same
patch-id as the reviewed head's (only a base merge, tests or release metadata
changed; the receipt's `carry` object holds the range-diff evidence): the receipt
now binds HEAD and the run is complete at its `GSTACK_RESULT` line, so stop.
`CARRY: refused` or `current` continues; a product change always re-runs Steps 2–16.
names a PR, run `~/.claude/skills/gstack/bin/gstack-ship-receipt carry --pr <n> --base <base> --dry-run`.
`CARRY: would carry` (same product patch-id): skip review, fix what the merge broke
(CHANGELOG, size caps), run `gstack-pregate --allow-repo-commands`, then carry without
`--dry-run` plus `--pregate .gstack/tmp/pregate.json`. `CARRY: carried` binds HEAD: stop.
A failed check, HEAD moving after pregate, `refused` or `current` re-runs Steps 2–16.
5. Display historical readiness using the dashboard below, then finish Step 1.
Prior CLEAR reviews or dashboard skips never replace Step 9's gates.
@@ -506,11 +505,13 @@ repeat Step 2 (decisions included) on the merged content, then Step 4.
## Step 11.5: Bind the reviews
1. **Select the two reviews.** Run `~/.claude/skills/gstack/bin/gstack-review-read`.
Select this invocation's final Step 9.4 record (`skill:"review"`, `via:"ship"`)
and Step 11 native record (`skill:"adversarial-review"`). Match each to its saved
Select this invocation's final Step 9 record (`skill:"review"`, `via:"ship"`, or a
last 3.5 `review-fix-check`) and Step 11 native record (`skill:"adversarial-review"`;
none after `ADVERSARIAL: skipped`). Match each to its saved
handle, original token and source; reject outside-provider or older records.
2. **Compare their content.** Require the native record's `review_binding.state`
to be `verified`. All three snapshots must match: its `wtree`, Step 9.4's
to be `verified`. All three snapshots must match: its `wtree` (none: `review_freshness`
`CURRENT`), the Step 9 record's
`review_binding.start_wtree` and `review_binding.end_wtree`. A mismatch or missing
record/field blocks release preparation: report **Review records missing or mismatched**
and insert `9 → 10 → 11 → 11.5` before Step 12. Bind the new records at 11.5.
+12 -11
View File
@@ -204,8 +204,8 @@ not a commit ID. Use `git diff <old-tree> <new-tree>` to compare these snapshots
only when it would fail a test, break production, or lose data or secrets;
everything below that floor ships as a named note in the PR body. Pass 1 reviews
the diff; a later pass reads only the range; two full passes plus one fix-check
is the cap. `NO FINDINGS` is a complete reviewer result. The ship receipt's
`review_loop` counters record the outcome; `ship: review_scope` (Step 3) is the knob.
is the cap. `NO FINDINGS` is a complete reviewer result. `ship: review_scope` (Step 3)
is the knob; the receipt's `review_loop` records the outcome.
### Ship control flow
@@ -326,12 +326,11 @@ repository-landing asks, including on Apple repos.
`git log origin/<base>..HEAD --oneline`.
4. **Carry check.** If `gh pr list --head <branch-name> --state open --json number`
names a PR, run `$GSTACK_ROOT/bin/gstack-ship-receipt carry --pr <n> --base <base>`.
`CARRY: carried` means the product diff against `origin/<base>` has the same
patch-id as the reviewed head's (only a base merge, tests or release metadata
changed; the receipt's `carry` object holds the range-diff evidence): the receipt
now binds HEAD and the run is complete at its `GSTACK_RESULT` line, so stop.
`CARRY: refused` or `current` continues; a product change always re-runs Steps 2–16.
names a PR, run `$GSTACK_ROOT/bin/gstack-ship-receipt carry --pr <n> --base <base> --dry-run`.
`CARRY: would carry` (same product patch-id): skip review, fix what the merge broke
(CHANGELOG, size caps), run `gstack-pregate --allow-repo-commands`, then carry without
`--dry-run` plus `--pregate .gstack/tmp/pregate.json`. `CARRY: carried` binds HEAD: stop.
A failed check, HEAD moving after pregate, `refused` or `current` re-runs Steps 2–16.
5. Display historical readiness using the dashboard below, then finish Step 1.
Prior CLEAR reviews or dashboard skips never replace Step 9's gates.
@@ -505,11 +504,13 @@ repeat Step 2 (decisions included) on the merged content, then Step 4.
## Step 11.5: Bind the reviews
1. **Select the two reviews.** Run `$GSTACK_ROOT/bin/gstack-review-read`.
Select this invocation's final Step 9.4 record (`skill:"review"`, `via:"ship"`)
and Step 11 native record (`skill:"adversarial-review"`). Match each to its saved
Select this invocation's final Step 9 record (`skill:"review"`, `via:"ship"`, or a
last 3.5 `review-fix-check`) and Step 11 native record (`skill:"adversarial-review"`;
none after `ADVERSARIAL: skipped`). Match each to its saved
handle, original token and source; reject outside-provider or older records.
2. **Compare their content.** Require the native record's `review_binding.state`
to be `verified`. All three snapshots must match: its `wtree`, Step 9.4's
to be `verified`. All three snapshots must match: its `wtree` (none: `review_freshness`
`CURRENT`), the Step 9 record's
`review_binding.start_wtree` and `review_binding.end_wtree`. A mismatch or missing
record/field blocks release preparation: report **Review records missing or mismatched**
and insert `9 → 10 → 11 → 11.5` before Step 12. Bind the new records at 11.5.
+12 -11
View File
@@ -206,8 +206,8 @@ not a commit ID. Use `git diff <old-tree> <new-tree>` to compare these snapshots
only when it would fail a test, break production, or lose data or secrets;
everything below that floor ships as a named note in the PR body. Pass 1 reviews
the diff; a later pass reads only the range; two full passes plus one fix-check
is the cap. `NO FINDINGS` is a complete reviewer result. The ship receipt's
`review_loop` counters record the outcome; `ship: review_scope` (Step 3) is the knob.
is the cap. `NO FINDINGS` is a complete reviewer result. `ship: review_scope` (Step 3)
is the knob; the receipt's `review_loop` records the outcome.
### Ship control flow
@@ -328,12 +328,11 @@ repository-landing asks, including on Apple repos.
`git log origin/<base>..HEAD --oneline`.
4. **Carry check.** If `gh pr list --head <branch-name> --state open --json number`
names a PR, run `$GSTACK_ROOT/bin/gstack-ship-receipt carry --pr <n> --base <base>`.
`CARRY: carried` means the product diff against `origin/<base>` has the same
patch-id as the reviewed head's (only a base merge, tests or release metadata
changed; the receipt's `carry` object holds the range-diff evidence): the receipt
now binds HEAD and the run is complete at its `GSTACK_RESULT` line, so stop.
`CARRY: refused` or `current` continues; a product change always re-runs Steps 2–16.
names a PR, run `$GSTACK_ROOT/bin/gstack-ship-receipt carry --pr <n> --base <base> --dry-run`.
`CARRY: would carry` (same product patch-id): skip review, fix what the merge broke
(CHANGELOG, size caps), run `gstack-pregate --allow-repo-commands`, then carry without
`--dry-run` plus `--pregate .gstack/tmp/pregate.json`. `CARRY: carried` binds HEAD: stop.
A failed check, HEAD moving after pregate, `refused` or `current` re-runs Steps 2–16.
5. Display historical readiness using the dashboard below, then finish Step 1.
Prior CLEAR reviews or dashboard skips never replace Step 9's gates.
@@ -507,11 +506,13 @@ repeat Step 2 (decisions included) on the merged content, then Step 4.
## Step 11.5: Bind the reviews
1. **Select the two reviews.** Run `$GSTACK_ROOT/bin/gstack-review-read`.
Select this invocation's final Step 9.4 record (`skill:"review"`, `via:"ship"`)
and Step 11 native record (`skill:"adversarial-review"`). Match each to its saved
Select this invocation's final Step 9 record (`skill:"review"`, `via:"ship"`, or a
last 3.5 `review-fix-check`) and Step 11 native record (`skill:"adversarial-review"`;
none after `ADVERSARIAL: skipped`). Match each to its saved
handle, original token and source; reject outside-provider or older records.
2. **Compare their content.** Require the native record's `review_binding.state`
to be `verified`. All three snapshots must match: its `wtree`, Step 9.4's
to be `verified`. All three snapshots must match: its `wtree` (none: `review_freshness`
`CURRENT`), the Step 9 record's
`review_binding.start_wtree` and `review_binding.end_wtree`. A mismatch or missing
record/field blocks release preparation: report **Review records missing or mismatched**
and insert `9 → 10 → 11 → 11.5` before Step 12. Bind the new records at 11.5.
+2 -2
View File
@@ -155,7 +155,7 @@ export const CARVE_GUARDS: Record<string, CarveGuard> = {
},
behavioral: 'external',
externalTest: 'test/skill-e2e-ship-section-loading.test.ts',
maxSkeletonBytes: 60_913, // GSTA-96 hoist merged with GSTA-92 phase 2 (2026-10-26): the hoisted skeleton (59,706) plus this PR's growth, keeping the hoist's 65-byte headroom; measured 60,848. Before: + v2.0 {{ASIDE_RESEARCH}} (Aside first, WebSearch fallback); measured 78_275. + gate integrity (HONEST_WORK_GATE_INTEGRITY W3): Step 16 stage 4 ZERO-RUN/unknown receipt rows, stage 5 honesty inventory, Step 17 open-gate ready-PR gate; measured 80,540 (2026-10-10). + rule 22 / GBRA-108 (2026-10-25): Step 22's terminal line (gstack-ship-receipt result, the GSTACK_RESULT line a parent greps) and Step 18's unattended routing (an open PR at HEAD gets its body updated, else handoff), offset in part by tightening Steps 14, 16, 18 and 20 prose after the 2026-10-14 tightening left 2 bytes of headroom; measured 81,535. + harness lane E (2026-10-26, issue row 3: four Captains re-ran /ship on identical product code): Step 1's carry check (gstack-ship-receipt carry binds an earlier receipt to HEAD when the product patch-id is unchanged); measured 82,165. + the Review policy paragraph (the blocking floor, pass structure and cap, NO FINDINGS, the review_loop receipt counters, the review_scope knob), stated in the skeleton because gstack is the canonical holder of its review policy (docs/review-policy.md); measured 82,645.
maxSkeletonBytes: 60_960, // GSTA-131 token efficiency (2026-10-27, Garry's D1): Step 1's delta carry (dry run, pregate at HEAD, then carry --pregate) and Step 11.5's fix-check and planned-skip binding, +60 bytes after tightening Step 1 and the Review policy paragraph; measured 60,960 (before: 60,900 on 10b77de4). Before that: GSTA-96 hoist merged with GSTA-92 phase 2 (2026-10-26): the hoisted skeleton (59,706) plus this PR's growth, keeping the hoist's 65-byte headroom; measured 60,848. Before: + v2.0 {{ASIDE_RESEARCH}} (Aside first, WebSearch fallback); measured 78_275. + gate integrity (HONEST_WORK_GATE_INTEGRITY W3): Step 16 stage 4 ZERO-RUN/unknown receipt rows, stage 5 honesty inventory, Step 17 open-gate ready-PR gate; measured 80,540 (2026-10-10). + rule 22 / GBRA-108 (2026-10-25): Step 22's terminal line (gstack-ship-receipt result, the GSTACK_RESULT line a parent greps) and Step 18's unattended routing (an open PR at HEAD gets its body updated, else handoff), offset in part by tightening Steps 14, 16, 18 and 20 prose after the 2026-10-14 tightening left 2 bytes of headroom; measured 81,535. + harness lane E (2026-10-26, issue row 3: four Captains re-ran /ship on identical product code): Step 1's carry check (gstack-ship-receipt carry binds an earlier receipt to HEAD when the product patch-id is unchanged); measured 82,165. + the Review policy paragraph (the blocking floor, pass structure and cap, NO FINDINGS, the review_loop receipt counters, the review_scope knob), stated in the skeleton because gstack is the canonical holder of its review policy (docs/review-policy.md); measured 82,645.
minUnionBytes: 181_000, // token-reduction Phases 1-2 (v1.69.x branch); measured union 201,464
mustContain: ['VERSION', 'CHANGELOG', 'review', 'merge', 'PR'],
// v1.58.5.0: pre-push-guard install (#2077) stacks on the shared first-run-guidance preamble.
@@ -168,7 +168,7 @@ export const CARVE_GUARDS: Record<string, CarveGuard> = {
// wave's headline capability) grows the union to 1.195x. Deliberate:
// the section is on-demand (loads only for Apple store targets), so
// per-invocation cost for non-iOS ships is one manifest line.
maxSizeRatio: 1.7615, // GSTA-131 token efficiency (2026-10-27, Garry's D1; GStack Minder's re-pin with the rule 19 grant): the review-army section's review plan (gstack-review-plan dispatch lines and effort, the plan-driven repeat decision, the 3.5 fix-check), the adversarial section's plan line, Step 1's delta carry sequence, Step 11.5's fix-check and planned-skip binding and the specialists' 400-word findings cap and the Step 7/8 audits' light-effort line, offset by a tighter Step 1, +1,614 union bytes; measured 330,638 / 187,706 = 1.76147 on the tree merged with 10b77de4 (before: 329,024 = 1.75287). Before that: GSTA-128 flaky pass (#3516, 2026-10-27, GStack Minder's rule 19 grant): the review-army section's small-diff clause names State Machines as item 9's call and a State Machines-only specialist list, +5 union bytes on a main (703b0049, v1.91.280.0) that sat 4 bytes under the 1.7524 cap after #3515 and #3517; measured 328,937 / 187,706 = 1.7524 (before: 328,932 = 1.7524) on the merged tree, pinned one in the last place above. Before that: GSTA-96 hoist merged with v1.91.273.0 (#3501, 2026-10-27): main's 1.7500 plus the hoist's own raise, no trim; measured 328,918 / 187,706 = 1.7523 on the merged tree. Before: Shared advisory identity/dedup + critical-severity validation: 248,065 union bytes / 187,706 baseline = 1.3216 (2026-09-17). + test value bar in the lazy Step 7 section (value cards, weak paths, gate table, base control, machine checks; ~13.6KB): measured 1.396 (2026-09-29). + W1 guarded state-root resolution (`eval gstack-paths; : "${GSTACK_STATE_ROOT:?…}"`) in the Context Recovery preamble, the eureka log and each state-writing bash block; measured 1.401 (2026-09-30). + the shared QA review step's plan-check timing rule (plan checks and their revalidation run on --timeout-ms after smoke expiry); measured 1.4022 (2026-09-30). + the shared TEST_BOOTSTRAP owned-change undo rules (s03 H1-H3, M1) and the value-bar coverage wording (s03 M2/M3); measured 1.4044 (2026-10-02). + severe fix wave ship-review lane: G1 NO_VERSION dispatch, title and CHANGELOG arms (versionless repos ship without inventing a version), F1 parent-side plan binding before the audit dispatch (binding, docs/designs candidates, exact not-run line) and D3's data-not-instructions line; +2,126 union bytes, measured 1.4135 (2026-10-03). + INV-1 consumer lines (outside review `unverified`/`unavailable` is missing coverage in the readiness note and PR body); measured 1.4148 (2026-10-03). + v1.91.19.0 severe fix wave: verdict-form outside-review callers with sandbox preflight and first-use notice (INV-1/B1/Q2), P0/P1 gate and unverified wording, bound-plan discovery (F1), visible LEARNINGS failures (B5), carved external pointers; measured 267,752 / 187,706 = 1.4264 (2026-10-03). + v1.91.18.0 (#3023) merged into the v1.91.19.0 wave; measured 267,874 / 187,706 = 1.4271 (2026-10-04). + Oct 6 fix wave: quota_exhausted preflight branch (#3051) and quoted-heredoc issue template (#3046); measured 268,199 / 187,706 = 1.4288 (2026-10-06). + Step 10's inline no-PR check before the Greptile dispatch (#3020); measured 268,655 / 187,706 = 1.4313 (2026-10-06). + Oct 6 fix wave A3/CEO-12: free text reaches commands only through agent-written mktemp files (mktemp block, write rule, path rebuild and refusal at each posting, prompt and brief site); measured 275,422 / 187,706 = 1.4673 (2026-10-06). Combined Oct 6 wave head (B free-text files + D measure loop + A rework): measured 280,363 / 187,706 = 1.4936 (2026-10-06). + gate-once push rule (no second paid gate after a CI push); measured 280,573 / 187,706 = 1.4947 (2026-10-06). + the measurement bar's decision step (exit-code branches for MEETS, MEETS-qualified, needs-classify with the qualifying and fix-round classes, EXTEND once; the measure prose was tightened to offset most of it); measured 281,058 / 187,706 = 1.4973 (2026-10-06). + Oct 7 wave B3: the Codex probe runs as a command (select-model capture lines, check-sandbox/probe-model/run-with-timeout subcommands) instead of being sourced; measured 281,438 / 187,706 = 1.4994 (2026-10-07). + Oct 7 wave CEO-28/DX-11: review prompts ask for a severity per finding and design proposals use the proposal gate; measured 281,556 / 187,706 = 1.5000 (2026-10-07). + Oct 7 wave B4: /ship publishes through gstack-post (title prepared in a file in the pr-body section, compose block plus gstack-post exit-code branches replacing the two scans); measured 281,894 / 187,706 = 1.5018 (2026-10-07). + B2 early Greptile PR (lazy Step 6.5 section: detect, public-repo consent, push, provisional title file, open; Step 10's bounded wait; the stop-leaves-the-draft rule); measured 286,626 / 187,706 = 1.5270 (2026-10-07). B3, the classifier, B4 and B2 merged: measured 287,128 / 187,706 = 1.5297 (2026-10-07). + Oct 7 wave guard (CEO-20): conditional `run_in_background` request plus the background-run recovery at each foreground dispatch site; measured 281,370 / 187,706 = 1. Line truncated
maxSizeRatio: 1.7631, // GSTA-131 token efficiency (2026-10-27, Garry's D1; GStack Minder's re-pin with the rule 19 grant): the review-army section's review plan (gstack-review-plan dispatch lines and effort, the plan-driven repeat decision, the 3.5 fix-check), the adversarial section's plan line, Step 1's delta carry sequence, Step 11.5's fix-check and planned-skip binding and the specialists' 400-word findings cap and the Step 7/8 audits' light-effort line, offset by a tighter Step 1, +1,917 union bytes; measured 330,941 / 187,706 = 1.76308 on the tree merged with 10b77de4 (before: 329,024 = 1.75287). Before that: GSTA-128 flaky pass (#3516, 2026-10-27, GStack Minder's rule 19 grant): the review-army section's small-diff clause names State Machines as item 9's call and a State Machines-only specialist list, +5 union bytes on a main (703b0049, v1.91.280.0) that sat 4 bytes under the 1.7524 cap after #3515 and #3517; measured 328,937 / 187,706 = 1.7524 (before: 328,932 = 1.7524) on the merged tree, pinned one in the last place above. Before that: GSTA-96 hoist merged with v1.91.273.0 (#3501, 2026-10-27): main's 1.7500 plus the hoist's own raise, no trim; measured 328,918 / 187,706 = 1.7523 on the merged tree. Before: Shared advisory identity/dedup + critical-severity validation: 248,065 union bytes / 187,706 baseline = 1.3216 (2026-09-17). + test value bar in the lazy Step 7 section (value cards, weak paths, gate table, base control, machine checks; ~13.6KB): measured 1.396 (2026-09-29). + W1 guarded state-root resolution (`eval gstack-paths; : "${GSTACK_STATE_ROOT:?…}"`) in the Context Recovery preamble, the eureka log and each state-writing bash block; measured 1.401 (2026-09-30). + the shared QA review step's plan-check timing rule (plan checks and their revalidation run on --timeout-ms after smoke expiry); measured 1.4022 (2026-09-30). + the shared TEST_BOOTSTRAP owned-change undo rules (s03 H1-H3, M1) and the value-bar coverage wording (s03 M2/M3); measured 1.4044 (2026-10-02). + severe fix wave ship-review lane: G1 NO_VERSION dispatch, title and CHANGELOG arms (versionless repos ship without inventing a version), F1 parent-side plan binding before the audit dispatch (binding, docs/designs candidates, exact not-run line) and D3's data-not-instructions line; +2,126 union bytes, measured 1.4135 (2026-10-03). + INV-1 consumer lines (outside review `unverified`/`unavailable` is missing coverage in the readiness note and PR body); measured 1.4148 (2026-10-03). + v1.91.19.0 severe fix wave: verdict-form outside-review callers with sandbox preflight and first-use notice (INV-1/B1/Q2), P0/P1 gate and unverified wording, bound-plan discovery (F1), visible LEARNINGS failures (B5), carved external pointers; measured 267,752 / 187,706 = 1.4264 (2026-10-03). + v1.91.18.0 (#3023) merged into the v1.91.19.0 wave; measured 267,874 / 187,706 = 1.4271 (2026-10-04). + Oct 6 fix wave: quota_exhausted preflight branch (#3051) and quoted-heredoc issue template (#3046); measured 268,199 / 187,706 = 1.4288 (2026-10-06). + Step 10's inline no-PR check before the Greptile dispatch (#3020); measured 268,655 / 187,706 = 1.4313 (2026-10-06). + Oct 6 fix wave A3/CEO-12: free text reaches commands only through agent-written mktemp files (mktemp block, write rule, path rebuild and refusal at each posting, prompt and brief site); measured 275,422 / 187,706 = 1.4673 (2026-10-06). Combined Oct 6 wave head (B free-text files + D measure loop + A rework): measured 280,363 / 187,706 = 1.4936 (2026-10-06). + gate-once push rule (no second paid gate after a CI push); measured 280,573 / 187,706 = 1.4947 (2026-10-06). + the measurement bar's decision step (exit-code branches for MEETS, MEETS-qualified, needs-classify with the qualifying and fix-round classes, EXTEND once; the measure prose was tightened to offset most of it); measured 281,058 / 187,706 = 1.4973 (2026-10-06). + Oct 7 wave B3: the Codex probe runs as a command (select-model capture lines, check-sandbox/probe-model/run-with-timeout subcommands) instead of being sourced; measured 281,438 / 187,706 = 1.4994 (2026-10-07). + Oct 7 wave CEO-28/DX-11: review prompts ask for a severity per finding and design proposals use the proposal gate; measured 281,556 / 187,706 = 1.5000 (2026-10-07). + Oct 7 wave B4: /ship publishes through gstack-post (title prepared in a file in the pr-body section, compose block plus gstack-post exit-code branches replacing the two scans); measured 281,894 / 187,706 = 1.5018 (2026-10-07). + B2 early Greptile PR (lazy Step 6.5 section: detect, public-repo consent, push, provisional title file, open; Step 10's bounded wait; the stop-leaves-the-draft rule); measured 286,626 / 187,706 = 1.5270 (2026-10-07). B3, the classifier, B4 and B2 merged: measured 287,128 / 187,706 = 1.5297 (2026-10-07). + Oct 7 wave guard (CEO-20): conditional `run_in_background` request plus the background-run recovery at each foreground dispatch site; measured 281,370 / 187,706 = 1. Line truncated
},
'plan-ceo-review': {
skill: 'plan-ceo-review',
+13
View File
@@ -178,6 +178,19 @@ describe('lib: deriveFromFindings, bindReview order and reviewFreshness', () =>
expect(fs.existsSync(path.join(dir, `${token}.json`))).toBe(false);
});
test('a review-fix-check record binds like a pass, so Step 11.5 can read its CURRENT freshness', () => {
const dir = path.join(home, 'starts', '.review-starts');
fs.mkdirSync(dir, { recursive: true });
const token = '66666666-2222-3333-4444-555555555555';
const w = 'c'.repeat(64);
fs.writeFileSync(path.join(dir, `${token}.json`), JSON.stringify({ skill: 'review-fix-check', repo: '/r', branch: 'b', wtree: w, started_at: 't' }));
const env = { GSTACK_REVIEW_DIR: path.join(home, 'starts'), GSTACK_REVIEW_REPO: '/r', GSTACK_REVIEW_BRANCH: 'b', GSTACK_STAMP_WTREE: w };
const bound = bindReview({ skill: 'review-fix-check', status: 'clean', completed: true, converged: true }, token, env);
expect(bound.review_binding).toMatchObject({ state: 'verified', start_wtree: w, end_wtree: w });
expect(reviewFreshness(bound, w)?.status).toBe('CURRENT');
expect(reviewFreshness(bound, 'd'.repeat(64))?.status).toBe('STALE');
});
test('reviewFreshness reads findings_open: resolved criticals are CURRENT, claimed issues_found stays conservative', () => {
const w = 'b'.repeat(64);
const base = { skill: 'review', completed: true, converged: true, wtree: w, review_binding: { state: 'verified', start_wtree: w, end_wtree: w } };
+16
View File
@@ -92,4 +92,20 @@ describe('gstack-review-plan', () => {
expect(spawnSync(BIN, [], { encoding: 'utf8', timeout: 10_000 }).status).toBe(2);
expect(run(repo({ 'a.md': 'x' }), ['--last', 'later']).status).toBe(2);
});
test('scratch under .gstack/tmp/ is ignored, a subdirectory run plans the whole repository, and --adversarial-ran drops the repeat adversarial pass', () => {
const r = repo({ 'docs/a.md': '# a\n' });
fs.mkdirSync(path.join(r.dir, '.gstack', 'tmp'), { recursive: true });
fs.writeFileSync(path.join(r.dir, '.gstack', 'tmp', 'fix-check-list.ts'), 'export const x = 1;\n');
const plan = JSON.parse(run(r, ['--json']).stdout);
expect(plan.class).toBe('docs');
expect(plan.paths).toEqual(['docs/a.md']);
const sub = spawnSync(BIN, ['--base', 'main', '--gate-tagged', '0', '--json'], { cwd: path.join(r.dir, 'docs'), encoding: 'utf8', timeout: 60_000, env: { ...process.env, GSTACK_STATE_ROOT: r.state } });
expect(JSON.parse(sub.stdout).paths).toEqual(['docs/a.md']);
const code = repo({ 'src/a.ts': Array.from({ length: 80 }, (_, i) => `export const v${i} = ${i};`).join('\n') + '\n' });
const repeat = ['--pass', '1', '--last', 'full', '--fixed-blocking'];
expect(run(code, repeat).stdout).toContain('DISPATCH: adversarial-native adversarial effort=full');
const ran = run(code, [...repeat, '--adversarial-ran']).stdout;
expect(ran).not.toContain('DISPATCH: adversarial-native');
expect(ran).toContain('SKIP: adversarial-native and outside-voice (run once per invocation)');
});
});
+3 -3
View File
@@ -46,16 +46,16 @@ describe('ship source controller', () => {
expect(decide).toContain('`NEXT: stop` stops here, unfixed; `full` is decision 3, `fix-check` 3.5, `done` 4.');
expect(decide).toContain('`gstack-review-log --start review-fix-check` token');
expect(decide).toContain('`"skill":"review-fix-check"` with `--finish <token>`');
expect(compact(read('ship/sections/adversarial.md.tmpl'))).toContain("Step 9's latest plan governs this step");
expect(compact(read('ship/sections/adversarial.md.tmpl'))).toContain("Step 9's last full-pass plan governs this step");
const bind = compact(entry.slice(entry.indexOf('## Step 11.5:'), entry.indexOf('## Step 12:')));
expect(bind).toContain("3.5's `review-fix-check` when it ended the loop");
expect(bind).toContain("last 3.5 `review-fix-check`");
expect(bind).toContain('none after `ADVERSARIAL: skipped`');
expect(bind).toContain('`review_freshness` `CURRENT`');
});
test('Step 1 carries a receipt only after a dry run and a pregate at HEAD', () => {
const carry = compact(entry.slice(entry.indexOf('4. **Carry check.**'), entry.indexOf('5. Display historical readiness')));
const order = ['carry --pr <n> --base <base> --dry-run', '`CARRY: would carry`', 'skip review', '`gstack-pregate --allow-repo-commands`', 'carry without `--dry-run`', '`--pregate .gstack/tmp/pregate.json`', '`CARRY: carried` binds HEAD', 'A failed check, a moved HEAD, `refused` or `current` re-runs Steps 2–16.'];
const order = ['carry --pr <n> --base <base> --dry-run', '`CARRY: would carry`', 'skip review', '`gstack-pregate --allow-repo-commands`', 'carry without `--dry-run`', '`--pregate .gstack/tmp/pregate.json`', '`CARRY: carried` binds HEAD', 'A failed check, HEAD moving after pregate, `refused` or `current` re-runs Steps 2–16.'];
const at = order.map(t => carry.indexOf(t));
expect(at.every(i => i >= 0)).toBe(true);
expect(at).toEqual([...at].sort((a, b) => a - b));