Skip to content

Commit f589770

Browse files
garrytanclaude
andauthored
v1.39.1.0 feat: EXIT PLAN MODE GATE for plan-mode review skills (garrytan#1512)
* feat: EXIT PLAN MODE GATE for plan-mode review skills Add a terminal BLOCKING checklist that verifies the plan file ends with `## GSTACK REVIEW REPORT` before ExitPlanMode is called. Lives at EOF of all four plan-* review skills (eng/ceo/design/devex) and inside codex Step 2A. Tones down the preamble's "Plan Status Footer" to a neutral forward reference so review-report rules don't bleed into operational skills (/ship /qa /review). Single source of truth: `generateExitPlanModeGate` in scripts/resolvers/review.ts, registered as EXIT_PLAN_MODE_GATE in scripts/resolvers/index.ts. New test in test/gen-skill-docs.test.ts strips fenced code blocks before matching `## ` headings and asserts the gate is the terminal heading in all four plan-* review SKILL.md files. Codex's SKILL.md uses toContain (mid-file by design — Step 2B/2C are not plan-touching modes). Decisions locked via /plan-eng-review + /codex outside-voice: - D1=A: 4 plan-* reviews + codex (autoplan, office-hours deferred) - D2=B → D4=A: tone preamble down to neutral forward reference - D3=A: add automated test in test/gen-skill-docs.test.ts - D5=B: keep codex gate inside Step 2A (mid-file acceptable per gate self-gating) Codex pre-merge findings folded in: line numbers obsolete (use EOF), test regex must strip fences, fresh skill list (not stale REVIEW_SKILLS constant), gate check 4 short-circuits when no plan file in context. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * chore: bump version and changelog (v1.39.1.0) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: package.json build script uses subshells, not brace groups The three `{ git rev-parse HEAD 2>/dev/null || true; } > path/.version` brace groups in the build script regressed when v1.38.0.0 merged into this branch (resolved with --ours during conflict). Bun on Windows can't parse brace groups in this position; the v1.38.0.0 invariant requires `(...)` subshells. Windows CI test `package.json build scripts — POSIX shell compat` caught it. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 25cf5ed commit f589770

57 files changed

Lines changed: 291 additions & 144 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,51 @@
11
# Changelog
22

3+
## [1.39.1.0] - 2026-05-15
4+
5+
## **Plan-mode reviews now enforce a blocking ExitPlanMode gate.**
6+
## **The review report can no longer go missing without breaking the contract.**
7+
8+
`/plan-eng-review`, `/plan-ceo-review`, `/plan-design-review`, `/plan-devex-review`, and `/codex review` now end with an EXIT PLAN MODE GATE (BLOCKING) section. Before calling ExitPlanMode, the model runs a four-item checklist: read the plan file, confirm the last `## ` heading is `## GSTACK REVIEW REPORT`, verify the report has a Runs/Status/Findings table + VERDICT line, and confirm `gstack-review-log` + `gstack-review-read` ran. Failing the checklist and exiting plan mode anyway is framed as a contract violation, not a soft permission to defer. The structural property ("review report is the file's terminal heading") is what makes the gate immune to "I wrote some review prose into the plan body" self-deception. A regression test in `test/gen-skill-docs.test.ts` strips fenced code blocks and asserts the gate is the terminal `## ` heading in all four plan-* review SKILL.md files.
9+
10+
### The numbers that matter
11+
12+
Source: `bun test test/gen-skill-docs.test.ts` — 389 cases, all green in ~1.5s. Manual verification via `awk` confirms the gate is the LAST `## ` heading in the regenerated SKILL.md for each plan-* review skill, and present mid-file in codex's Step 2A (where it's review-mode-scoped per design).
13+
14+
| Surface | Before | After |
15+
|---|---|---|
16+
| ExitPlanMode discipline in plan-* reviews | Soft `## Plan Status Footer` injected at TOP of skill via preamble: "if the plan file lacks `## GSTACK REVIEW REPORT`, run `gstack-review-read` and append... PLAN MODE EXCEPTION — always allowed." Permission grant, not a precondition. Sat ~3000 lines above ExitPlanMode in the skill prompt. | Terminal `## EXIT PLAN MODE GATE (BLOCKING)` injected at EOF of every plan-* review skill: 4-item self-check with explicit "contract violation" framing for the failure mode. Last thing the model reads before ExitPlanMode. |
17+
| Preamble footer in operational skills (`/ship`, `/qa`, `/review`, `/health`) | Same enforcement text as plan-mode skills — review-report rules bled into skills that have no review report | Neutral forward reference: "Plan-review skills include the EXIT PLAN MODE GATE at the end; this footer is a no-op for operational skills." No imposed rules where they can't apply. |
18+
| Regression protection | None — gate placement could silently regress on any future template edit | `bun test test/gen-skill-docs.test.ts` asserts gate is terminal `## ` heading in 4 plan-* skills (with fenced-code-block stripping) and present in codex via `toContain`. |
19+
20+
Cross-model review by Codex (`/codex` consult mode) caught six pre-merge factual issues the eng review missed: insertion line numbers were not terminal positions, the test regex would false-match `## ` lines inside fenced code blocks, the existing `REVIEW_SKILLS` constant in the test file was missing `plan-devex-review`, the preamble retoning bled review-report rules into operational skills, gate check 4 conflicted with `PLAN_FILE_REVIEW_REPORT`'s "skip silently if no plan file" escape clause, and the implementation sequence wasn't explicit enough to prevent bisect-broken commits. All six folded in before push.
21+
22+
### What this means for plan reviews
23+
24+
When the model finishes a plan-* review and is about to exit plan mode, it reads a blocking checklist that reframes ExitPlanMode as a precondition-bearing call, not a free termination. The plan ships with its review report attached as the file's terminal heading, every time. If the user has been bitten by "approved a plan only to discover the review report was never written" before, that failure mode is gone.
25+
26+
### Itemized changes
27+
28+
#### Added
29+
30+
- `generateExitPlanModeGate` resolver in `scripts/resolvers/review.ts:161` — emits the 4-item blocking checklist with "contract violation" framing. Single source of truth for the gate text.
31+
- `EXIT_PLAN_MODE_GATE` placeholder registered in `scripts/resolvers/index.ts:42`. Appended at EOF of `plan-eng-review/SKILL.md.tmpl`, `plan-ceo-review/SKILL.md.tmpl`, `plan-design-review/SKILL.md.tmpl`, `plan-devex-review/SKILL.md.tmpl`. Inserted into `codex/SKILL.md.tmpl` after `{{PLAN_FILE_REVIEW_REPORT}}` in Step 2A (mid-file by design — Step 2B/2C are not plan-touching modes).
32+
- `test/gen-skill-docs.test.ts:3097` — new `EXIT PLAN MODE GATE placement` describe block. Strips fenced code blocks before matching `## ` headings (a naive regex would false-match the `## GSTACK REVIEW REPORT` example inside `PLAN_FILE_REVIEW_REPORT`'s fenced markdown block). Uses a fresh skill list — not the upstream `REVIEW_SKILLS` constant which only has 3 entries and would silently miss plan-devex-review.
33+
34+
#### Changed
35+
36+
- `scripts/resolvers/preamble/generate-completion-status.ts:82` — `## Plan Status Footer` retoned from enforcement language ("if the plan file lacks `## GSTACK REVIEW REPORT`, run `gstack-review-read`... PLAN MODE EXCEPTION — always allowed") to neutral forward reference ("plan-review skills include the EXIT PLAN MODE GATE at the end; this footer is a no-op for operational skills"). Avoids review-report rules bleeding into `/ship`, `/qa`, `/review`, `/health`, etc.
37+
- `test/gen-skill-docs.test.ts:1093` — updated existing "Plan status footer in preamble" assertion to match the new neutral wording. Now also asserts the absence of "NO REVIEWS YET" to lock in the no-bleed property.
38+
- `test/fixtures/golden/{claude,codex,factory}-ship-SKILL.md` — golden baselines updated to capture the new preamble wording. The ship skill's body did not change; only the inherited preamble footer.
39+
40+
#### Fixed
41+
42+
- `package.json` build script — three `{ git rev-parse HEAD 2>/dev/null || true; }` brace groups (Bun-Windows-hostile) regressed during the v1.38.0.0 merge resolution; replaced with `( ... )` subshells to match the v1.38.0.0 invariant. Caught by Windows CI's `build-script-shell-compat` test on PR #1512.
43+
44+
#### For contributors
45+
46+
- The implementation sequence is load-bearing: resolver → index → templates → preamble → `bun run gen:skill-docs` → tests. Adding the test before regeneration fails on missing gate; regenerating before the resolver edits produces no-op output. Bisectable commits should respect this order.
47+
- The codex gate is intentionally NOT terminal in `codex/SKILL.md`. Codex has three modes (review/challenge/consult) and only review mode writes to plan files. The gate's check-2 ("last heading is GSTACK REVIEW REPORT") short-circuits cleanly when no plan file is in context, so non-plan codex invocations are unaffected.
48+
349
## [1.39.0.0] - 2026-05-14
450

551
## **`buildFetchHandler` ships. Embedders compose overlay routes on top of**
@@ -31,6 +77,7 @@ gbrowser v0.6.0.0 (phoenix overlay) can now ship. Phoenix imports `buildFetchHan
3177
### Itemized changes
3278

3379
#### Added
80+
3481
- `buildFetchHandler(cfg: ServerConfig): ServerHandle` in `browse/src/server.ts`.
3582
- `beforeRoute` hook wiring in the request handler, with a security warning JSDoc for overlay authors.
3683
- 14 factory contract tests in `browse/test/server-factory.test.ts` (covers ServerHandle shape, auth wiring, validation throws, hook semantics across both surfaces, and registry idempotency / mismatch-throw).

SKILL.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -473,9 +473,7 @@ Replace `SKILL_NAME`, `OUTCOME`, and `USED_BROWSE` before running.
473473

474474
## Plan Status Footer
475475

476-
In plan mode before ExitPlanMode: if the plan file lacks `## GSTACK REVIEW REPORT`, run `~/.claude/skills/gstack/bin/gstack-review-read` and append the standard runs/status/findings table. With `NO_REVIEWS` or empty, append a 5-row placeholder with verdict "NO REVIEWS YET — run `/autoplan`". If a richer report exists, skip.
477-
478-
PLAN MODE EXCEPTION — always allowed (it's the plan file).
476+
Skills that run plan reviews (`/plan-*-review`, `/codex review`) include the EXIT PLAN MODE GATE blocking checklist at the end of the skill, which verifies the plan file ends with `## GSTACK REVIEW REPORT` before ExitPlanMode is called. Skills that don't run plan reviews (operational skills like `/ship`, `/qa`, `/review`) typically don't operate in plan mode and have no review report to verify; this footer is a no-op for them. Writing the plan file is the one edit allowed in plan mode.
479477

480478
If `PROACTIVE` is `false`: do NOT proactively invoke or suggest other gstack skills during
481479
this session. Only run skills the user explicitly invokes. This preference persists across

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.39.0.0
1+
1.39.1.0

autoplan/SKILL.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -766,9 +766,7 @@ Replace `SKILL_NAME`, `OUTCOME`, and `USED_BROWSE` before running.
766766

767767
## Plan Status Footer
768768

769-
In plan mode before ExitPlanMode: if the plan file lacks `## GSTACK REVIEW REPORT`, run `~/.claude/skills/gstack/bin/gstack-review-read` and append the standard runs/status/findings table. With `NO_REVIEWS` or empty, append a 5-row placeholder with verdict "NO REVIEWS YET — run `/autoplan`". If a richer report exists, skip.
770-
771-
PLAN MODE EXCEPTION — always allowed (it's the plan file).
769+
Skills that run plan reviews (`/plan-*-review`, `/codex review`) include the EXIT PLAN MODE GATE blocking checklist at the end of the skill, which verifies the plan file ends with `## GSTACK REVIEW REPORT` before ExitPlanMode is called. Skills that don't run plan reviews (operational skills like `/ship`, `/qa`, `/review`) typically don't operate in plan mode and have no review report to verify; this footer is a no-op for them. Writing the plan file is the one edit allowed in plan mode.
772770

773771
## Step 0: Detect platform and base branch
774772

benchmark-models/SKILL.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -475,9 +475,7 @@ Replace `SKILL_NAME`, `OUTCOME`, and `USED_BROWSE` before running.
475475

476476
## Plan Status Footer
477477

478-
In plan mode before ExitPlanMode: if the plan file lacks `## GSTACK REVIEW REPORT`, run `~/.claude/skills/gstack/bin/gstack-review-read` and append the standard runs/status/findings table. With `NO_REVIEWS` or empty, append a 5-row placeholder with verdict "NO REVIEWS YET — run `/autoplan`". If a richer report exists, skip.
479-
480-
PLAN MODE EXCEPTION — always allowed (it's the plan file).
478+
Skills that run plan reviews (`/plan-*-review`, `/codex review`) include the EXIT PLAN MODE GATE blocking checklist at the end of the skill, which verifies the plan file ends with `## GSTACK REVIEW REPORT` before ExitPlanMode is called. Skills that don't run plan reviews (operational skills like `/ship`, `/qa`, `/review`) typically don't operate in plan mode and have no review report to verify; this footer is a no-op for them. Writing the plan file is the one edit allowed in plan mode.
481479

482480
# /benchmark-models — Cross-Model Skill Benchmark
483481

benchmark/SKILL.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -475,9 +475,7 @@ Replace `SKILL_NAME`, `OUTCOME`, and `USED_BROWSE` before running.
475475

476476
## Plan Status Footer
477477

478-
In plan mode before ExitPlanMode: if the plan file lacks `## GSTACK REVIEW REPORT`, run `~/.claude/skills/gstack/bin/gstack-review-read` and append the standard runs/status/findings table. With `NO_REVIEWS` or empty, append a 5-row placeholder with verdict "NO REVIEWS YET — run `/autoplan`". If a richer report exists, skip.
479-
480-
PLAN MODE EXCEPTION — always allowed (it's the plan file).
478+
Skills that run plan reviews (`/plan-*-review`, `/codex review`) include the EXIT PLAN MODE GATE blocking checklist at the end of the skill, which verifies the plan file ends with `## GSTACK REVIEW REPORT` before ExitPlanMode is called. Skills that don't run plan reviews (operational skills like `/ship`, `/qa`, `/review`) typically don't operate in plan mode and have no review report to verify; this footer is a no-op for them. Writing the plan file is the one edit allowed in plan mode.
481479

482480
## SETUP (run this check BEFORE any browse command)
483481

browse/SKILL.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -474,9 +474,7 @@ Replace `SKILL_NAME`, `OUTCOME`, and `USED_BROWSE` before running.
474474

475475
## Plan Status Footer
476476

477-
In plan mode before ExitPlanMode: if the plan file lacks `## GSTACK REVIEW REPORT`, run `~/.claude/skills/gstack/bin/gstack-review-read` and append the standard runs/status/findings table. With `NO_REVIEWS` or empty, append a 5-row placeholder with verdict "NO REVIEWS YET — run `/autoplan`". If a richer report exists, skip.
478-
479-
PLAN MODE EXCEPTION — always allowed (it's the plan file).
477+
Skills that run plan reviews (`/plan-*-review`, `/codex review`) include the EXIT PLAN MODE GATE blocking checklist at the end of the skill, which verifies the plan file ends with `## GSTACK REVIEW REPORT` before ExitPlanMode is called. Skills that don't run plan reviews (operational skills like `/ship`, `/qa`, `/review`) typically don't operate in plan mode and have no review report to verify; this footer is a no-op for them. Writing the plan file is the one edit allowed in plan mode.
480478

481479
# browse: QA Testing & Dogfooding
482480

canary/SKILL.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -740,9 +740,7 @@ Replace `SKILL_NAME`, `OUTCOME`, and `USED_BROWSE` before running.
740740

741741
## Plan Status Footer
742742

743-
In plan mode before ExitPlanMode: if the plan file lacks `## GSTACK REVIEW REPORT`, run `~/.claude/skills/gstack/bin/gstack-review-read` and append the standard runs/status/findings table. With `NO_REVIEWS` or empty, append a 5-row placeholder with verdict "NO REVIEWS YET — run `/autoplan`". If a richer report exists, skip.
744-
745-
PLAN MODE EXCEPTION — always allowed (it's the plan file).
743+
Skills that run plan reviews (`/plan-*-review`, `/codex review`) include the EXIT PLAN MODE GATE blocking checklist at the end of the skill, which verifies the plan file ends with `## GSTACK REVIEW REPORT` before ExitPlanMode is called. Skills that don't run plan reviews (operational skills like `/ship`, `/qa`, `/review`) typically don't operate in plan mode and have no review report to verify; this footer is a no-op for them. Writing the plan file is the one edit allowed in plan mode.
746744

747745
## SETUP (run this check BEFORE any browse command)
748746

codex/SKILL.md

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -760,9 +760,7 @@ Replace `SKILL_NAME`, `OUTCOME`, and `USED_BROWSE` before running.
760760

761761
## Plan Status Footer
762762

763-
In plan mode before ExitPlanMode: if the plan file lacks `## GSTACK REVIEW REPORT`, run `~/.claude/skills/gstack/bin/gstack-review-read` and append the standard runs/status/findings table. With `NO_REVIEWS` or empty, append a 5-row placeholder with verdict "NO REVIEWS YET — run `/autoplan`". If a richer report exists, skip.
764-
765-
PLAN MODE EXCEPTION — always allowed (it's the plan file).
763+
Skills that run plan reviews (`/plan-*-review`, `/codex review`) include the EXIT PLAN MODE GATE blocking checklist at the end of the skill, which verifies the plan file ends with `## GSTACK REVIEW REPORT` before ExitPlanMode is called. Skills that don't run plan reviews (operational skills like `/ship`, `/qa`, `/review`) typically don't operate in plan mode and have no review report to verify; this footer is a no-op for them. Writing the plan file is the one edit allowed in plan mode.
766764

767765
## Step 0: Detect platform and base branch
768766

@@ -1153,6 +1151,31 @@ prior versions to leave the report mid-file when an older report already lived
11531151
there — the user then sees a plan whose review report is not at the bottom and
11541152
(correctly) rejects it.
11551153

1154+
## EXIT PLAN MODE GATE (BLOCKING)
1155+
1156+
Before calling ExitPlanMode, run this self-check. If any item fails, do the
1157+
missing work — do NOT call ExitPlanMode:
1158+
1159+
1. Read the plan file with the Read tool (after your most recent write to it).
1160+
2. Confirm the LAST `## ` heading in the file is `## GSTACK REVIEW REPORT`.
1161+
In-body prose that mentions "outside voice", "codex findings", or similar
1162+
does NOT count — only the structured `## GSTACK REVIEW REPORT` section
1163+
satisfies this check.
1164+
3. Confirm the report contains: a Runs / Status / Findings table, a VERDICT
1165+
line, and absorbs CODEX / CROSS-MODEL / UNRESOLVED lines if applicable.
1166+
4. If a plan file is in context for this skill invocation: confirm
1167+
`gstack-review-log` was called and `gstack-review-read` was run at least
1168+
once. If no plan file is in context (e.g. `/codex consult` against a
1169+
diff with no plan), this check short-circuits — checks 1-3 already
1170+
short-circuit when no plan file exists.
1171+
1172+
Failing this gate and calling ExitPlanMode anyway is a contract violation —
1173+
the user will see a plan whose review report is missing or stale, and will
1174+
(correctly) reject it. Self-deception failure mode to watch for: feeling
1175+
"done" after writing review prose into the plan body. The body prose is not
1176+
the report. The report is a separate, structured, table-bearing section that
1177+
must be the file's terminal heading.
1178+
11561179
---
11571180

11581181
## Step 2B: Challenge (Adversarial) Mode

codex/SKILL.md.tmpl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,8 @@ rm -f "$TMPERR"
295295

296296
{{PLAN_FILE_REVIEW_REPORT}}
297297

298+
{{EXIT_PLAN_MODE_GATE}}
299+
298300
---
299301

300302
## Step 2B: Challenge (Adversarial) Mode

0 commit comments

Comments
 (0)