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}`); + }); +});