From 8b7a0b696bbc655604be3ebe1ba4edaa8464a7e9 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Mon, 7 Sep 2026 12:18:38 -0400 Subject: [PATCH] =?UTF-8?q?enhance(#4139):=20Phase=202=20=E2=80=94=20one?= =?UTF-8?q?=20shared=20gate,=20one=20pilot=20split,=20one=20accuracy=20spo?= =?UTF-8?q?t-check=20(#4471)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * enhance(#4402): split plan-phase into a spine + detail, add the shared compact-content gate ADR-4139 Decisions 3-5, Phase 2 of the #4139 Compact Content epic. Pilot split for plan-phase.md, the largest of the 58 eagerly-@-included workflow files (98,290 bytes): the spine keeps every happy-path step, every protected-content block (planner/checker prompt templates, quality gates, the failing-direction few-shot example, the two ScheduleWakeup guardrail paragraphs — each marked with a sentinel), and condensed one-paragraph summaries of five rare/opt-in fallback paths (planner and checker filesystem-hang recovery, phase-split recommendation, source-audit gaps, the thinking-partner conditional, and plan bounce). The full text of those five moves verbatim to gsd-core/workflows/plan-phase/detail.md (9.9KB, well under the 32,768-byte NEW_FILE_CAP), read by the spine only when workflow.compact_content is false (the default) — the exact same resolution rule now stated once in the new shared gsd-core/references/compact-content-gate.md, which every future split references instead of restating. Verified mechanically (tests/plan-phase-compact-split.test.cjs, scoped to this one split — Phase 3/#4403 owns the generalized guard): the union of spine + detail contains every non-trivial line the parent commit carried (0 missing), no non-trivial line is duplicated between them (0 duplicated), and every declared protected block is well-formed and non-empty. The spine shrinks from 98,290 to 93,206 bytes (-5.2% of the eager-window cost this epic exists to reduce); detail.md's 9,853 bytes are only ever paid by a project that has NOT opted in. Verified live, end to end, twice, against this actual repo (not a synthetic fixture) — real gsd-planner and gsd-plan-checker subagent spawns, real PLAN.md output: - workflow.compact_content=false: planned a real disposable phase (a docs/how-to page for enabling the key itself); planner returned PLANNING COMPLETE, checker returned VERIFICATION PASSED, all fact-checks against real repo state confirmed. - workflow.compact_content=true (detail.md never read): planned a second real disposable phase; planner returned PLANNING COMPLETE with frontmatter.validate and verify.plan-structure both clean, again fully grounded against real repo state. The five condensed fallback sections were independently re-read spine-only and confirmed sufficient to act on correctly without detail.md's elaboration. Also drafts gsd-core/references/compact-content-protected-content.md — the protected-content category list and sentinel syntax ADR-4139 Decision 5 calls for, written to move to Phase 3 (#4403) unchanged once it lands there. Co-Authored-By: Claude Sonnet 5 * fix(#4402): move detail.md into the ADR-4139-mandated detail/ subdirectory Two independent review sub-agents (Standards and Spec axes of /code-review) caught the same structural defect: ADR-4139 Decision 6 mandates gsd-core/workflows//detail/*.md ("one or more parts... individually skippable"), and this PR had shipped a flat plan-phase/detail.md instead, copying issue #4402's own (inconsistent) restatement rather than the locked ADR text. Fixed by git-mv to plan-phase/detail/elaboration.md and updating every cross-reference (the spine's step 0.5 gate pointer, the shared compact-content-gate.md's own resolution-rule wording, and the completeness test's path constants). Also, from the same review pass: - docs/CONFIGURATION.md and gsd-core/references/planning-config.md's workflow.compact_content rows said "nothing branches on it yet" — no longer true now that plan-phase.md's spine does. Updated both to name plan-phase as the pilot and note the rest of the corpus is still pending. - Regenerated all 19 tests/fixtures/install-tree/*.json golden fixtures (npm run gen:install-tree) — the three new shipped files were missing from the installer emitted-tree goldens. - Found via a cache-busted `eslint . --max-warnings 0` (this repo's eslint --cache has produced false-greens before): the split test's `git show` call had a bare `timeout: 10000` literal, tripping local/no-adhoc-timeout-literal. Extracted to the existing GIT_TIMEOUT_MS constant from tests/helpers/timeouts.cjs instead of a second guessed copy of the same class of timeout. Verified NOT needed, by tracing the actual mechanism rather than asserting (tests/helpers/emitted-provenance.cjs's gsd-core-verbatim rule attributes every gsd-core/{workflows,references}/** path to itself as an identity source): an Emitted-Drift-Ack-Hash/-Growth trailer. Every changed/added path in this diff is hand-authored and present in the diff itself, so diffEmitted's attribution loop resolves `via` to the path's own source before ever reaching the ack-lookup branch — there is no unattributed delta to acknowledge. The spine also shrank (98,290 to 93,206 bytes), so the growth ratchet has nothing to ack either. Re-verified after these changes: the completeness/disjointness self-check (0 missing, 0 duplicated) still holds against the relocated detail file, and a full `npm run lint:ci` passes clean with the eslint cache cleared. Co-Authored-By: Claude Sonnet 5 * fix(#4402): restore literal content the pre-existing drift guards pin on The first gsd-test run against this split (19 failures) surfaced real regressions: several pre-existing structural guards pin the EXACT text of the sections this split condensed, and paraphrasing broke them. - tests/plan-phase-drift-guard.test.cjs expects the literal `DISK_PLANS=$(gsd_run query find-phase ...)` bash assignment inside plan-phase.md itself, not a prose description of the same check. Restored the exact line into both §9a and §11a's spine summaries. - tests/thinking-partner.test.cjs expects plan-phase.md to literally offer "No, I'll decide" as the skip option. Restored that exact phrase into the condensed thinking-partner paragraph. - Both restores would have duplicated the same text into plan-phase/detail/elaboration.md (which still carries the full elaboration). Removed the now-redundant restatements from the detail file instead of leaving them duplicated — the spine already computes DISK_PLANS before the detail elaboration is ever read, so the detail file references it rather than recomputing it. - Re-running scripts/sync-runtime-launcher.cjs after that edit found the canonical gsd_run preamble had also become an unintentional spine/detail duplicate (both files call gsd_run and each is required, by runtime-launcher-parity's own contract, to carry its own copy). That's sanctioned duplication under a DIFFERENT contract, not lost/copy-pasted content, so tests/plan-phase-compact-split.test.cjs now excludes it from the disjointness check the same way it already excludes trivial fences/headings. - Applied the adversarial-review finding on tests/plan-phase-compact-split.test.cjs's own isTrivial(): a blanket `line.length <= 15` cutoff silently swallowed real content (e.g. the 14-char `` sentinel). Replaced it with a specific bare-label-line pattern (`Options:`, `Display banner:` etc.) — verified 0 missing / 0 duplicated against the actual split, an improvement over both the original cutoff and a naive full removal (which produces false-positive "duplicates" on generic recurring labels). - gsd-core/references/planning-config.md's own workflow.compact_content row used `/gsd-plan-phase` (hyphen). That file is Claude-facing source text (gsd-core/references/), which tests/slash-command-namespace.test.cjs requires in colon form; docs/CONFIGURATION.md's use of the hyphen form is correct as-is since docs/ is human-facing and outside that test's scanned directories. Fixed to `/gsd:plan-phase`. - tests/plan-phase-compact-split.test.cjs's own `git show` of the parent commit failed inside the gsd-test sandbox ("detected dubious ownership") because the checkout is mounted under a UID the invoking user doesn't own. Scoped `-c safe.directory=` to that one git invocation rather than touching global git config. - docs/INVENTORY.md still had one outstanding "detail.md part" wording fix from the earlier adversarial-review pass, staged now. Re-verified locally against the exact assertions in all four affected test files (all pass) before dispatching a fresh gsd-test run — no change here should have broken any of the other 18 gates; `npm run lint` is clean with the eslint cache cleared. Co-Authored-By: Claude Sonnet 5 * fix(#4402): restore the full marker enumeration to §9a's spine trigger line The isolated Spec-axis review flagged that §9a's "Triggered when" line was condensed to "Agent() returns but the return contains no recognized marker" — dropping the literal `## PLANNING COMPLETE` / `## PHASE SPLIT RECOMMENDED` / `## ⚠ Source Audit` / `## CHECKPOINT REACHED` / `## PLANNING INCONCLUSIVE` enumeration, which is exactly the "machine- parsed structural headings" category compact-content-protected-content.md lists as protected. The load-bearing use of that same list (the gsd_stall_watch call and the Handle Planner Return bullets a few lines above) was never touched — only this one descriptive restatement was genericized — but leaving any instance of a protected category unsentineled is the silent erosion ADR-4139 Decision 4(c) warns sufficiency isn't machine-checkable enough to catch on its own. Restored the full enumeration into the spine. That reintroduced an exact duplicate into plan-phase/detail/elaboration.md, which still stated the same trigger sentence verbatim. Reworded the detail file's version to reference the spine's trigger condition instead of restating it, since the spine is now the single place that sentence lives in full — mirroring the DISK_PLANS/"already computed above" pattern from the previous commit. Re-verified locally: completeness/disjointness (0 missing, 0 duplicated) and all previously-fixed literal-content assertions still hold. Co-Authored-By: Claude Sonnet 5 * docs(#4402): backfill changeset pr number to 4471 Co-Authored-By: Claude Sonnet 5 --------- Co-authored-by: sim Co-authored-by: Claude Sonnet 5 --- .changeset/curious-dogs-dart.md | 5 + docs/CONFIGURATION.md | 2 +- docs/INVENTORY-MANIFEST.json | 2 + docs/INVENTORY.md | 2 + gsd-core/references/compact-content-gate.md | 18 ++ .../compact-content-protected-content.md | 26 +++ gsd-core/references/planning-config.md | 2 +- gsd-core/workflows/plan-phase.md | 201 ++--------------- .../plan-phase/detail/elaboration.md | 209 ++++++++++++++++++ tests/fixtures/install-tree/antigravity.json | 3 + tests/fixtures/install-tree/augment.json | 3 + tests/fixtures/install-tree/claude-local.json | 3 + tests/fixtures/install-tree/claude.json | 3 + tests/fixtures/install-tree/cline.json | 3 + tests/fixtures/install-tree/codebuddy.json | 3 + tests/fixtures/install-tree/codex.json | 3 + tests/fixtures/install-tree/copilot.json | 3 + tests/fixtures/install-tree/cursor.json | 3 + tests/fixtures/install-tree/hermes.json | 3 + tests/fixtures/install-tree/kilo.json | 3 + tests/fixtures/install-tree/kimi-code.json | 3 + tests/fixtures/install-tree/kimi.json | 3 + tests/fixtures/install-tree/opencode.json | 3 + tests/fixtures/install-tree/pi.json | 3 + tests/fixtures/install-tree/qwen.json | 3 + tests/fixtures/install-tree/trae.json | 3 + tests/fixtures/install-tree/windsurf.json | 3 + tests/fixtures/install-tree/zcode.json | 3 + tests/plan-phase-compact-split.test.cjs | 172 ++++++++++++++ 29 files changed, 514 insertions(+), 182 deletions(-) create mode 100644 .changeset/curious-dogs-dart.md create mode 100644 gsd-core/references/compact-content-gate.md create mode 100644 gsd-core/references/compact-content-protected-content.md create mode 100644 gsd-core/workflows/plan-phase/detail/elaboration.md create mode 100644 tests/plan-phase-compact-split.test.cjs diff --git a/.changeset/curious-dogs-dart.md b/.changeset/curious-dogs-dart.md new file mode 100644 index 000000000..a63e7ca6d --- /dev/null +++ b/.changeset/curious-dogs-dart.md @@ -0,0 +1,5 @@ +--- +type: Added +pr: 4471 +--- +**`workflow.compact_content` now actually does something: `plan-phase` is the first workflow split into a spine + detail file.** With the key off (default), nothing changes — the spine reads the deferred elaboration back in before continuing, so the instruction set is identical to today. With it on, that read is skipped and the orchestrator runs on the terser spine alone, which is complete enough to plan a phase correctly on its own. The check and the resolution rule live in one shared reference (`gsd-core/references/compact-content-gate.md`) that future splits reference instead of restating. (#4402) diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 5cc2f6eb2..f6991909d 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -517,7 +517,7 @@ All workflow toggles follow the **absent = enabled** pattern. If a key is missin | `workflow.text_mode` | boolean | `false` | Replaces AskUserQuestion TUI menus with plain-text numbered lists. Required for Claude Code remote sessions (`/rc` mode) where TUI menus don't render. Can also be set per-session with `--text` flag on discuss-phase. Added in v1.28 | | `workflow.use_worktrees` | boolean | `true` | When `false`, disables git worktree isolation for parallel execution. Users who prefer sequential execution or whose environment does not support worktrees can disable this. Added in v1.31. **Branch-divergence note:** when your branch has diverged from `origin/HEAD`, GSD auto-degrades to sequential and prints a warning. See [`worktree.baseRef`](#worktree-settings) to restore parallel execution on a diverged branch. **Per-runtime note:** whether this key can be honored depends on the runtime's declared `dispatch.isolation` capability, not on its name (#2584). Runtimes whose own harness isolates each executor (**Claude Code**, **Cursor**) run parallel worktrees natively; runtimes exposing a headless exec with an explicit working directory (**Codex**, **OpenCode**, **Kimi**, **Kimi Code**) get worktrees GSD itself creates and merges — where a dispatch site can only drive the harness model, those hosts degrade to sequential with a warning rather than aborting. Every other runtime declares no isolation primitive, and forcing `use_worktrees: true` there still fails closed before any executor dispatch. `/gsd-health` reports such a value as warning `W025` (#2486). **Default on a non-Claude install:** if a worktree-capable non-Claude host is not isolating as described above, check whether the install stamped this key's default to `false` and set an explicit `use_worktrees: true`. See [Executor isolation per runtime](#executor-isolation-per-runtime). | | `workflow.agent_hint_routing` | boolean | `true` | Per-plan specialist executor routing (#1689). When `true`, a plan whose `agent_hint:` frontmatter names a subagent that resolves on the active runtime is dispatched to that specialist instead of `gsd-executor`. Default `true` — a no-op for plans without `agent_hint:`, so existing dispatch is unchanged. Set `false` to disable. See [PLAN.md `agent_hint`](reference/plan-md.md#per-plan-executor-routing). | -| `workflow.compact_content` | boolean | `false` | Compact content mode (#4139, [ADR-4139](adr/4139-compact-content-seam.md)). Per-project boolean selecting the terser form of GSD's own shipped prompt content (workflows, templates, agent-skill payloads). The key is registered and readable today; nothing branches on it yet — the load mechanism is a later sub-issue. | +| `workflow.compact_content` | boolean | `false` | Compact content mode (#4139, [ADR-4139](adr/4139-compact-content-seam.md)). Per-project boolean selecting the terser form of GSD's own shipped prompt content (workflows, templates, agent-skill payloads). `/gsd-plan-phase` is the pilot workflow that actually branches on it today (#4402) — with the key off, its spine reads a deferred elaboration file back in before continuing (byte-identical instruction set to before); with it on, that read is skipped. The rest of the corpus does not branch on it yet — full coverage is later sub-issues (#4405+). | | `workflow.worktree_skip_hooks` | boolean | `false` | When `true`, executor agents in worktree mode pass `--no-verify` (skipping pre-commit hooks) and post-wave hook validation runs against the merged result instead. Opt-in escape hatch for projects whose hooks cannot run in agent worktrees. Default `false` runs hooks on every commit (#2924). | | `workflow.code_review` | boolean | `true` | Enable `/gsd-code-review` and `/gsd-code-review --fix` commands. When `false`, the commands exit with a configuration gate message. Added in v1.34 | | `workflow.code_review_point` | string | `execute:post` | Loop point at which the code-review capability's step registers: `execute:post` reviews once, after every wave in a phase has landed (default — unchanged behavior); `execute:wave:post` reviews once per completed wave instead, scoped to what changed since the phase's prior review (the whole phase's diff on the first wave, each subsequent wave's own diff thereafter). Manual `/gsd-code-review ` invocation is unaffected by this key — it is gated by `workflow.code_review` alone and runs regardless of which point is configured. `/gsd-autonomous` and `/gsd-quick` have no wave granularity of their own, so setting this to `execute:wave:post` means code review does not run automatically inside those two flows (consistent with how every other `execute:wave:post`-only capability already behaves for them). Added in #3661 | diff --git a/docs/INVENTORY-MANIFEST.json b/docs/INVENTORY-MANIFEST.json index 7adf3fe53..f331486b7 100644 --- a/docs/INVENTORY-MANIFEST.json +++ b/docs/INVENTORY-MANIFEST.json @@ -213,6 +213,8 @@ "autonomous-ui-design-contract.md", "checkpoints.md", "common-bug-patterns.md", + "compact-content-gate.md", + "compact-content-protected-content.md", "context-budget.md", "continuation-format.md", "debugger-bug-taxonomy.md", diff --git a/docs/INVENTORY.md b/docs/INVENTORY.md index 1aa6e65ee..fb09dc1df 100644 --- a/docs/INVENTORY.md +++ b/docs/INVENTORY.md @@ -387,6 +387,8 @@ Full roster at `gsd-core/references/*.md`. References are shared knowledge docum | `execute-mvp-tdd.md` | Runtime gate semantics for execute-phase under TDD mode — pre-task failing-test verification, end-of-phase blocking review. | | `mvp-concepts.md` | Cross-reference index for the six MVP-related reference files; maps each file to its purpose and which workflow loads it. | | `verify-mvp-mode.md` | UAT framing rules for MVP-mode phases — user-flow-first ordering, deferred technical checks, user-story-format guard. | +| `compact-content-gate.md` | Shared compact-content gate (ADR-4139 Decision 3/4) — the `workflow.compact_content` check and detail-file resolution rule every compact-split workflow spine references, stated once. | +| `compact-content-protected-content.md` | Compact-content protected-content list (ADR-4139 Decision 5) — the five categories a workflow spine must never move to a `detail/*.md` part, and the `` sentinel syntax that marks them; drafted here for Phase 3 (#4403) to relocate unchanged. | ### Sketch References diff --git a/gsd-core/references/compact-content-gate.md b/gsd-core/references/compact-content-gate.md new file mode 100644 index 000000000..f582e3ab3 --- /dev/null +++ b/gsd-core/references/compact-content-gate.md @@ -0,0 +1,18 @@ +# Compact Content Gate + +Shared by every workflow spine split under ADR-4139. States the config check and the resolution rule once — a spine references this file; it never restates the check inline. + +## The check + +```bash +COMPACT_CONTENT=$(gsd_run query config-get workflow.compact_content --raw 2>/dev/null || echo "false") +``` + +## The resolution rule + +- **`COMPACT_CONTENT` is `"false"` (default):** Read every part under this workflow's own `detail/` directory (a sibling of this spine, e.g. `gsd-core/workflows//detail/*.md`) now, in full, before continuing past this point. Their content elaborates on the spine you are reading — treat everything they say as part of this document from here on. +- **`COMPACT_CONTENT` is `"true"`:** Do not read the detail file. Continue directly with the spine's own content — per ADR-4139 Decision 3, it is complete enough to run this workflow correctly on its own. + +## The fail-safe this exists to hold (ADR-4139 Decision 4) + +A `Read` that does not fire for any reason (tool error, a skipped step, a misread condition) leaves you running on the spine alone. That is the same, correct, terser state an opted-in project runs in on purpose — never a state with no instructions. The spine's own completeness is what makes this safe; this gate is only ever additive. diff --git a/gsd-core/references/compact-content-protected-content.md b/gsd-core/references/compact-content-protected-content.md new file mode 100644 index 000000000..e7bdefa0b --- /dev/null +++ b/gsd-core/references/compact-content-protected-content.md @@ -0,0 +1,26 @@ +# Compact Content — Protected Content List + +(ADR-4139 Decision 5.) Content in this list may never leave a workflow spine during a compact-content split — it stays directly in the eagerly-loaded spine file, never moved to a `detail/*.md` part, regardless of how much it would shrink the spine. + +## The categories + +1. **Negative instructions and guardrails** — any "do not X" / "never X" instruction that changes what the orchestrator must refuse to do (e.g. "Never call `ScheduleWakeup`... to literalize this wait"). +2. **Output-format contracts** — any block defining the literal shape of output another system consumes: a prompt template handed to a subagent, a JSON/XML schema, a `` or `` checklist. +3. **Few-shot examples the workflow's own steps depend on** — a worked example whose absence would leave a later instruction ambiguous (e.g. a ``/`` XML pair a planner prompt's own rule depends on). +4. **Security and prompt-injection language** — any text establishing a security boundary or defending against injected instructions. +5. **Machine-parsed structural headings** — a heading or marker another tool locates by exact text (a `## PLANNING COMPLETE`-style return marker, a `` directive, a ``/`` boundary). + +## Marking + +A sentinel comment declares protection at authoring time — the guard (Phase 3, #4403) checks for the sentinel's continued presence, never for category membership, because a guard cannot judge prose category on its own: + +```markdown + +… one protected block … + + +… a protected region spanning several blocks … + +``` + +The rule is mechanical: a sentinel present in the canonical file at the parent commit must be present in the spine afterward, and every line it covers must be in the spine. The categories above are authoring guidance for *where* to place a sentinel when splitting a file — they are never what an automated guard evaluates; only the sentinel's presence is. diff --git a/gsd-core/references/planning-config.md b/gsd-core/references/planning-config.md index 32dae8e22..fd8c694cc 100644 --- a/gsd-core/references/planning-config.md +++ b/gsd-core/references/planning-config.md @@ -289,7 +289,7 @@ Set via `workflow.*` namespace in config.json (e.g., `"workflow": { "research": | `workflow.ui_phase` | boolean | `true` | `true`, `false` | Generate UI-SPEC.md for frontend phases | | `workflow.ui_safety_gate` | boolean | `true` | `true`, `false` | Require safety gate approval for UI changes | | `workflow.text_mode` | boolean | `false` | `true`, `false` | Use plain-text numbered lists instead of AskUserQuestion menus | -| `workflow.compact_content` | boolean | `false` | `true`, `false` | Compact content mode (#4139, ADR-4139) — per-project boolean selecting terser payloads; nothing branches on it yet | +| `workflow.compact_content` | boolean | `false` | `true`, `false` | Compact content mode (#4139, ADR-4139) — per-project boolean selecting terser payloads. `/gsd:plan-phase` branches on it today as the pilot (#4402); the rest of the corpus does not yet | | `workflow.research_before_questions` | boolean | `false` | `true`, `false` | Run research before interactive questions in discuss phase (also honored on the `/gsd:quick` path, #3894). _Alias:_ `research_before_questions` is the flat-key form used in `CONFIG_DEFAULTS`; `workflow.research_before_questions` is the canonical namespaced form. | | `workflow.discuss_mode` | string | `"discuss"` | `"discuss"`, `"assumptions"` | Default mode for discuss-phase: `"discuss"` runs interactive questioning; `"assumptions"` analyzes codebase and surfaces assumptions instead | | `workflow.skip_discuss` | boolean | `false` | `true`, `false` | Skip discuss phase entirely | diff --git a/gsd-core/workflows/plan-phase.md b/gsd-core/workflows/plan-phase.md index 0b4db2a13..9ddccaf76 100644 --- a/gsd-core/workflows/plan-phase.md +++ b/gsd-core/workflows/plan-phase.md @@ -61,6 +61,10 @@ for the plan-checker gate to be meaningful. **Do not create, rename, or switch git branches during plan-phase.** Branch identity is established at discuss-phase and is owned by the user's git workflow. A phase rename in ROADMAP.md is a plan-level change only — it does not mutate git branch names. If `phase_slug` in the init JSON differs from the current branch name, that is expected and correct; leave the branch unchanged. +## 0.5. Compact Content Gate + +Read and follow `gsd-core/references/compact-content-gate.md` now — it states the `workflow.compact_content` check and the resolution rule this spine defers to. When it directs a Read, read `gsd-core/workflows/plan-phase/detail/elaboration.md` in full before continuing past this point; its content elaborates on several steps below. + ## 1. Initialize Load all context in one call (paths only to minimize orchestrator context): @@ -409,6 +413,7 @@ Agent( ) ``` + > **ORCHESTRATOR RULE — ALL RUNTIMES**: After calling Agent() above, stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available. Never call `ScheduleWakeup` or any host wake/sleep-scheduling tool to literalize this wait (#4079) — the Agent() call returns on its own; a partial-args wake call surfaces a red validation error. ### Handle Researcher Return @@ -675,6 +680,7 @@ Agent( ) ``` + > **ORCHESTRATOR RULE — ALL RUNTIMES**: After calling Agent() above, stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available. Never call `ScheduleWakeup` or any host wake/sleep-scheduling tool to literalize this wait (#4079) — the Agent() call returns on its own; a partial-args wake call surfaces a red validation error. **Handle return:** @@ -806,6 +812,7 @@ inherited paths: fix a mirror path, never inherit. Submodule files: check from within the submodule. + **Stated failing direction (#3172):** Every runnable `` verify command you write MUST be followed by a `` sibling naming what output @@ -830,6 +837,7 @@ doing nothing, what in its output would tell me? If you cannot answer, fix the command — do not invent a statement for it. Rules + worked examples: @gsd-core/references/planner-failing-direction.md + **Project instructions:** Read ./CLAUDE.md or ./.claude/CLAUDE.md if either exists — follow project-specific guidelines **Project skills:** Check .claude/skills/ or .agents/skills/ directory (if either exists) — read SKILL.md files, plans should account for project skill rules @@ -865,6 +873,7 @@ ${SPECLESS_FALLBACK_DISABLED ? ` + Output consumed by /gsd:execute-phase. Plans need: - Frontmatter (wave, depends_on, files_modified, autonomous) @@ -876,6 +885,7 @@ Output consumed by /gsd:execute-phase. Plans need: - If a `-UI-SPEC.md` exists (resolved above as `UI_SPEC_PATH`) with a `## UI Considerations` section, lift it by the **identical rule** as `## Edge Coverage` above — resolved (explicit) → `must_haves.truths` string, resolved (backstop) → flat scalar `{ statement, verification: backstop }`, `unresolved` → explicit planner assumption (no new verb — ADR-550 #1278/#1154; #1867). Read it from `UI_SPEC_PATH` (the SPEC glob excludes `-UI-SPEC.md`). - **"Artifacts this phase produces" section (MANDATORY)** — list every symbol this phase creates: decorators, classes, functions, CLI flags, struct/dataclass fields, new file paths. The plan-review-convergence source-grounding pass reads this section to exclude newly-created symbols from drift verification; omitting it causes new symbols to be flagged for acknowledgement. + ## Anti-Shallow Execution Rules (MANDATORY) @@ -908,6 +918,7 @@ Every task MUST include these fields — they are NOT optional: **Why this matters:** Executor agents work from the plan text. Vague instructions like "update the config to match production" produce shallow one-line changes. Concrete instructions like "add DATABASE_URL, set POOL_SIZE=20, add REDIS_URL, and read config/runtime.ts before editing" produce complete work without turning the planner into the executor. + - [ ] PLAN.md files created in phase directory - [ ] Each plan has valid frontmatter @@ -923,6 +934,7 @@ Every task MUST include these fields — they are NOT optional: - [ ] Every UI-SPEC ## UI Considerations resolved consideration is represented in a plan's must_haves (no silent drops) - [ ] Every SPEC ## Prohibitions resolved item is represented in a plan's must_haves.prohibitions (no silent drops) + ``` **If `CHUNKED_MODE` is `false` (default):** Spawn the planner as a single long-lived Agent: @@ -959,93 +971,18 @@ If `section_manifest` is `null` or `"chunked-planning-mode"` is in its `included **Triggered when:** Agent() returns but the return contains no recognized marker (`## PLANNING COMPLETE`, `## PHASE SPLIT RECOMMENDED`, `## ⚠ Source Audit`, `## CHECKPOINT REACHED`, `## PLANNING INCONCLUSIVE`). ```bash -# #3218: this asks "did the planner write files to disk at all" — a -# planner-produced-nothing check, not outstanding-work counting — so it -# takes the PHYSICAL set (`plan_count_all`, status:superseded INCLUDED): a -# superseded plan is still a file the planner wrote, and this check must not -# read "nothing written" just because every plan happens to be superseded. DISK_PLANS=$(gsd_run query find-phase "${PHASE_NUMBER}" | jq -r '.plan_count_all // 0') ``` -**If `DISK_PLANS` > 0:** The planner wrote plans to disk but the Agent() return was empty or -truncated (the Windows stdio hang pattern — the subagent finished but the return never -arrived). Display: - -```text -◆ Planner wrote {DISK_PLANS} plan(s) to disk but did not emit a PLANNING COMPLETE marker. - This is a known Windows stdio hang pattern — work is likely recoverable. - - Plans found on disk: - {ls output of *-PLAN.md} -``` - -Offer 3 options: -1. **Accept plans** — treat as `## PLANNING COMPLETE` and continue through step 9 `## PLANNING COMPLETE` handling (so `--skip-verify` / `plan_checker_enabled=false` are honored — may skip to step 13 rather than step 10) -2. **Retry planner** — re-spawn the planner with the same prompt (return to step 8) -3. **Stop** — exit; user can re-run `/gsd:plan-phase {N}` to resume - -**If `DISK_PLANS` is 0 and no marker:** The planner produced no output. Treat as -`## PLANNING INCONCLUSIVE` and handle accordingly. +If `DISK_PLANS` is greater than 0 (a known Windows stdio hang pattern — the planner wrote plans to disk but the return never arrived), offer: 1) Accept plans (treat as `## PLANNING COMPLETE`), 2) Retry planner (return to step 8), 3) Stop. If it is 0 and no marker, treat as `## PLANNING INCONCLUSIVE`. Full banner text: `gsd-core/workflows/plan-phase/detail/elaboration.md` § 9a. ## 9b. Handle Phase Split Recommendation -When the planner returns `## PHASE SPLIT RECOMMENDED`, it means the phase's source items exceed the context budget for full-fidelity implementation. The planner proposes groupings. - -**Extract from planner return:** -- Proposed sub-phases (e.g., "17a: processing core (D-01 to D-19)", "17b: billing + config UX (D-20 to D-27)") -- Which source items (REQ-IDs, D-XX decisions, RESEARCH items) go in each sub-phase -- Why the split is necessary (context cost estimate, file count) - -**Present to user:** -``` -## Phase {X} exceeds context budget for full-fidelity implementation - -The planner found {N} source items that exceed the context budget when -planned at full fidelity. Instead of reducing scope, we recommend splitting: - -**Option 1: Split into sub-phases** -- Phase {X}a: {name} — {items} ({N} source items, ~{P}% context) -- Phase {X}b: {name} — {items} ({M} source items, ~{Q}% context) - -**Option 2: Proceed anyway** (planner will attempt all, quality may degrade past 50% context) - -**Option 3: Prioritize** — you choose which items to implement now, -rest become a follow-up phase -``` - -Use AskUserQuestion with these 3 options. - -**If "Split":** Use `/gsd:phase --insert` to create the sub-phases, then replan each. -**If "Proceed":** Return to planner with instruction to attempt all items at full fidelity, accepting more plans/tasks. -**If "Prioritize":** Use AskUserQuestion (multiSelect) to let user pick which items are "now" vs "later". Create CONTEXT.md for each sub-phase with the selected items. +When the planner returns `## PHASE SPLIT RECOMMENDED`, the phase's source items exceed the context budget for full-fidelity implementation. Extract the planner's proposed sub-phase groupings and present the user three options via AskUserQuestion: Split into sub-phases (use `/gsd:phase --insert`, then replan each), Proceed anyway (return to planner accepting degraded quality), or Prioritize (AskUserQuestion multiSelect to choose now vs. later, create CONTEXT.md per sub-phase). Full banner text: `gsd-core/workflows/plan-phase/detail/elaboration.md` § 9b. ## 9c. Handle Source Audit Gaps -When the planner returns `## ⚠ Source Audit: Unplanned Items Found`, it means items from REQUIREMENTS.md, RESEARCH.md, ROADMAP goal, or CONTEXT.md decisions have no corresponding plan. - -**Extract from planner return:** -- Each unplanned item with its source artifact and section -- The planner's suggested options (A: add plan, B: split phase, C: defer with confirmation) - -**Present each gap to user.** For each unplanned item: - -``` -## ⚠ Unplanned: {item description} - -Source: {RESEARCH.md / REQUIREMENTS.md / ROADMAP goal / CONTEXT.md} -Details: {why the planner flagged this} - -Options: -1. Add a plan to cover this item (recommended) -2. Split phase — move to a sub-phase with related items -3. Defer — add to backlog (developer confirms this is intentional) -``` - -Use AskUserQuestion for each gap (or batch if multiple gaps). - -**If "Add plan":** Return to planner (step 8) with instruction to add plans covering the missing items, preserving existing plans. -**If "Split":** Use `/gsd:phase --insert` for overflow items, then replan. -**If "Defer":** Record in CONTEXT.md `## Deferred Ideas` with developer's confirmation. Proceed to step 10. +When the planner returns `## ⚠ Source Audit: Unplanned Items Found`, items from REQUIREMENTS.md, RESEARCH.md, ROADMAP goal, or CONTEXT.md decisions have no corresponding plan. Present each gap to the user with three options: Add a plan (return to planner, step 8), Split phase (`/gsd:phase --insert`, then replan), or Defer (record in CONTEXT.md `## Deferred Ideas` with developer confirmation, proceed to step 10). Full banner text: `gsd-core/workflows/plan-phase/detail/elaboration.md` § 9c. ## 10. Spawn gsd-plan-checker Agent @@ -1150,52 +1087,17 @@ Agent( - **`stalled`:** Automatically surface 11a's recovery choice (Accept verification / Retry checker / Stop) — no manual interrupt needed. - **Empty / truncated / no recognized marker:** → Filesystem fallback (step 11a). -**Thinking partner for architectural tradeoffs (conditional):** -If `features.thinking_partner` is enabled, scan the checker's issues for architectural tradeoff keywords -("architecture", "approach", "strategy", "pattern", "vs", "alternative"). If found: - -``` -The plan-checker flagged an architectural decision point: -{issue description} - -Brief analysis: -- Option A: {approach_from_plan} — {pros/cons} -- Option B: {alternative_approach} — {pros/cons} -- Recommendation: {choice} aligned with {phase_goal} - -Apply this to the revision? [Yes] / [No, I'll decide] -``` - -If yes: include the recommendation in the revision prompt. If no: proceed to revision loop as normal. -If thinking_partner disabled: skip this block entirely. +**Thinking partner for architectural tradeoffs (conditional):** If `features.thinking_partner` is enabled and the checker's issues contain architectural tradeoff keywords ("architecture", "approach", "strategy", "pattern", "vs", "alternative"), present a brief Option A/B analysis with a recommendation and ask "Apply this to the revision? [Yes] / [No, I'll decide]". If disabled, skip. Full prompt template: `gsd-core/workflows/plan-phase/detail/elaboration.md` § 11 thinking-partner. ## 11a. Filesystem Fallback (Checker) **Triggered when:** Checker Agent() returns but the return contains neither `## VERIFICATION PASSED` nor `## ISSUES FOUND`. ```bash -# #3218: this asks "did the planner write files to disk at all" — a -# planner-produced-nothing check, not outstanding-work counting — so it -# takes the PHYSICAL set (`plan_count_all`, status:superseded INCLUDED): a -# superseded plan is still a file the planner wrote, and this check must not -# read "nothing written" just because every plan happens to be superseded. DISK_PLANS=$(gsd_run query find-phase "${PHASE_NUMBER}" | jq -r '.plan_count_all // 0') ``` -**If `DISK_PLANS` > 0:** Plans exist on disk; the checker return was empty or truncated (the -Windows stdio hang pattern — the subagent finished but the return never arrived). Display: - -```text -◆ Checker return was empty or truncated. {DISK_PLANS} plan(s) exist on disk. - This is a known Windows stdio hang pattern — checker may have completed without returning. -``` - -Offer 3 options: -1. **Accept verification** — treat as `## VERIFICATION PASSED` and continue to step 13 -2. **Retry checker** — re-spawn the checker with the same prompt (return to step 10) -3. **Stop** — exit; user can re-run `/gsd:plan-phase {N}` to resume - -**If `DISK_PLANS` is 0:** No plans on disk — something is seriously wrong. Display error and stop. +If `DISK_PLANS` is greater than 0 (plans exist on disk; a known Windows stdio hang pattern), offer: 1) Accept verification (treat as `## VERIFICATION PASSED`, continue to step 13), 2) Retry checker (return to step 10), 3) Stop. If it is 0, something is seriously wrong — display error and stop. Full banner text: `gsd-core/workflows/plan-phase/detail/elaboration.md` § 11a. ## 12. Revision Loop (Max 3 Iterations) @@ -1353,72 +1255,9 @@ Offer: 1) Force proceed, 2) Provide guidance and retry, 3) Abandon ## 12.5. Plan Bounce (Optional External Refinement) -**Skip if:** `--skip-bounce` flag, `--gaps` flag, or bounce is not activated. +**Skip if:** `--skip-bounce`, `--gaps`, or bounce not activated (`--bounce` flag or `workflow.plan_bounce` config; `--skip-bounce` always wins). Requires `workflow.plan_bounce_script` set to a valid script path — warn and skip if bounce is activated with no script configured. -**Activation:** Bounce runs when `--bounce` flag is present OR `workflow.plan_bounce` config is `true`. The `--skip-bounce` flag always wins (disables bounce even if config enables it). The `--gaps` flag also disables bounce (gap-closure mode should not modify plans externally). - -**Prerequisites:** `workflow.plan_bounce_script` must be set to a valid script path. If bounce is activated but no script is configured, display warning and skip: -``` -⚠ Plan bounce activated but no script configured. -Set workflow.plan_bounce_script to the path of your refinement script. -Skipping bounce step. -``` - -**Read pass count:** -```bash -BOUNCE_PASSES=$(gsd_run query config-get workflow.plan_bounce_passes --raw 2>/dev/null || echo "2") -BOUNCE_SCRIPT=$(gsd_run query config-get workflow.plan_bounce_script --raw 2>/dev/null || true) -``` - -Display banner: -``` -### GSD ► BOUNCING PLANS (External Refinement) - -Script: ${BOUNCE_SCRIPT} -Max passes: ${BOUNCE_PASSES} -``` - -**For each PLAN.md file in the phase directory:** - -1. **Backup:** Copy `*-PLAN.md` to `*-PLAN.pre-bounce.md` -```bash -cp "${PLAN_FILE}" "${PLAN_FILE%.md}.pre-bounce.md" -``` - -2. **Invoke bounce script:** -```bash -"${BOUNCE_SCRIPT}" "${PLAN_FILE}" "${BOUNCE_PASSES}" -``` - -3. **Validate bounced plan — YAML frontmatter integrity:** -After the script returns, check that the bounced file still has valid YAML frontmatter (opening and closing `---` delimiters with parseable content between them). If the bounced plan breaks YAML frontmatter validation, restore the original from the pre-bounce.md backup and continue to the next plan: -``` -⚠ Bounced plan ${PLAN_FILE} has broken YAML frontmatter — restoring original from pre-bounce backup. -``` - -4. **Handle script failure:** If the bounce script exits non-zero, restore the original plan from the pre-bounce.md backup and continue to the next plan: -``` -⚠ Bounce script failed for ${PLAN_FILE} (exit code ${EXIT_CODE}) — restoring original from pre-bounce backup. -``` - -**After all plans are bounced:** - -5. **Re-run plan checker on bounced plans:** Spawn gsd-plan-checker (same as step 10) on all modified plans. If a bounced plan fails the checker, restore original from its pre-bounce.md backup: -``` -⚠ Bounced plan ${PLAN_FILE} failed checker validation — restoring original from pre-bounce backup. -``` - -6. **Commit surviving bounced plans:** If at least one plan survived both the frontmatter validation and the checker re-run, commit the changes: -```bash -gsd_run query commit "refactor(${padded_phase}): bounce plans through external refinement" --files "${PHASE_DIR}/*-PLAN.md" -``` - -Display summary: -``` -Plan bounce complete: {survived}/{total} plans refined -``` - -**Clean up:** Remove all `*-PLAN.pre-bounce.md` backup files after the bounce step completes (whether plans survived or were restored). +For each `*-PLAN.md`: back it up to `*-PLAN.pre-bounce.md`, invoke `${BOUNCE_SCRIPT}` with the plan file and `workflow.plan_bounce_passes` (default 2), validate the result's YAML frontmatter integrity, and restore from backup on either broken frontmatter or a non-zero script exit. After all plans are bounced, re-run the plan checker (step 10) on the modified plans, restoring any that fail. Commit surviving bounced plans if at least one survived (`refactor(${padded_phase}): bounce plans through external refinement`), display a `{survived}/{total}` summary, and remove all `*-PLAN.pre-bounce.md` backups. Exact banner text, messages, and commands: `gsd-core/workflows/plan-phase/detail/elaboration.md` § 12.5. ## 13. Requirements Coverage Gate @@ -1722,6 +1561,7 @@ Verification: {Passed | Passed with override | Skipped} Read `gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md` if plan-phase freezes on Windows during agent spawning (stdio deadlocks with MCP servers, anthropics/claude-code#28126) — it covers force-kill, orphaned-node cleanup, stale task-dir cleanup, reducing the MCP server count, and the `--skip-research` fallback. + - [ ] .planning/ directory validated - [ ] Phase validated against roadmap @@ -1737,3 +1577,4 @@ Read `gsd-core/workflows/plan-phase/steps/windows-troubleshooting.md` if plan-ph - [ ] User sees status between agent spawns - [ ] User knows next steps + diff --git a/gsd-core/workflows/plan-phase/detail/elaboration.md b/gsd-core/workflows/plan-phase/detail/elaboration.md new file mode 100644 index 000000000..4c8b03afb --- /dev/null +++ b/gsd-core/workflows/plan-phase/detail/elaboration.md @@ -0,0 +1,209 @@ +Apply response_language to all user-facing prose — narration between tool calls, status updates, progress notes, and findings included; preserve code, paths, and identifiers. + +# plan-phase — Detail + +Elaboration deferred from the `plan-phase.md` spine under ADR-4139 (Compact Content mode). Read via `gsd-core/references/compact-content-gate.md` when `workflow.compact_content` is `false`. This file supplements the spine — it does not stand alone. + +## § 9a — Filesystem Fallback (Planner) + +This elaborates the spine's §9a trigger condition (above) — the recovery banner and its three options. + +```bash +# #3218: this asks "did the planner write files to disk at all" — a +# planner-produced-nothing check, not outstanding-work counting — so it +# takes the PHYSICAL set (`plan_count_all`, status:superseded INCLUDED): a +# superseded plan is still a file the planner wrote, and this check must not +# read "nothing written" just because every plan happens to be superseded. +``` + +The spine already computed `DISK_PLANS` (above) before reaching this elaboration. + +**If `DISK_PLANS` > 0:** The planner wrote plans to disk but the Agent() return was empty or +truncated (the Windows stdio hang pattern — the subagent finished but the return never +arrived). Display: + +```text +◆ Planner wrote {DISK_PLANS} plan(s) to disk but did not emit a PLANNING COMPLETE marker. + This is a known Windows stdio hang pattern — work is likely recoverable. + + Plans found on disk: + {ls output of *-PLAN.md} +``` + +Offer 3 options: +1. **Accept plans** — treat as `## PLANNING COMPLETE` and continue through step 9 `## PLANNING COMPLETE` handling (so `--skip-verify` / `plan_checker_enabled=false` are honored — may skip to step 13 rather than step 10) +2. **Retry planner** — re-spawn the planner with the same prompt (return to step 8) +3. **Stop** — exit; user can re-run `/gsd:plan-phase {N}` to resume + +**If `DISK_PLANS` is 0 and no marker:** The planner produced no output. Treat as +`## PLANNING INCONCLUSIVE` and handle accordingly. + +## § 9b — Handle Phase Split Recommendation + +When the planner returns `## PHASE SPLIT RECOMMENDED`, it means the phase's source items exceed the context budget for full-fidelity implementation. The planner proposes groupings. + +**Extract from planner return:** +- Proposed sub-phases (e.g., "17a: processing core (D-01 to D-19)", "17b: billing + config UX (D-20 to D-27)") +- Which source items (REQ-IDs, D-XX decisions, RESEARCH items) go in each sub-phase +- Why the split is necessary (context cost estimate, file count) + +**Present to user:** +``` +## Phase {X} exceeds context budget for full-fidelity implementation + +The planner found {N} source items that exceed the context budget when +planned at full fidelity. Instead of reducing scope, we recommend splitting: + +**Option 1: Split into sub-phases** +- Phase {X}a: {name} — {items} ({N} source items, ~{P}% context) +- Phase {X}b: {name} — {items} ({M} source items, ~{Q}% context) + +**Option 2: Proceed anyway** (planner will attempt all, quality may degrade past 50% context) + +**Option 3: Prioritize** — you choose which items to implement now, +rest become a follow-up phase +``` + +Use AskUserQuestion with these 3 options. + +**If "Split":** Use `/gsd:phase --insert` to create the sub-phases, then replan each. +**If "Proceed":** Return to planner with instruction to attempt all items at full fidelity, accepting more plans/tasks. +**If "Prioritize":** Use AskUserQuestion (multiSelect) to let user pick which items are "now" vs "later". Create CONTEXT.md for each sub-phase with the selected items. + +## § 9c — Handle Source Audit Gaps + +When the planner returns `## ⚠ Source Audit: Unplanned Items Found`, it means items from REQUIREMENTS.md, RESEARCH.md, ROADMAP goal, or CONTEXT.md decisions have no corresponding plan. + +**Extract from planner return:** +- Each unplanned item with its source artifact and section +- The planner's suggested options (A: add plan, B: split phase, C: defer with confirmation) + +**Present each gap to user.** For each unplanned item: + +``` +## ⚠ Unplanned: {item description} + +Source: {RESEARCH.md / REQUIREMENTS.md / ROADMAP goal / CONTEXT.md} +Details: {why the planner flagged this} + +Options: +1. Add a plan to cover this item (recommended) +2. Split phase — move to a sub-phase with related items +3. Defer — add to backlog (developer confirms this is intentional) +``` + +Use AskUserQuestion for each gap (or batch if multiple gaps). + +**If "Add plan":** Return to planner (step 8) with instruction to add plans covering the missing items, preserving existing plans. +**If "Split":** Use `/gsd:phase --insert` for overflow items, then replan. +**If "Defer":** Record in CONTEXT.md `## Deferred Ideas` with developer's confirmation. Proceed to step 10. + +## § 11a — Filesystem Fallback (Checker) + +Fires when the checker's Agent() call comes back without either completion marker (`## VERIFICATION PASSED` / `## ISSUES FOUND`). The spine already computed `DISK_PLANS` before reaching this elaboration. + +**If `DISK_PLANS` > 0:** Plans exist on disk; the checker return was empty or truncated (the +Windows stdio hang pattern — the subagent finished but the return never arrived). Display: + +```text +◆ Checker return was empty or truncated. {DISK_PLANS} plan(s) exist on disk. + This is a known Windows stdio hang pattern — checker may have completed without returning. +``` + +Offer 3 options: +1. **Accept verification** — treat as `## VERIFICATION PASSED` and continue to step 13 +2. **Retry checker** — re-spawn the checker with the same prompt (return to step 10) +3. **Stop** — exit; user can re-run `/gsd:plan-phase {N}` to resume + +**If `DISK_PLANS` is 0:** No plans on disk — something is seriously wrong. Display error and stop. + +## § 11 thinking-partner — Thinking Partner For Architectural Tradeoffs + +**Thinking partner for architectural tradeoffs (conditional):** +If `features.thinking_partner` is enabled, scan the checker's issues for architectural tradeoff keywords +("architecture", "approach", "strategy", "pattern", "vs", "alternative"). If found: + +``` +The plan-checker flagged an architectural decision point: +{issue description} + +Brief analysis: +- Option A: {approach_from_plan} — {pros/cons} +- Option B: {alternative_approach} — {pros/cons} +- Recommendation: {choice} aligned with {phase_goal} + +Apply this to the revision? [Yes] / [No, I'll decide] +``` + +If yes: include the recommendation in the revision prompt. If no: proceed to revision loop as normal. +If thinking_partner disabled: skip this block entirely. + +## § 12.5 — Plan Bounce (Optional External Refinement) + +**Skip if:** `--skip-bounce` flag, `--gaps` flag, or bounce is not activated. + +**Activation:** Bounce runs when `--bounce` flag is present OR `workflow.plan_bounce` config is `true`. The `--skip-bounce` flag always wins (disables bounce even if config enables it). The `--gaps` flag also disables bounce (gap-closure mode should not modify plans externally). + +**Prerequisites:** `workflow.plan_bounce_script` must be set to a valid script path. If bounce is activated but no script is configured, display warning and skip: +``` +⚠ Plan bounce activated but no script configured. +Set workflow.plan_bounce_script to the path of your refinement script. +Skipping bounce step. +``` + +**Read pass count:** +```bash +_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; _gsd_at() { for _p; do if [ -f "$_p" ]; then GSD_TOOLS="$_p"; return 0; fi; done; return 1; }; if _gsd_at "${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif unset -f gsd_run; _G="$(command -v gsd_run)"; then GSD_TOOLS="$_G"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif _gsd_at "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; then gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd_run is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; GSD_IDENTITY_STATUS=unverified; case "$(gsd_run runtime-identity --raw 2>/dev/null || true)" in '{"packageName":"@opengsd/gsd-core"'*'}') GSD_IDENTITY_STATUS=ok;; esac; export GSD_IDENTITY_STATUS; [ "$GSD_IDENTITY_STATUS" = ok ] || echo "WARNING: \"$GSD_TOOLS\" did not prove it is @opengsd/gsd-core - it is either a different package or an @opengsd/gsd-core older than the runtime-identity verb. See docs/how-to/diagnose-a-foreign-gsd-tools.md" >&2; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi +BOUNCE_PASSES=$(gsd_run query config-get workflow.plan_bounce_passes --raw 2>/dev/null || echo "2") +BOUNCE_SCRIPT=$(gsd_run query config-get workflow.plan_bounce_script --raw 2>/dev/null || true) +``` + +Display banner: +``` +### GSD ► BOUNCING PLANS (External Refinement) + +Script: ${BOUNCE_SCRIPT} +Max passes: ${BOUNCE_PASSES} +``` + +**For each PLAN.md file in the phase directory:** + +1. **Backup:** Copy `*-PLAN.md` to `*-PLAN.pre-bounce.md` +```bash +cp "${PLAN_FILE}" "${PLAN_FILE%.md}.pre-bounce.md" +``` + +2. **Invoke bounce script:** +```bash +"${BOUNCE_SCRIPT}" "${PLAN_FILE}" "${BOUNCE_PASSES}" +``` + +3. **Validate bounced plan — YAML frontmatter integrity:** +After the script returns, check that the bounced file still has valid YAML frontmatter (opening and closing `---` delimiters with parseable content between them). If the bounced plan breaks YAML frontmatter validation, restore the original from the pre-bounce.md backup and continue to the next plan: +``` +⚠ Bounced plan ${PLAN_FILE} has broken YAML frontmatter — restoring original from pre-bounce backup. +``` + +4. **Handle script failure:** If the bounce script exits non-zero, restore the original plan from the pre-bounce.md backup and continue to the next plan: +``` +⚠ Bounce script failed for ${PLAN_FILE} (exit code ${EXIT_CODE}) — restoring original from pre-bounce backup. +``` + +**After all plans are bounced:** + +5. **Re-run plan checker on bounced plans:** Spawn gsd-plan-checker (same as step 10) on all modified plans. If a bounced plan fails the checker, restore original from its pre-bounce.md backup: +``` +⚠ Bounced plan ${PLAN_FILE} failed checker validation — restoring original from pre-bounce backup. +``` + +6. **Commit surviving bounced plans:** If at least one plan survived both the frontmatter validation and the checker re-run, commit the changes: +```bash +gsd_run query commit "refactor(${padded_phase}): bounce plans through external refinement" --files "${PHASE_DIR}/*-PLAN.md" +``` + +Display summary: +``` +Plan bounce complete: {survived}/{total} plans refined +``` + +**Clean up:** Remove all `*-PLAN.pre-bounce.md` backup files after the bounce step completes (whether plans survived or were restored). diff --git a/tests/fixtures/install-tree/antigravity.json b/tests/fixtures/install-tree/antigravity.json index b0f94cefa..412122bdd 100644 --- a/tests/fixtures/install-tree/antigravity.json +++ b/tests/fixtures/install-tree/antigravity.json @@ -168,6 +168,8 @@ "gsd-core/references/autonomous-ui-design-contract.md", "gsd-core/references/checkpoints.md", "gsd-core/references/common-bug-patterns.md", + "gsd-core/references/compact-content-gate.md", + "gsd-core/references/compact-content-protected-content.md", "gsd-core/references/context-budget.md", "gsd-core/references/continuation-format.md", "gsd-core/references/debugger-bug-taxonomy.md", @@ -435,6 +437,7 @@ "gsd-core/workflows/onboard.md", "gsd-core/workflows/pause-work.md", "gsd-core/workflows/plan-phase.md", + "gsd-core/workflows/plan-phase/detail/elaboration.md", "gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md", "gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md", diff --git a/tests/fixtures/install-tree/augment.json b/tests/fixtures/install-tree/augment.json index 00f8f292e..ec30b21ce 100644 --- a/tests/fixtures/install-tree/augment.json +++ b/tests/fixtures/install-tree/augment.json @@ -240,6 +240,8 @@ "gsd-core/references/autonomous-ui-design-contract.md", "gsd-core/references/checkpoints.md", "gsd-core/references/common-bug-patterns.md", + "gsd-core/references/compact-content-gate.md", + "gsd-core/references/compact-content-protected-content.md", "gsd-core/references/context-budget.md", "gsd-core/references/continuation-format.md", "gsd-core/references/debugger-bug-taxonomy.md", @@ -507,6 +509,7 @@ "gsd-core/workflows/onboard.md", "gsd-core/workflows/pause-work.md", "gsd-core/workflows/plan-phase.md", + "gsd-core/workflows/plan-phase/detail/elaboration.md", "gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md", "gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md", diff --git a/tests/fixtures/install-tree/claude-local.json b/tests/fixtures/install-tree/claude-local.json index dbdefbd0f..c1c261b33 100644 --- a/tests/fixtures/install-tree/claude-local.json +++ b/tests/fixtures/install-tree/claude-local.json @@ -133,6 +133,8 @@ "gsd-core/references/autonomous-ui-design-contract.md", "gsd-core/references/checkpoints.md", "gsd-core/references/common-bug-patterns.md", + "gsd-core/references/compact-content-gate.md", + "gsd-core/references/compact-content-protected-content.md", "gsd-core/references/context-budget.md", "gsd-core/references/continuation-format.md", "gsd-core/references/debugger-bug-taxonomy.md", @@ -400,6 +402,7 @@ "gsd-core/workflows/onboard.md", "gsd-core/workflows/pause-work.md", "gsd-core/workflows/plan-phase.md", + "gsd-core/workflows/plan-phase/detail/elaboration.md", "gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md", "gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md", diff --git a/tests/fixtures/install-tree/claude.json b/tests/fixtures/install-tree/claude.json index cad6eb778..db1babe3f 100644 --- a/tests/fixtures/install-tree/claude.json +++ b/tests/fixtures/install-tree/claude.json @@ -168,6 +168,8 @@ "gsd-core/references/autonomous-ui-design-contract.md", "gsd-core/references/checkpoints.md", "gsd-core/references/common-bug-patterns.md", + "gsd-core/references/compact-content-gate.md", + "gsd-core/references/compact-content-protected-content.md", "gsd-core/references/context-budget.md", "gsd-core/references/continuation-format.md", "gsd-core/references/debugger-bug-taxonomy.md", @@ -435,6 +437,7 @@ "gsd-core/workflows/onboard.md", "gsd-core/workflows/pause-work.md", "gsd-core/workflows/plan-phase.md", + "gsd-core/workflows/plan-phase/detail/elaboration.md", "gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md", "gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md", diff --git a/tests/fixtures/install-tree/cline.json b/tests/fixtures/install-tree/cline.json index edce069ac..854ba7124 100644 --- a/tests/fixtures/install-tree/cline.json +++ b/tests/fixtures/install-tree/cline.json @@ -170,6 +170,8 @@ "gsd-core/references/autonomous-ui-design-contract.md", "gsd-core/references/checkpoints.md", "gsd-core/references/common-bug-patterns.md", + "gsd-core/references/compact-content-gate.md", + "gsd-core/references/compact-content-protected-content.md", "gsd-core/references/context-budget.md", "gsd-core/references/continuation-format.md", "gsd-core/references/debugger-bug-taxonomy.md", @@ -437,6 +439,7 @@ "gsd-core/workflows/onboard.md", "gsd-core/workflows/pause-work.md", "gsd-core/workflows/plan-phase.md", + "gsd-core/workflows/plan-phase/detail/elaboration.md", "gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md", "gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md", diff --git a/tests/fixtures/install-tree/codebuddy.json b/tests/fixtures/install-tree/codebuddy.json index 14f1133bc..b53440eab 100644 --- a/tests/fixtures/install-tree/codebuddy.json +++ b/tests/fixtures/install-tree/codebuddy.json @@ -240,6 +240,8 @@ "gsd-core/references/autonomous-ui-design-contract.md", "gsd-core/references/checkpoints.md", "gsd-core/references/common-bug-patterns.md", + "gsd-core/references/compact-content-gate.md", + "gsd-core/references/compact-content-protected-content.md", "gsd-core/references/context-budget.md", "gsd-core/references/continuation-format.md", "gsd-core/references/debugger-bug-taxonomy.md", @@ -507,6 +509,7 @@ "gsd-core/workflows/onboard.md", "gsd-core/workflows/pause-work.md", "gsd-core/workflows/plan-phase.md", + "gsd-core/workflows/plan-phase/detail/elaboration.md", "gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md", "gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md", diff --git a/tests/fixtures/install-tree/codex.json b/tests/fixtures/install-tree/codex.json index cdb5f8445..67478dbf3 100644 --- a/tests/fixtures/install-tree/codex.json +++ b/tests/fixtures/install-tree/codex.json @@ -204,6 +204,8 @@ "gsd-core/references/autonomous-ui-design-contract.md", "gsd-core/references/checkpoints.md", "gsd-core/references/common-bug-patterns.md", + "gsd-core/references/compact-content-gate.md", + "gsd-core/references/compact-content-protected-content.md", "gsd-core/references/context-budget.md", "gsd-core/references/continuation-format.md", "gsd-core/references/debugger-bug-taxonomy.md", @@ -471,6 +473,7 @@ "gsd-core/workflows/onboard.md", "gsd-core/workflows/pause-work.md", "gsd-core/workflows/plan-phase.md", + "gsd-core/workflows/plan-phase/detail/elaboration.md", "gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md", "gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md", diff --git a/tests/fixtures/install-tree/copilot.json b/tests/fixtures/install-tree/copilot.json index 8108a27ea..844063746 100644 --- a/tests/fixtures/install-tree/copilot.json +++ b/tests/fixtures/install-tree/copilot.json @@ -169,6 +169,8 @@ "gsd-core/references/autonomous-ui-design-contract.md", "gsd-core/references/checkpoints.md", "gsd-core/references/common-bug-patterns.md", + "gsd-core/references/compact-content-gate.md", + "gsd-core/references/compact-content-protected-content.md", "gsd-core/references/context-budget.md", "gsd-core/references/continuation-format.md", "gsd-core/references/debugger-bug-taxonomy.md", @@ -436,6 +438,7 @@ "gsd-core/workflows/onboard.md", "gsd-core/workflows/pause-work.md", "gsd-core/workflows/plan-phase.md", + "gsd-core/workflows/plan-phase/detail/elaboration.md", "gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md", "gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md", diff --git a/tests/fixtures/install-tree/cursor.json b/tests/fixtures/install-tree/cursor.json index 868a8cf07..d94dfa9d3 100644 --- a/tests/fixtures/install-tree/cursor.json +++ b/tests/fixtures/install-tree/cursor.json @@ -168,6 +168,8 @@ "gsd-core/references/autonomous-ui-design-contract.md", "gsd-core/references/checkpoints.md", "gsd-core/references/common-bug-patterns.md", + "gsd-core/references/compact-content-gate.md", + "gsd-core/references/compact-content-protected-content.md", "gsd-core/references/context-budget.md", "gsd-core/references/continuation-format.md", "gsd-core/references/debugger-bug-taxonomy.md", @@ -435,6 +437,7 @@ "gsd-core/workflows/onboard.md", "gsd-core/workflows/pause-work.md", "gsd-core/workflows/plan-phase.md", + "gsd-core/workflows/plan-phase/detail/elaboration.md", "gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md", "gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md", diff --git a/tests/fixtures/install-tree/hermes.json b/tests/fixtures/install-tree/hermes.json index dbcf40faf..b9a7c327f 100644 --- a/tests/fixtures/install-tree/hermes.json +++ b/tests/fixtures/install-tree/hermes.json @@ -168,6 +168,8 @@ "gsd-core/references/autonomous-ui-design-contract.md", "gsd-core/references/checkpoints.md", "gsd-core/references/common-bug-patterns.md", + "gsd-core/references/compact-content-gate.md", + "gsd-core/references/compact-content-protected-content.md", "gsd-core/references/context-budget.md", "gsd-core/references/continuation-format.md", "gsd-core/references/debugger-bug-taxonomy.md", @@ -435,6 +437,7 @@ "gsd-core/workflows/onboard.md", "gsd-core/workflows/pause-work.md", "gsd-core/workflows/plan-phase.md", + "gsd-core/workflows/plan-phase/detail/elaboration.md", "gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md", "gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md", diff --git a/tests/fixtures/install-tree/kilo.json b/tests/fixtures/install-tree/kilo.json index 7f9faf98e..b18cda0be 100644 --- a/tests/fixtures/install-tree/kilo.json +++ b/tests/fixtures/install-tree/kilo.json @@ -240,6 +240,8 @@ "gsd-core/references/autonomous-ui-design-contract.md", "gsd-core/references/checkpoints.md", "gsd-core/references/common-bug-patterns.md", + "gsd-core/references/compact-content-gate.md", + "gsd-core/references/compact-content-protected-content.md", "gsd-core/references/context-budget.md", "gsd-core/references/continuation-format.md", "gsd-core/references/debugger-bug-taxonomy.md", @@ -507,6 +509,7 @@ "gsd-core/workflows/onboard.md", "gsd-core/workflows/pause-work.md", "gsd-core/workflows/plan-phase.md", + "gsd-core/workflows/plan-phase/detail/elaboration.md", "gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md", "gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md", diff --git a/tests/fixtures/install-tree/kimi-code.json b/tests/fixtures/install-tree/kimi-code.json index 46870aaea..4148e94b7 100644 --- a/tests/fixtures/install-tree/kimi-code.json +++ b/tests/fixtures/install-tree/kimi-code.json @@ -169,6 +169,8 @@ "gsd-core/references/autonomous-ui-design-contract.md", "gsd-core/references/checkpoints.md", "gsd-core/references/common-bug-patterns.md", + "gsd-core/references/compact-content-gate.md", + "gsd-core/references/compact-content-protected-content.md", "gsd-core/references/context-budget.md", "gsd-core/references/continuation-format.md", "gsd-core/references/debugger-bug-taxonomy.md", @@ -436,6 +438,7 @@ "gsd-core/workflows/onboard.md", "gsd-core/workflows/pause-work.md", "gsd-core/workflows/plan-phase.md", + "gsd-core/workflows/plan-phase/detail/elaboration.md", "gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md", "gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md", diff --git a/tests/fixtures/install-tree/kimi.json b/tests/fixtures/install-tree/kimi.json index f551fbeea..1c14051a9 100644 --- a/tests/fixtures/install-tree/kimi.json +++ b/tests/fixtures/install-tree/kimi.json @@ -205,6 +205,8 @@ "gsd-core/references/autonomous-ui-design-contract.md", "gsd-core/references/checkpoints.md", "gsd-core/references/common-bug-patterns.md", + "gsd-core/references/compact-content-gate.md", + "gsd-core/references/compact-content-protected-content.md", "gsd-core/references/context-budget.md", "gsd-core/references/continuation-format.md", "gsd-core/references/debugger-bug-taxonomy.md", @@ -472,6 +474,7 @@ "gsd-core/workflows/onboard.md", "gsd-core/workflows/pause-work.md", "gsd-core/workflows/plan-phase.md", + "gsd-core/workflows/plan-phase/detail/elaboration.md", "gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md", "gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md", diff --git a/tests/fixtures/install-tree/opencode.json b/tests/fixtures/install-tree/opencode.json index 6faa1ca62..77225f799 100644 --- a/tests/fixtures/install-tree/opencode.json +++ b/tests/fixtures/install-tree/opencode.json @@ -240,6 +240,8 @@ "gsd-core/references/autonomous-ui-design-contract.md", "gsd-core/references/checkpoints.md", "gsd-core/references/common-bug-patterns.md", + "gsd-core/references/compact-content-gate.md", + "gsd-core/references/compact-content-protected-content.md", "gsd-core/references/context-budget.md", "gsd-core/references/continuation-format.md", "gsd-core/references/debugger-bug-taxonomy.md", @@ -507,6 +509,7 @@ "gsd-core/workflows/onboard.md", "gsd-core/workflows/pause-work.md", "gsd-core/workflows/plan-phase.md", + "gsd-core/workflows/plan-phase/detail/elaboration.md", "gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md", "gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md", diff --git a/tests/fixtures/install-tree/pi.json b/tests/fixtures/install-tree/pi.json index 8b8b68466..9034e8167 100644 --- a/tests/fixtures/install-tree/pi.json +++ b/tests/fixtures/install-tree/pi.json @@ -28,6 +28,8 @@ "gsd-core/references/autonomous-ui-design-contract.md", "gsd-core/references/checkpoints.md", "gsd-core/references/common-bug-patterns.md", + "gsd-core/references/compact-content-gate.md", + "gsd-core/references/compact-content-protected-content.md", "gsd-core/references/context-budget.md", "gsd-core/references/continuation-format.md", "gsd-core/references/debugger-bug-taxonomy.md", @@ -295,6 +297,7 @@ "gsd-core/workflows/onboard.md", "gsd-core/workflows/pause-work.md", "gsd-core/workflows/plan-phase.md", + "gsd-core/workflows/plan-phase/detail/elaboration.md", "gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md", "gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md", diff --git a/tests/fixtures/install-tree/qwen.json b/tests/fixtures/install-tree/qwen.json index edc7463cc..e071e177e 100644 --- a/tests/fixtures/install-tree/qwen.json +++ b/tests/fixtures/install-tree/qwen.json @@ -168,6 +168,8 @@ "gsd-core/references/autonomous-ui-design-contract.md", "gsd-core/references/checkpoints.md", "gsd-core/references/common-bug-patterns.md", + "gsd-core/references/compact-content-gate.md", + "gsd-core/references/compact-content-protected-content.md", "gsd-core/references/context-budget.md", "gsd-core/references/continuation-format.md", "gsd-core/references/debugger-bug-taxonomy.md", @@ -435,6 +437,7 @@ "gsd-core/workflows/onboard.md", "gsd-core/workflows/pause-work.md", "gsd-core/workflows/plan-phase.md", + "gsd-core/workflows/plan-phase/detail/elaboration.md", "gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md", "gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md", diff --git a/tests/fixtures/install-tree/trae.json b/tests/fixtures/install-tree/trae.json index 8050cd141..1171c16cf 100644 --- a/tests/fixtures/install-tree/trae.json +++ b/tests/fixtures/install-tree/trae.json @@ -168,6 +168,8 @@ "gsd-core/references/autonomous-ui-design-contract.md", "gsd-core/references/checkpoints.md", "gsd-core/references/common-bug-patterns.md", + "gsd-core/references/compact-content-gate.md", + "gsd-core/references/compact-content-protected-content.md", "gsd-core/references/context-budget.md", "gsd-core/references/continuation-format.md", "gsd-core/references/debugger-bug-taxonomy.md", @@ -435,6 +437,7 @@ "gsd-core/workflows/onboard.md", "gsd-core/workflows/pause-work.md", "gsd-core/workflows/plan-phase.md", + "gsd-core/workflows/plan-phase/detail/elaboration.md", "gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md", "gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md", diff --git a/tests/fixtures/install-tree/windsurf.json b/tests/fixtures/install-tree/windsurf.json index 38cebc17d..19da7c414 100644 --- a/tests/fixtures/install-tree/windsurf.json +++ b/tests/fixtures/install-tree/windsurf.json @@ -96,6 +96,8 @@ "gsd-core/references/autonomous-ui-design-contract.md", "gsd-core/references/checkpoints.md", "gsd-core/references/common-bug-patterns.md", + "gsd-core/references/compact-content-gate.md", + "gsd-core/references/compact-content-protected-content.md", "gsd-core/references/context-budget.md", "gsd-core/references/continuation-format.md", "gsd-core/references/debugger-bug-taxonomy.md", @@ -363,6 +365,7 @@ "gsd-core/workflows/onboard.md", "gsd-core/workflows/pause-work.md", "gsd-core/workflows/plan-phase.md", + "gsd-core/workflows/plan-phase/detail/elaboration.md", "gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md", "gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md", diff --git a/tests/fixtures/install-tree/zcode.json b/tests/fixtures/install-tree/zcode.json index 510831b62..a42364f38 100644 --- a/tests/fixtures/install-tree/zcode.json +++ b/tests/fixtures/install-tree/zcode.json @@ -240,6 +240,8 @@ "gsd-core/references/autonomous-ui-design-contract.md", "gsd-core/references/checkpoints.md", "gsd-core/references/common-bug-patterns.md", + "gsd-core/references/compact-content-gate.md", + "gsd-core/references/compact-content-protected-content.md", "gsd-core/references/context-budget.md", "gsd-core/references/continuation-format.md", "gsd-core/references/debugger-bug-taxonomy.md", @@ -507,6 +509,7 @@ "gsd-core/workflows/onboard.md", "gsd-core/workflows/pause-work.md", "gsd-core/workflows/plan-phase.md", + "gsd-core/workflows/plan-phase/detail/elaboration.md", "gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md", "gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md", "gsd-core/workflows/plan-phase/steps/closed-phase-gate.md", diff --git a/tests/plan-phase-compact-split.test.cjs b/tests/plan-phase-compact-split.test.cjs new file mode 100644 index 000000000..7b2e9df08 --- /dev/null +++ b/tests/plan-phase-compact-split.test.cjs @@ -0,0 +1,172 @@ +'use strict'; + +/** + * Issue #4402 (ADR-4139 Decision 5): verifies the plan-phase.md spine / + * plan-phase/detail/ split is complete (nothing lost), disjoint (nothing + * duplicated), size-capped, and preserves the protected-content sentinels + * this pilot draws from gsd-core/references/compact-content-protected-content.md. + * + * Scoped to this one split — Phase 3 (#4403) owns the generalized guard that + * runs this class of check against every future split. + */ + +const { describe, test } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('fs'); +const path = require('path'); +const { execFileSync } = require('node:child_process'); +const { GIT_TIMEOUT_MS } = require('./helpers/timeouts.cjs'); + +const ROOT = path.join(__dirname, '..'); +// The commit immediately before this branch split plan-phase.md (PR #4441's merge to next). +const PARENT_SHA = 'e54d3aa159810b2308cd777047b9de9c04de418a'; +const SPINE_REL = 'gsd-core/workflows/plan-phase.md'; +const DETAIL_REL = 'gsd-core/workflows/plan-phase/detail/elaboration.md'; + +/** Trivial lines (fences, rules, bare headings, bare labels) are excluded from + * both the completeness and disjointness checks — they are boilerplate that + * legitimately repeats throughout any markdown file with code blocks, not + * content that could be silently lost or duplicated in a meaningful sense. + * A blanket short-line length cutoff would swallow real content (e.g. the + * 14-char `` sentinel), so short lines are trivial only when + * they match a specific boilerplate shape rather than by length alone. */ +function isTrivial(line) { + if (/^`{3,}/.test(line)) return true; + if (/^-{3,}$/.test(line)) return true; + if (/^#+\s*$/.test(line)) return true; + if (/^[A-Za-z][A-Za-z ]*:$/.test(line)) return true; + return false; +} + +/** The canonical gsd_run launcher preamble (see gsd-core/workflows/_runtime-launcher.snippet.sh, + * tests/runtime-launcher-parity.test.cjs). runtime-launcher-parity mandates exactly one inlined + * copy in EVERY workflow/agent .md file that calls gsd_run — spine and detail both call gsd_run, + * so both are required to carry their own copy. That is sanctioned duplication by a different + * contract, not content this split lost or copy-pasted; exclude it from the disjointness check + * the same way trivial fences/headings are excluded. */ +function isCanonicalLauncherPreamble(line) { + return line.startsWith('_GSD_SHIM_NAME="gsd-tools.cjs";'); +} + +function normalizeNonTrivialLines(content) { + return content + .split(/\r?\n/) + .map((l) => l.trim()) + .filter((l) => l.length > 0 && !isTrivial(l)); +} + +function readParentSpine() { + // -c safe.directory= (scoped to this one invocation, not global config): + // a sandboxed test runner can mount the repo under a UID that doesn't own the + // checkout, and git refuses every operation with "detected dubious ownership" + // until the directory is trusted. Passing it per-call avoids mutating shared + // git config for a check this test alone needs. + return execFileSync('git', ['-c', `safe.directory=${ROOT}`, 'show', `${PARENT_SHA}:${SPINE_REL}`], { + cwd: ROOT, + encoding: 'utf-8', + timeout: GIT_TIMEOUT_MS, + }); +} + +function readCurrent(relPath) { + return fs.readFileSync(path.join(ROOT, relPath), 'utf-8'); +} + +describe('plan-phase compact-content spine/detail split (#4402, ADR-4139 Decision 5)', () => { + test('union of spine + detail contains every non-trivial line the parent commit carried', () => { + const parentLines = normalizeNonTrivialLines(readParentSpine()); + const spineLines = normalizeNonTrivialLines(readCurrent(SPINE_REL)); + const detailLines = normalizeNonTrivialLines(readCurrent(DETAIL_REL)); + const unionSet = new Set([...spineLines, ...detailLines]); + + const missing = parentLines.filter((l) => !unionSet.has(l)); + assert.deepStrictEqual( + missing, + [], + `${missing.length} line(s) from the parent commit are missing from spine+detail:\n${missing.slice(0, 15).join('\n')}${missing.length > 15 ? `\n(+${missing.length - 15} more)` : ''}`, + ); + }); + + test('no non-trivial line appears in both spine and detail', () => { + const spineLines = normalizeNonTrivialLines(readCurrent(SPINE_REL)); + const detailLines = normalizeNonTrivialLines(readCurrent(DETAIL_REL)); + const spineSet = new Set(spineLines); + const duplicated = detailLines + .filter((l) => spineSet.has(l)) + .filter((l) => !isCanonicalLauncherPreamble(l)); + assert.deepStrictEqual( + duplicated, + [], + `${duplicated.length} line(s) appear in both spine and detail:\n${duplicated.slice(0, 15).join('\n')}`, + ); + }); + + test('detail.md is a new shipped file under the NEW_FILE_CAP (32768 bytes, tests/helpers/emitted-diff.cjs)', () => { + const size = fs.statSync(path.join(ROOT, DETAIL_REL)).size; + assert.ok(size < 32768, `plan-phase/detail/elaboration.md is ${size} bytes; NEW_FILE_CAP is 32768`); + }); + + test('the spine is smaller than the parent commit\'s file (eager-window byte reduction is real)', () => { + const parentSize = Buffer.byteLength(readParentSpine(), 'utf-8'); + const spineSize = fs.statSync(path.join(ROOT, SPINE_REL)).size; + assert.ok( + spineSize < parentSize, + `spine (${spineSize}B) is not smaller than the parent commit's plan-phase.md (${parentSize}B)`, + ); + }); + + test('the spine references the shared compact-content gate exactly once', () => { + const spine = readCurrent(SPINE_REL); + const matches = spine.match(/compact-content-gate\.md/g) || []; + assert.strictEqual(matches.length, 1, `expected exactly one reference to compact-content-gate.md, found ${matches.length}`); + }); + + test('every gsd:protected sentinel in the spine is well-formed (start/end paired, or a single-line marker followed by content)', () => { + const spine = readCurrent(SPINE_REL); + const lines = spine.split(/\r?\n/); + let openStart = -1; + const singleMarkers = []; + const pairedBlocks = []; + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trim(); + if (line === '') { + assert.strictEqual(openStart, -1, `nested/unclosed gsd:protected:start at line ${i + 1}`); + openStart = i; + } else if (line === '') { + assert.notStrictEqual(openStart, -1, `gsd:protected:end with no matching start at line ${i + 1}`); + pairedBlocks.push({ start: openStart, end: i }); + openStart = -1; + } else if (line === '') { + singleMarkers.push(i); + } + } + assert.strictEqual(openStart, -1, 'a gsd:protected:start sentinel was never closed'); + assert.ok(pairedBlocks.length >= 4, `expected at least 4 paired protected blocks, found ${pairedBlocks.length}`); + assert.ok(singleMarkers.length >= 2, `expected at least 2 single-line protected markers, found ${singleMarkers.length}`); + + // Each paired block must actually enclose non-trivial content (not an empty/decorative wrap). + for (const block of pairedBlocks) { + const enclosed = lines.slice(block.start + 1, block.end).join('\n').trim(); + assert.ok(enclosed.length > 0, `protected block at lines ${block.start + 1}-${block.end + 1} encloses no content`); + } + // Each single marker must be immediately followed by non-trivial content on the next non-empty line. + for (const idx of singleMarkers) { + let j = idx + 1; + while (j < lines.length && lines[j].trim() === '') j++; + assert.ok(j < lines.length && lines[j].trim().length > 0, `single protected marker at line ${idx + 1} has no following content`); + } + }); + + test('the four protected-content categories named in gsd-core/references/compact-content-protected-content.md are represented among the spine\'s protected blocks', () => { + const spine = readCurrent(SPINE_REL); + // Output-format contracts: + assert.match(spine, /\s*/, 'quality_gate output-format contract must be protected'); + assert.match(spine, /\s*/, 'success_criteria output-format contract must be protected'); + assert.match(spine, /\s*/, 'downstream_consumer output-format contract must be protected'); + // Few-shot example the workflow's own steps depend on: + assert.match(spine, /\s*/, 'failing_direction_contract few-shot example must be protected'); + // Negative instruction / guardrail: + const guardrailCount = (spine.match(/\n> \*\*ORCHESTRATOR RULE[^]*?Never call `ScheduleWakeup`/g) || []).length; + assert.strictEqual(guardrailCount, 2, `expected 2 protected ScheduleWakeup guardrail paragraphs, found ${guardrailCount}`); + }); +});