diff --git a/.changeset/curious-koalas-travel.md b/.changeset/curious-koalas-travel.md new file mode 100644 index 000000000..ba2bd3099 --- /dev/null +++ b/.changeset/curious-koalas-travel.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 3596 +--- +**Shipped workflow/agent citations resolve again** — 43 backticked `references/.md` cites across 19 shipped files were dead pointers from every install location; all repaired to the canonical `gsd-core/references/.md` form, and a new sweep gate fails the build on any future bare cite across the runtime-loaded trees. (#3576) diff --git a/agents/gsd-doc-synthesizer.md b/agents/gsd-doc-synthesizer.md index 4d5863455..3e4aa3380 100644 --- a/agents/gsd-doc-synthesizer.md +++ b/agents/gsd-doc-synthesizer.md @@ -17,7 +17,7 @@ You are a GSD doc synthesizer. You consume per-doc classification JSON files and You do NOT prompt the user. You do NOT write PROJECT.md, REQUIREMENTS.md, or ROADMAP.md — those are produced downstream by `gsd-roadmapper` using your output. Your job is synthesis + conflict surfacing. **CRITICAL: Mandatory Initial Read** -If the prompt contains a `` block, load every file listed there first — especially `references/doc-conflict-engine.md` which defines your conflict report format. +If the prompt contains a `` block, load every file listed there first — especially `gsd-core/references/doc-conflict-engine.md` which defines your conflict report format. @~/.claude/gsd-core/references/untrusted-input-boundary.md @@ -173,7 +173,7 @@ Absent fields → mark absent (empty / omit), never fabricate. LOCKED-vs-LOCKED -Write `CONFLICTS_PATH` using the format from `references/doc-conflict-engine.md`. Three buckets, plain text, no tables. +Write `CONFLICTS_PATH` using the format from `gsd-core/references/doc-conflict-engine.md`. Three buckets, plain text, no tables. Structure: diff --git a/agents/gsd-executor.md b/agents/gsd-executor.md index 5e3afef06..b8c047cad 100644 --- a/agents/gsd-executor.md +++ b/agents/gsd-executor.md @@ -426,7 +426,7 @@ If RED or GREEN gate commits are missing, add a warning to SUMMARY.md under a `# **Halt-and-report protocol:** 1. Stop. Do not run the task's implementation step. -2. Emit the structured halt report defined in `references/execute-mvp-tdd.md` (header line, reason code, expected behavior, required next step). +2. Emit the structured halt report defined in `gsd-core/references/execute-mvp-tdd.md` (header line, reason code, expected behavior, required next step). 3. Update `STATE.md` with `last_gate_trip: {plan_id}/{task_id}`. 4. Exit the current execution wave cleanly. Prior commits in the same wave stay — do not roll back. @@ -436,7 +436,7 @@ If RED or GREEN gate commits are missing, add a warning to SUMMARY.md under a `# IS_BEHAVIOR_ADDING=$(gsd_run query task.is-behavior-adding "$TASK_FILE" --pick is_behavior_adding) ``` -The verb owns the canonical predicate (tdd="true" frontmatter AND `` block AND non-test source files in ``). Pure doc-only / config-only / test-only tasks return `false` and are exempt. Full result also exposes per-check breakdown (`checks.tdd_true`, `checks.has_behavior_block`, `checks.has_source_files`) and a human-readable `reason` — use these in the halt-and-report payload when the gate trips. See `references/execute-mvp-tdd.md` for halt protocol. +The verb owns the canonical predicate (tdd="true" frontmatter AND `` block AND non-test source files in ``). Pure doc-only / config-only / test-only tasks return `false` and are exempt. Full result also exposes per-check breakdown (`checks.tdd_true`, `checks.has_behavior_block`, `checks.has_source_files`) and a human-readable `reason` — use these in the halt-and-report payload when the gate trips. See `gsd-core/references/execute-mvp-tdd.md` for halt protocol. **Mode is all-or-nothing per phase** (PRD decision Q1, inherited from Phase 1). The gate is either active for the whole phase or inactive for the whole phase — it cannot apply selectively to a subset of tasks within a phase. diff --git a/gsd-core/references/context-budget.md b/gsd-core/references/context-budget.md index 32434bf2e..194134d55 100644 --- a/gsd-core/references/context-budget.md +++ b/gsd-core/references/context-budget.md @@ -2,7 +2,7 @@ Standard rules for keeping orchestrator context lean. Reference this in workflows that spawn subagents or read significant content. -See also: `references/universal-anti-patterns.md` for the complete set of universal rules. +See also: `gsd-core/references/universal-anti-patterns.md` for the complete set of universal rules. --- diff --git a/gsd-core/references/doc-conflict-engine.md b/gsd-core/references/doc-conflict-engine.md index 5e8a338b6..770165b83 100644 --- a/gsd-core/references/doc-conflict-engine.md +++ b/gsd-core/references/doc-conflict-engine.md @@ -56,7 +56,7 @@ Exit WITHOUT writing any destination files. The gate must hold regardless of WAR **If only WARNINGS and/or INFO (no blockers):** -Render the full report, then prompt for approval via the `approve-revise-abort` or `yes-no` pattern from `references/gate-prompts.md`. Respect text mode (see the workflow's own text-mode handling). If the user aborts, exit cleanly with a cancellation message. +Render the full report, then prompt for approval via the `approve-revise-abort` or `yes-no` pattern from `gsd-core/references/gate-prompts.md`. Respect text mode (see the workflow's own text-mode handling). If the user aborts, exit cleanly with a cancellation message. **If the report is empty (no entries in any bucket):** diff --git a/gsd-core/references/execute-mvp-tdd.md b/gsd-core/references/execute-mvp-tdd.md index 6acaacc3c..bba22c9c3 100644 --- a/gsd-core/references/execute-mvp-tdd.md +++ b/gsd-core/references/execute-mvp-tdd.md @@ -4,7 +4,7 @@ ## When this gate fires -- `MVP_MODE` is `true` (resolved from CLI flag → ROADMAP `**Mode:**` field → config; see `references/planner-mvp-mode.md`). +- `MVP_MODE` is `true` (resolved from CLI flag → ROADMAP `**Mode:**` field → config; see `gsd-core/references/planner-mvp-mode.md`). - `TDD_MODE` is `true` (resolved from `--tdd` flag → `workflow.tdd_mode` config). - The current task being executed has `tdd="true"` in its `` frontmatter (set by the planner per Phase 1). - The task's `` block lists at least one expected behavior. @@ -71,11 +71,11 @@ The `--force-mvp-gate` flag is documented but not introduced by this plan — it ## What this gate does NOT do -- It does not enforce REFACTOR commits. REFACTOR remains optional (per `references/tdd.md`). +- It does not enforce REFACTOR commits. REFACTOR remains optional (per `gsd-core/references/tdd.md`). - It does not check test quality (the test could be trivially passing). That's the planner's job. - It does not run tests. The executor only inspects git log + file system. Running tests is the implementation step's job. - It does not gate config-only or doc-only tasks (see "behavior-adding task" definition). ## Compatibility with existing TDD discipline -This gate is additive to `references/tdd.md`. Tasks not under MVP+TDD continue to use the existing advisory TDD discipline (RED/GREEN/REFACTOR commits with end-of-phase review checkpoint). Only the runtime gate and the blocking escalation are new. +This gate is additive to `gsd-core/references/tdd.md`. Tasks not under MVP+TDD continue to use the existing advisory TDD discipline (RED/GREEN/REFACTOR commits with end-of-phase review checkpoint). Only the runtime gate and the blocking escalation are new. diff --git a/gsd-core/references/execute-phase-context-guard.md b/gsd-core/references/execute-phase-context-guard.md index 97b86021b..4bc7c6f86 100644 --- a/gsd-core/references/execute-phase-context-guard.md +++ b/gsd-core/references/execute-phase-context-guard.md @@ -1,7 +1,7 @@ 0. **Context exhaustion guard — `context_guard` (BEFORE spawning, #1452):** Before spawning any agents for this wave, self-assess context pressure using the - degradation signals in `references/context-budget.md`. Signs of POOR tier (70%+): + degradation signals in `gsd-core/references/context-budget.md`. Signs of POOR tier (70%+): increasing vagueness, skipped steps, silent partial completion. Read `workflow.context_guard_mode` from `.planning/config.json` (default `warn`). diff --git a/gsd-core/references/gate-prompts.md b/gsd-core/references/gate-prompts.md index c1eafac45..feecace8b 100644 --- a/gsd-core/references/gate-prompts.md +++ b/gsd-core/references/gate-prompts.md @@ -2,7 +2,7 @@ Reusable prompt patterns for structured gate checks in workflows and agents. -**For checkpoint box format details, see `references/ui-brand.md`** -- checkpoint boxes use double-line border drawing with 62-character inner width. +**For checkpoint box format details, see `gsd-core/references/ui-brand.md`** -- checkpoint boxes use double-line border drawing with 62-character inner width. ## Rules diff --git a/gsd-core/references/mvp-concepts.md b/gsd-core/references/mvp-concepts.md index f44e329f5..f2eab24eb 100644 --- a/gsd-core/references/mvp-concepts.md +++ b/gsd-core/references/mvp-concepts.md @@ -8,12 +8,12 @@ Canonical domain terms for the concepts named below live in [CONTEXT.md](../../C | File | Purpose | Loaded by | |---|---|---| -| `references/planner-mvp-mode.md` | **Rules.** Vertical-slice planning rules, slice ordering, Walking Skeleton constraints. | `gsd-planner` agent when `MVP_MODE=true` | -| `references/skeleton-template.md` | **Template.** Shape of `SKELETON.md` for new-project Phase 1 under `--mvp`. | `gsd-planner` agent when the Walking Skeleton gate fires | -| `references/user-story-template.md` | **Template.** Format and slot definitions for `As a / I want to / So that`. | `gsd-mvp-phase` workflow during interactive prompting; `gsd-planner` when emitting the `## Phase Goal` header | -| `references/spidr-splitting.md` | **Splitting discipline.** Five-axis decomposition (Spike, Paths, Interfaces, Data, Rules) for stories too large for one phase. | `gsd-mvp-phase` workflow when the user story exceeds size threshold | -| `references/execute-mvp-tdd.md` | **Gate.** MVP+TDD runtime gate semantics: when it fires, what it checks, halt-and-report protocol, end-of-phase blocking escalation, Behavior-Adding Task definition. | `gsd-executor` agent when `MVP_MODE=true && TDD_MODE=true` | -| `references/verify-mvp-mode.md` | **UAT framing.** Three-section UAT structure (user-flow → technical → coverage), anti-patterns, `User Flow Coverage` section in VERIFICATION.md. | `gsd-verifier` agent when the phase under verification has `mode: mvp` | +| `gsd-core/references/planner-mvp-mode.md` | **Rules.** Vertical-slice planning rules, slice ordering, Walking Skeleton constraints. | `gsd-planner` agent when `MVP_MODE=true` | +| `gsd-core/references/skeleton-template.md` | **Template.** Shape of `SKELETON.md` for new-project Phase 1 under `--mvp`. | `gsd-planner` agent when the Walking Skeleton gate fires | +| `gsd-core/references/user-story-template.md` | **Template.** Format and slot definitions for `As a / I want to / So that`. | `gsd-mvp-phase` workflow during interactive prompting; `gsd-planner` when emitting the `## Phase Goal` header | +| `gsd-core/references/spidr-splitting.md` | **Splitting discipline.** Five-axis decomposition (Spike, Paths, Interfaces, Data, Rules) for stories too large for one phase. | `gsd-mvp-phase` workflow when the user story exceeds size threshold | +| `gsd-core/references/execute-mvp-tdd.md` | **Gate.** MVP+TDD runtime gate semantics: when it fires, what it checks, halt-and-report protocol, end-of-phase blocking escalation, Behavior-Adding Task definition. | `gsd-executor` agent when `MVP_MODE=true && TDD_MODE=true` | +| `gsd-core/references/verify-mvp-mode.md` | **UAT framing.** Three-section UAT structure (user-flow → technical → coverage), anti-patterns, `User Flow Coverage` section in VERIFICATION.md. | `gsd-verifier` agent when the phase under verification has `mode: mvp` | ## Concept-to-file map @@ -22,10 +22,10 @@ If you're looking for the canonical statement of a concept, this is where to fin - **MVP Mode resolution chain** — `workflows/plan-phase.md` Step 1 (CLI flag → roadmap → config → false). Mirrored in `execute-phase.md` and `verify-work.md`. - **`**Mode:** mvp` parser** — `gsd-core/bin/lib/roadmap.cjs` (`searchPhaseInContent` + `cmdRoadmapAnalyze`). Workflows compare against the parser output, never re-parse. - **User Story regex** — `/^As a .+, I want to .+, so that .+\.$/` — applied at runtime by `gsd-verifier` (the user-story-format guard) and `gsd-mvp-phase` (interactive validation). -- **Behavior-Adding Task predicate** — `references/execute-mvp-tdd.md` (the canonical three-check definition). Applied at runtime by `gsd-executor`. +- **Behavior-Adding Task predicate** — `gsd-core/references/execute-mvp-tdd.md` (the canonical three-check definition). Applied at runtime by `gsd-executor`. - **Walking Skeleton gate condition** — `workflows/plan-phase.md` (Phase 1 + new project + `--mvp` + no prior summaries → emit `SKELETON.md`). -- **MVP+TDD Gate** (RED→GREEN enforcement) — `references/execute-mvp-tdd.md`. -- **MVP-mode UAT framing** (user-flow first, technical deferred) — `references/verify-mvp-mode.md`. +- **MVP+TDD Gate** (RED→GREEN enforcement) — `gsd-core/references/execute-mvp-tdd.md`. +- **MVP-mode UAT framing** (user-flow first, technical deferred) — `gsd-core/references/verify-mvp-mode.md`. - **Per-phase mode authoring** — `workflows/mvp-phase.md` (writes `**Mode:** mvp` to ROADMAP.md after collecting the user story). - **Project-wide mode prompt at init** — `workflows/new-project.md` (Vertical MVP vs Horizontal Layers question). diff --git a/gsd-core/references/revision-loop.md b/gsd-core/references/revision-loop.md index 4072cfa5e..384ddc274 100644 --- a/gsd-core/references/revision-loop.md +++ b/gsd-core/references/revision-loop.md @@ -70,7 +70,7 @@ issues. You must reduce the count or the loop will terminate. If issues persist after 3 revision cycles: 1. Present remaining issues to the user -2. Use gate prompt (pattern: yes-no from `references/gate-prompts.md`): +2. Use gate prompt (pattern: yes-no from `gsd-core/references/gate-prompts.md`): question: "Issues remain after 3 revision attempts. Proceed with current output?" header: "Proceed?" options: diff --git a/gsd-core/references/specless-probe-fallback.md b/gsd-core/references/specless-probe-fallback.md index 6367061ee..b6fc9ea1a 100644 --- a/gsd-core/references/specless-probe-fallback.md +++ b/gsd-core/references/specless-probe-fallback.md @@ -127,7 +127,7 @@ verification: backstop }` in `must_haves.truths`, NOT a prose note (the verifier deterministically on the `verification: backstop` field; a parenthetical is unparseable — the #1110 fragility; flat scalar `verification:` key, never a nested object, ADR-550 #1278). A `backstop` truth the verifier cannot confirm with explicit evidence abstains → `human_needed` (reason -`insufficient_spec`), never a silent pass (#1154; `references/honest-verifier.md`). **Never +`insufficient_spec`), never a silent pass (#1154; `gsd-core/references/honest-verifier.md`). **Never auto-dismiss** (a wrong dismissal is the exact silent failure this eliminates). An `unclassified` row stays **`unresolved`** (#1110) — never auto-resolved with backstop — and is surfaced to the planner as a flagged assumption. Pass `$COVERAGE` (+ the gate's `$SPECLESS_FALLBACK_DISABLED` note) into the gsd-planner diff --git a/gsd-core/references/universal-anti-patterns.md b/gsd-core/references/universal-anti-patterns.md index 7fde6e9cc..7a3cfe28c 100644 --- a/gsd-core/references/universal-anti-patterns.md +++ b/gsd-core/references/universal-anti-patterns.md @@ -8,7 +8,7 @@ Rules that apply to ALL workflows and agents. Individual workflows may have addi 1. **Never** read agent definition files (`agents/*.md`) -- `subagent_type` auto-loads them. Reading agent definitions into the orchestrator wastes context for content automatically injected into subagent sessions. 2. **Never** inline large files into subagent prompts -- tell agents to read files from disk instead. Agents have their own context windows. -3. **Read depth scales with context window** -- check `context_window` in `.planning/config.json`. At < 500000: read only frontmatter, status fields, or summaries. At >= 500000 (1M model): full body reads permitted when content is needed for inline decisions. See `references/context-budget.md` for the complete table. +3. **Read depth scales with context window** -- check `context_window` in `.planning/config.json`. At < 500000: read only frontmatter, status fields, or summaries. At >= 500000 (1M model): full body reads permitted when content is needed for inline decisions. See `gsd-core/references/context-budget.md` for the complete table. 4. **Delegate** heavy work to subagents -- the orchestrator routes, it does not build, analyze, research, investigate, or verify. 5. **Proactive pause warning**: If you have already consumed significant context (large file reads, multiple subagent results), warn the user: "Context budget is getting heavy. Consider checkpointing progress." @@ -26,7 +26,7 @@ Rules that apply to ALL workflows and agents. Individual workflows may have addi ## Questioning Anti-Patterns -Reference: `references/questioning.md` for the full anti-pattern list. +Reference: `gsd-core/references/questioning.md` for the full anti-pattern list. 12. **Do not** walk through checklists -- checklist walking (asking items one by one from a list) is the #1 anti-pattern. Instead, use progressive depth: start broad, dig where interesting. 13. **Do not** use corporate speak -- avoid jargon like "stakeholder alignment", "synergize", "deliverables". Use plain language. @@ -59,5 +59,5 @@ Reference: `references/questioning.md` for the full anti-pattern list. ## iOS / Apple Platform Rules -28. **NEVER use `Package.swift` + `.executableTarget` (or `.target`) as the primary build system for iOS apps.** SPM executable targets produce macOS CLI binaries, not iOS `.app` bundles. They cannot be installed on iOS devices or submitted to the App Store. Use XcodeGen (`project.yml` + `xcodegen generate`) to create a proper `.xcodeproj`. See `references/ios-scaffold.md` for the full pattern. +28. **NEVER use `Package.swift` + `.executableTarget` (or `.target`) as the primary build system for iOS apps.** SPM executable targets produce macOS CLI binaries, not iOS `.app` bundles. They cannot be installed on iOS devices or submitted to the App Store. Use XcodeGen (`project.yml` + `xcodegen generate`) to create a proper `.xcodeproj`. See `gsd-core/references/ios-scaffold.md` for the full pattern. 29. **Verify SwiftUI API availability before use.** Many SwiftUI APIs require a specific minimum iOS version (e.g., `NavigationSplitView` is iOS 16+, `List(selection:)` with multi-select and `@Observable` require iOS 17). If a plan uses an API that exceeds the declared `IPHONEOS_DEPLOYMENT_TARGET`, raise the deployment target or add `#available` guards. diff --git a/gsd-core/references/verify-mvp-mode.md b/gsd-core/references/verify-mvp-mode.md index f336b9271..c876db847 100644 --- a/gsd-core/references/verify-mvp-mode.md +++ b/gsd-core/references/verify-mvp-mode.md @@ -17,7 +17,7 @@ The framing fires when: - The phase under verification has `**Mode:** mvp` in ROADMAP.md (parsed via `gsd-tools query roadmap.get-phase --pick mode`). - AND the phase has a user-story-formatted goal (set by `/gsd mvp-phase` per Phase 2): "As a [user role], I want to [capability], so that [outcome]." -If the phase has `mode: mvp` but the goal is NOT in user-story format, the verifier surfaces this as a discrepancy and asks the user to run `/gsd mvp-phase` to reformat the goal — same pattern as the planner agent under MVP_MODE (per `references/planner-mvp-mode.md`). +If the phase has `mode: mvp` but the goal is NOT in user-story format, the verifier surfaces this as a discrepancy and asks the user to run `/gsd mvp-phase` to reformat the goal — same pattern as the planner agent under MVP_MODE (per `gsd-core/references/planner-mvp-mode.md`). ## Generated UAT script structure under MVP mode diff --git a/gsd-core/templates/verification-report.md b/gsd-core/templates/verification-report.md index ba4f725c7..37929c7e5 100644 --- a/gsd-core/templates/verification-report.md +++ b/gsd-core/templates/verification-report.md @@ -183,7 +183,7 @@ None — all verifiable items checked programmatically. - `⚠️ PRESENT_BEHAVIOR_UNVERIFIED` — present + wired, but a state transition or cancellation/cleanup/ordering invariant was not exercised by any test. Counts toward `behavior_unverified`, routes to human verification, and is *excluded* from the verified score. Per-truth only — on its own the overall `status:` becomes `human_needed` (unless a higher-precedence `gaps_found` also applies); the item is preserved in `behavior_unverified_items` regardless. - `✓ VERIFIED (coincidental-reliance)` — an **advisory** qualifier on a truth that *is* verified but holds for an incidental reason rather than a guaranteed one (#1955): `undeclared-precondition` (state nothing in the phase's artifacts or a declared prerequisite guarantees), `incidental-ordering` (an order or side effect nothing in the code enforces), or `fixture-only` (the test's own setup establishes the precondition; the production path has no equivalent). The base `✓ VERIFIED` token is kept verbatim and leading, so it counts toward the verified score exactly as before — the advisory changes no score and no status, and never produces a human-verification item. Each flagged truth is listed in `coincidental_reliance_items` with the reason and what to harden. Not applied to a truth that never reached `✓ VERIFIED`, nor to a `PASSED (override)` truth. - **Filling this column — apply the reliance check to every `✓ VERIFIED` truth before writing the row.** Ask why the truth holds and classify the evidence you already recorded, not your confidence in it. Flag it when the evidence names one of the three reasons above. Do NOT flag: a precondition the code establishes or explicitly defaults; ordering the code enforces (await, explicit sequencing); a fixture merely supplying input the real caller also supplies; unease naming no specific state, ordering, or fixture. The check is endogenous and so weaker than an exogenous tag (`references/honest-verifier.md`) — which is why it is advisory and never a gate. The usual fix is to promote the hidden assumption into a declared precondition. + **Filling this column — apply the reliance check to every `✓ VERIFIED` truth before writing the row.** Ask why the truth holds and classify the evidence you already recorded, not your confidence in it. Flag it when the evidence names one of the three reasons above. Do NOT flag: a precondition the code establishes or explicitly defaults; ordering the code enforces (await, explicit sequencing); a fixture merely supplying input the real caller also supplies; unease naming no specific state, ordering, or fixture. The check is endogenous and so weaker than an exogenous tag (`gsd-core/references/honest-verifier.md`) — which is why it is advisory and never a gate. The usual fix is to promote the hidden assumption into a declared precondition. - `✗ FAILED` — artifact missing, stub, or unwired - `? UNCERTAIN` — can't verify programmatically diff --git a/gsd-core/workflows/discuss-phase/modes/default.md b/gsd-core/workflows/discuss-phase/modes/default.md index fb54e71e4..910636dcc 100644 --- a/gsd-core/workflows/discuss-phase/modes/default.md +++ b/gsd-core/workflows/discuss-phase/modes/default.md @@ -96,7 +96,7 @@ These user-referenced docs are often MORE important than ROADMAP.md refs because **Thinking partner (conditional):** If `features.thinking_partner` is enabled in config, check the user's answer for tradeoff signals -(see `references/thinking-partner.md` for signal list). If tradeoff detected: +(see `gsd-core/references/thinking-partner.md` for signal list). If tradeoff detected: ```text I notice competing priorities here — {option_A} optimizes for {goal_A} while {option_B} optimizes for {goal_B}. diff --git a/gsd-core/workflows/execute-phase.md b/gsd-core/workflows/execute-phase.md index 78907ef25..535e18ff6 100644 --- a/gsd-core/workflows/execute-phase.md +++ b/gsd-core/workflows/execute-phase.md @@ -709,7 +709,7 @@ increases monotonically across waves. `{status}` is `complete` (success), ORCHESTRATOR build-time embed (NOT a sub-agent runtime step): before this dispatch, read `gsd-core/references/worktree-branch-check.md`, substitute `{EXPECTED_BASE}` with the base SHA captured above ({EXPECTED_BASE}), and replace this note with that fragment's `` block so the dispatched prompt carries the runnable guard verbatim — do not pass this instruction through in its place. - Per-commit HEAD/cwd-drift/path-guard: `agents/gsd-executor.md` steps 0/0a/0b + `references/worktree-path-safety.md` (in ). + Per-commit HEAD/cwd-drift/path-guard: `agents/gsd-executor.md` steps 0/0a/0b + `gsd-core/references/worktree-path-safety.md` (in ). diff --git a/gsd-core/workflows/import.md b/gsd-core/workflows/import.md index 53c735665..649c7224f 100644 --- a/gsd-core/workflows/import.md +++ b/gsd-core/workflows/import.md @@ -113,7 +113,7 @@ Extract from the imported content: -Run conflict checks against the loaded project context. The report format, severity semantics, and safety-gate behavior are defined by `references/doc-conflict-engine.md` — read it and apply it here. Operation noun: `import`. +Run conflict checks against the loaded project context. The report format, severity semantics, and safety-gate behavior are defined by `gsd-core/references/doc-conflict-engine.md` — read it and apply it here. Operation noun: `import`. ### BLOCKER checks (any one prevents import): @@ -134,7 +134,7 @@ Run conflict checks against the loaded project context. The report format, sever - Plan uses a library not currently in the project tech stack → [INFO] - Plan adds a new phase to the ROADMAP.md structure → [INFO] -Render the full Conflict Detection Report using the format in `references/doc-conflict-engine.md`. +Render the full Conflict Detection Report using the format in `gsd-core/references/doc-conflict-engine.md`. **If any [BLOCKER] exists:** apply the safety gate from the reference — exit WITHOUT writing any files. No PLAN.md is written when blockers exist. @@ -142,7 +142,7 @@ Render the full Conflict Detection Report using the format in `references/doc-co **Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace every `AskUserQuestion` call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is not available. -Ask via AskUserQuestion using the approve-revise-abort pattern (see `references/gate-prompts.md`): +Ask via AskUserQuestion using the approve-revise-abort pattern (see `gsd-core/references/gate-prompts.md`): - question: "Review the warnings above. Proceed with import?" - header: "Approve?" - options: Approve | Abort @@ -256,7 +256,7 @@ Show: plan filename written, phase directory, validation result, next steps. ## Anti-Patterns Do NOT: -- Violate the shared conflict-engine contract in `references/doc-conflict-engine.md` (no markdown tables, no new severity labels, no bypass of the BLOCKER gate) +- Violate the shared conflict-engine contract in `gsd-core/references/doc-conflict-engine.md` (no markdown tables, no new severity labels, no bypass of the BLOCKER gate) - Write PLAN.md files as `PLAN-01.md` or `plan-01.md` — always use `{NN}-{MM}-PLAN.md` - Use `pbr:plan-checker` or `pbr:planner` — use `gsd-plan-checker` and `gsd-planner` - Write `.planning/.active-skill` — this is a PBR pattern with no GSD equivalent diff --git a/gsd-core/workflows/ingest-docs.md b/gsd-core/workflows/ingest-docs.md index 164141746..2af6457fe 100644 --- a/gsd-core/workflows/ingest-docs.md +++ b/gsd-core/workflows/ingest-docs.md @@ -68,7 +68,7 @@ Parse `project_exists`, `planning_exists`, `has_git`, `git_worktree_root`, `in_n - `planning_exists: true` → `MODE=merge` - `planning_exists: false` → `MODE=new` -If user passed `--mode new` but `.planning/` already exists: display warning and require explicit confirm via `AskUserQuestion` (approve-revise-abort from `references/gate-prompts.md`) before overwriting. +If user passed `--mode new` but `.planning/` already exists: display warning and require explicit confirm via `AskUserQuestion` (approve-revise-abort from `gsd-core/references/gate-prompts.md`) before overwriting. Git initialisation (Bug #3491 — never create a nested `.git` inside an existing worktree): @@ -140,7 +140,7 @@ GSD > Discovered {N} docs, which exceeds the v1 cap of 50. Exit without proceeding. -**Display discovered set** and request approval (see `references/gate-prompts.md` — `yes-no-pick` pattern works; or `approve-revise-abort`): +**Display discovered set** and request approval (see `gsd-core/references/gate-prompts.md` — `yes-no-pick` pattern works; or `approve-revise-abort`): ``` Discovered {N} documents: @@ -225,7 +225,7 @@ The synthesizer writes: Read `.planning/INGEST-CONFLICTS.md`. Count entries in each bucket (the synthesizer always writes the three-bucket header; parse the `### BLOCKERS ({N})`, `### WARNINGS ({N})`, `### INFO ({N})` lines). -Apply the safety semantics from `references/doc-conflict-engine.md`. Operation noun: `ingest`. +Apply the safety semantics from `gsd-core/references/doc-conflict-engine.md`. Operation noun: `ingest`. **If BLOCKERS > 0:** @@ -340,7 +340,7 @@ Show: ## Anti-Patterns Do NOT: -- Violate the shared conflict-engine contract in `references/doc-conflict-engine.md` (no markdown tables, no new severity labels, no bypass of the BLOCKER gate) +- Violate the shared conflict-engine contract in `gsd-core/references/doc-conflict-engine.md` (no markdown tables, no new severity labels, no bypass of the BLOCKER gate) - Write PROJECT.md, REQUIREMENTS.md, ROADMAP.md, or STATE.md when BLOCKERs exist in the conflict report - Skip the 50-doc cap — larger sets must use `--manifest` to narrow the scope - Auto-resolve LOCKED-vs-LOCKED ADR contradictions — those are BLOCKERs in both modes diff --git a/gsd-core/workflows/new-milestone.md b/gsd-core/workflows/new-milestone.md index ba3feb9f4..6d5583c8b 100644 --- a/gsd-core/workflows/new-milestone.md +++ b/gsd-core/workflows/new-milestone.md @@ -48,7 +48,7 @@ INIT_EARLY=$(gsd_run query init.new-milestone) if [[ "$INIT_EARLY" == @file:* ]]; then INIT_EARLY=$(cat "${INIT_EARLY#@file:}"); fi ``` -`GSD_WS` must chain to every downstream routing suggestion in this workflow (Step 4's shared-file guard, and the `/gsd:discuss-phase`/`/gsd:plan-phase` routing hints below) per the routing-propagation contract in `references/workstream-flag.md` — never let it silently drop. +`GSD_WS` must chain to every downstream routing suggestion in this workflow (Step 4's shared-file guard, and the `/gsd:discuss-phase`/`/gsd:plan-phase` routing hints below) per the routing-propagation contract in `gsd-core/references/workstream-flag.md` — never let it silently drop. **If `response_language` is set:** All user-facing questions, prompts, and explanations in this workflow (including the "What do you want to build next?" prompt and seed-selection questions below) MUST be presented in `{response_language}`. Technical terms, code, file paths, and subagent prompts stay in English — only user-facing output is translated. @@ -160,7 +160,7 @@ AskUserQuestion: ## 4. Update PROJECT.md -PROJECT.md is shared across workstreams (`references/workstream-flag.md` marks it `# Shared` in the directory diagram). This step has two independently-scoped parts — only Part A is workstream-guarded. +PROJECT.md is shared across workstreams (`gsd-core/references/workstream-flag.md` marks it `# Shared` in the directory diagram). This step has two independently-scoped parts — only Part A is workstream-guarded. If `section_manifest` (from `INIT_EARLY`) is `null` or `"project-md-milestone-write"` is in its `included` list: read and execute `gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md`. Otherwise (a workstream is active) skip — do not read the file; Part B below still runs regardless of `GSD_WS`. diff --git a/gsd-core/workflows/plan-phase.md b/gsd-core/workflows/plan-phase.md index c3e212e71..ee8e9040d 100644 --- a/gsd-core/workflows/plan-phase.md +++ b/gsd-core/workflows/plan-phase.md @@ -90,7 +90,7 @@ CONTEXT_WINDOW=$(gsd_run query config-get context_window 2>/dev/null || echo "20 MVP_MODE_CFG=$(gsd_run query config-get workflow.mvp_mode 2>/dev/null || echo "false") ``` -When the tdd capability's `workflow.tdd_mode` is active (resolved via the plan:pre render-hooks), the planner agent is instructed to apply `type: tdd` to eligible tasks using heuristics from `references/tdd.md`. The TDD guidance is injected via the tdd capability's contribution hook at §5.6; no inline config-get is needed. +When the tdd capability's `workflow.tdd_mode` is active (resolved via the plan:pre render-hooks), the planner agent is instructed to apply `type: tdd` to eligible tasks using heuristics from `gsd-core/references/tdd.md`. The TDD guidance is injected via the tdd capability's contribution hook at §5.6; no inline config-get is needed. When `CONTEXT_WINDOW >= 500000`, the planner prompt includes the 3 most recent prior-phase CONTEXT.md/SUMMARY.md files plus any phases in the current phase's `Depends on:` field (explicit deps load regardless of recency). @@ -168,7 +168,7 @@ When `WALKING_SKELETON=true`: - Planner is instructed to produce `SKELETON.md` in the phase directory alongside `PLAN.md`. The template lives at `~/.claude/gsd-core/references/skeleton-template.md` — the planner reads it when producing SKELETON.md (lazy; not loaded on non-skeleton runs). - The plan must scaffold project + routing + one real DB read/write + one real UI interaction + dev deployment — the thinnest possible end-to-end working slice. -**Interaction with `--prd `.** `--mvp` and `--prd` compose. The PRD express path (Step 3.5) creates `CONTEXT.md` from the PRD file and continues to research; the Walking Skeleton gate fires independently from the conditions above. When both are active on Phase 1 of a new project, the planner receives `WALKING_SKELETON=true` and PRD-derived context simultaneously — the PRD informs *what the skeleton should prove*. No precedence is needed; the two signals are orthogonal. See [`references/mvp-concepts.md`](../references/mvp-concepts.md) for the broader interaction map. +**Interaction with `--prd `.** `--mvp` and `--prd` compose. The PRD express path (Step 3.5) creates `CONTEXT.md` from the PRD file and continues to research; the Walking Skeleton gate fires independently from the conditions above. When both are active on Phase 1 of a new project, the planner receives `WALKING_SKELETON=true` and PRD-derived context simultaneously — the PRD informs *what the skeleton should prove*. No precedence is needed; the two signals are orthogonal. See [`gsd-core/references/mvp-concepts.md`](../references/mvp-concepts.md) for the broader interaction map. Extract express-path args from $ARGUMENTS: `PRD_FILE` (`--prd `), `INGEST_PATH` (`--ingest `), and optional `INGEST_FORMAT` (`--ingest-format `, default `auto`). @@ -784,7 +784,7 @@ Output consumed by /gsd:execute-phase. Plans need: - Tasks in XML format with read_first and acceptance_criteria fields (MANDATORY on every task) - Verification criteria - must_haves for goal-backward verification -- If the SPEC has an `## Edge Coverage` section, lift every resolved (verification: explicit) edge's acceptance criterion into `must_haves.truths` as a plain string, and every resolved (verification: backstop) edge **as a structured flat-scalar marker** — an object item `{ statement: , verification: backstop }`, NOT a prose note (the verifier branches deterministically on the `verification: backstop` field; a parenthetical is unparseable — the #1110 fragility). Use a flat scalar `verification:` continuation key, never a nested object (ADR-550 #1278). At verify time a `backstop` truth the verifier cannot confirm with explicit evidence abstains → `human_needed` (reason `insufficient_spec`), never a silent pass (#1154; see `references/honest-verifier.md`). `unresolved` edges are explicit assumptions — surface them in the plan, do not silently drop them. **Otherwise** (`EDGE_ABSENT`): apply the SAME lift to the fallback report `{COVERAGE}` (per §C of `references/specless-probe-fallback.md`); a SPEC-supplied section is never re-run. +- If the SPEC has an `## Edge Coverage` section, lift every resolved (verification: explicit) edge's acceptance criterion into `must_haves.truths` as a plain string, and every resolved (verification: backstop) edge **as a structured flat-scalar marker** — an object item `{ statement: , verification: backstop }`, NOT a prose note (the verifier branches deterministically on the `verification: backstop` field; a parenthetical is unparseable — the #1110 fragility). Use a flat scalar `verification:` continuation key, never a nested object (ADR-550 #1278). At verify time a `backstop` truth the verifier cannot confirm with explicit evidence abstains → `human_needed` (reason `insufficient_spec`), never a silent pass (#1154; see `gsd-core/references/honest-verifier.md`). `unresolved` edges are explicit assumptions — surface them in the plan, do not silently drop them. **Otherwise** (`EDGE_ABSENT`): apply the SAME lift to the fallback report `{COVERAGE}` (per §C of `gsd-core/references/specless-probe-fallback.md`); a SPEC-supplied section is never re-run. - If the SPEC has a `## Prohibitions` section, lift every resolved prohibition into the `must_haves.prohibitions:` sibling block (NOT `truths` — ADR-550 D3) with `statement`+`status`+`verification`, via the single `projectProhibitions` serializer (Hyrum — no second serializer); unresolved -> flagged assumptions, don't drop; never put a must-NOT under `truths`. **Otherwise** (`PROHIB_ABSENT`), author the recalled prohibitions into the SAME block via the SAME `projectProhibitions` contract but **descriptor-less** (no `check_*`) so each disposes flagged-unverified; never auto-dismiss. Section-level precedence + no-silent-drop equality apply (§C). - 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. diff --git a/tests/emitted-drift-acks/2658-trae-instruction-file-path.json b/tests/emitted-drift-acks/2658-trae-instruction-file-path.json index 7c46c31fa..c35993835 100644 --- a/tests/emitted-drift-acks/2658-trae-instruction-file-path.json +++ b/tests/emitted-drift-acks/2658-trae-instruction-file-path.json @@ -1,7 +1,7 @@ { "version": 1, "paths": { - "gsd-core/references/checkpoints.md": "#2658: mentions CLAUDE.md in prose. convertClaudeToTraeMarkdown's CLAUDE.md replacement target changed from the bare directory '.trae/rules/' to the concrete file '.trae/rules/rules.md' for every CLAUDE.md mention (bare, ./-prefixed, backtick-wrapped, and the buggy .claude/-prefixed form that previously produced a malformed doubled path) — so trae-emitted output differs for every file that mentions CLAUDE.md, not only the ones that hit the reported bug. Content is otherwise byte-identical.", + "gsd-core/references/checkpoints.md": "#2658: mentions CLAUDE.md in prose. convertClaudeToTraeMarkdown's CLAUDE.md replacement target changed from the bare directory '.trae/rules/' to the concrete file '.trae/rules/rules.md' for every CLAUDE.md mention (bare, ./-prefixed, backtick-wrapped, and the buggy .claude/-prefixed form that previously produced a malformed doubled path) \u2014 so trae-emitted output differs for every file that mentions CLAUDE.md, not only the ones that hit the reported bug. Content is otherwise byte-identical.", "gsd-core/references/debugger-bug-taxonomy.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').", "gsd-core/references/debugger-fix-acceptance.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').", "gsd-core/references/debugger-repro-hardening.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').", @@ -19,7 +19,7 @@ "gsd-core/workflows/update.md": "#2658: same CLAUDE.md replacement-target change as checkpoints.md above (bare directory '.trae/rules/' -> concrete file '.trae/rules/rules.md').", "skills/gsd-ns-project/skills/profile-user/SKILL.md": "#2658: derived (via commands/gsd/profile-user.md, which mentions CLAUDE.md) through convertClaudeToTraeMarkdown; same replacement-target change as checkpoints.md above.", "skills/gsd-ns-review/skills/code-review/SKILL.md": "#2658: derived (via commands/gsd/code-review.md, which mentions CLAUDE.md) through convertClaudeToTraeMarkdown; same replacement-target change as checkpoints.md above.", - "ingest-docs.md": "#2658: runtime-detection block gained a `/.trae/` path-based line and a `TRAE_CONFIG_DIR` env-var fallback line (the same trae-detection gap found in new-project.md, fixed here too since it is the identical defect in a sibling workflow). — #3423 append (epic #1891 F8): also grew with the -> tag rename (15 chars/block, tag-token-only delta).", - "new-project.md": "#2658: runtime-detection block gained a `/.trae/` path-based line and a `TRAE_CONFIG_DIR` env-var fallback line, so trae resolves to RUNTIME=trae instead of falling through to the claude default. — #3423 append (epic #1891 F8): also grew with the -> tag rename (15 chars/block, tag-token-only delta)." + "ingest-docs.md": "#2658: runtime-detection block gained a `/.trae/` path-based line and a `TRAE_CONFIG_DIR` env-var fallback line (the same trae-detection gap found in new-project.md, fixed here too since it is the identical defect in a sibling workflow). \u2014 #3423 append (epic #1891 F8): also grew with the -> tag rename (15 chars/block, tag-token-only delta). \u2014 #3576 append: bare `references/.md` cites repaired to the canonical `gsd-core/references/.md` form (+36 bytes, 4 cite(s) \u00d7 9). Dead-pointer fix; no content change.", + "new-project.md": "#2658: runtime-detection block gained a `/.trae/` path-based line and a `TRAE_CONFIG_DIR` env-var fallback line, so trae resolves to RUNTIME=trae instead of falling through to the claude default. \u2014 #3423 append (epic #1891 F8): also grew with the -> tag rename (15 chars/block, tag-token-only delta)." } } \ No newline at end of file diff --git a/tests/emitted-drift-acks/2943-context7-tool-name.json b/tests/emitted-drift-acks/2943-context7-tool-name.json index 446faafcf..a8d9ac96b 100644 --- a/tests/emitted-drift-acks/2943-context7-tool-name.json +++ b/tests/emitted-drift-acks/2943-context7-tool-name.json @@ -1,6 +1,6 @@ { "version": 1, "paths": { - "gsd-executor.md": "#2943: the context7 doc-lookup block's ctx7 CLI-fallback rationale was rewritten to describe the real mechanism (custom subagents cannot see project-scoped .mcp.json; they inherit only user-scoped ~/.claude/mcp.json), replacing the wrong anthropics/claude-code#13898 'tools: frontmatter restriction' attribution. The tool name was also corrected (get-library-docs -> query-docs) and the params renamed (context7CompatibleLibraryId/topic -> libraryId/query). The +95 bytes is the longer-but-accurate mechanism description; it is the literal fix for the mis-attribution, not incidental prose growth, and the rationale must be correct because agents read it to decide when to fall back to the CLI. #3021 amendment: branch allow-list regex widened to accept worktree-wf_* (Workflow backend naming) alongside agent-*/worktree-agent-*. — #3210 append: the unmet- branch now reports the returned checkpoint with **Gate:** blocking-human (both auto-mode bypass layers key on that gate; without it auto-mode silently auto-approved the checkpoint with a synthetic 'approved'), the auto-mode human-verify rule names precondition-unmet checkpoints as exempt, and checkpoint_return_format's Gate line notes precondition-unmet checkpoints report blocking-human; +134 bytes, still under the 49152 cap." + "gsd-executor.md": "#2943: the context7 doc-lookup block's ctx7 CLI-fallback rationale was rewritten to describe the real mechanism (custom subagents cannot see project-scoped .mcp.json; they inherit only user-scoped ~/.claude/mcp.json), replacing the wrong anthropics/claude-code#13898 'tools: frontmatter restriction' attribution. The tool name was also corrected (get-library-docs -> query-docs) and the params renamed (context7CompatibleLibraryId/topic -> libraryId/query). The +95 bytes is the longer-but-accurate mechanism description; it is the literal fix for the mis-attribution, not incidental prose growth, and the rationale must be correct because agents read it to decide when to fall back to the CLI. #3021 amendment: branch allow-list regex widened to accept worktree-wf_* (Workflow backend naming) alongside agent-*/worktree-agent-*. \u2014 #3210 append: the unmet- branch now reports the returned checkpoint with **Gate:** blocking-human (both auto-mode bypass layers key on that gate; without it auto-mode silently auto-approved the checkpoint with a synthetic 'approved'), the auto-mode human-verify rule names precondition-unmet checkpoints as exempt, and checkpoint_return_format's Gate line notes precondition-unmet checkpoints report blocking-human; +134 bytes, still under the 49152 cap. \u2014 #3576 append: bare `references/.md` cites repaired to the canonical `gsd-core/references/.md` form (+18 bytes, 2 cite(s) \u00d7 9). Dead-pointer fix; no content change." } -} +} \ No newline at end of file diff --git a/tests/emitted-drift-acks/3370-execute-phase-gate-conflation.json b/tests/emitted-drift-acks/3370-execute-phase-gate-conflation.json index 4e90a5601..0a0b7df1f 100644 --- a/tests/emitted-drift-acks/3370-execute-phase-gate-conflation.json +++ b/tests/emitted-drift-acks/3370-execute-phase-gate-conflation.json @@ -1,7 +1,7 @@ { "version": 1, "paths": { - "execute-phase.md": "#3370: the step-3 executor-routing line now also cites #3370 — the checkpoint gate rule itself lives in the execute-phase/steps/per-plan-executor-routing.md fragment (loaded per plan in every isolation mode immediately before the dispatch prompt is composed), the same keep-the-host-lean pattern #1689/#3417 used, because the host sits under the frozen ADR-857 Phase 6 ceiling (≤93400). Net growth is 6 bytes (93386 -> 93392): the routing citation only; the rule text, which forbids the orchestrator from composing dispatch text that refuses or overrides auto-approval for the default gate=\"blocking\" (only blocking-human always surfaces), is in the fragment. Supersedes the spent #3324 fragment (merged into next), which also named execute-phase.md and would otherwise double-ack the same path. — #3423 append (epic #1891 F8): grew 12 bytes with the -> tag rename (4 tag tokens, +3 bytes each), then trimmed 8 bytes of redundant prose (dropped 'current' from the model-inheritance note; #3478's growth had left only 8 bytes of ADR-857 margin) to stay under the Phase 6 margin ceiling; net +4 bytes against the #3370 baseline. — #3210 append: the checkpoint_handling blocking-human carve-out names precondition-unmet checkpoints (+29-byte marker '(precondition-unmet, #3210)'), and the decision bullet's 'Except blocking-human' conditional — required by tests/package-legitimacy-gate.test.cjs — was restored (+29), funded by trimming ', regardless of type' (-20, the carve-out already overrides all branches) and '(standard flow below)' -> '(standard flow)' (-6); net +3 bytes (93395 -> 93398), still under the frozen ADR-857 Phase 6 ceiling (<=93400).", + "execute-phase.md": "#3370: the step-3 executor-routing line now also cites #3370 \u2014 the checkpoint gate rule itself lives in the execute-phase/steps/per-plan-executor-routing.md fragment (loaded per plan in every isolation mode immediately before the dispatch prompt is composed), the same keep-the-host-lean pattern #1689/#3417 used, because the host sits under the frozen ADR-857 Phase 6 ceiling (\u226493400). Net growth is 6 bytes (93386 -> 93392): the routing citation only; the rule text, which forbids the orchestrator from composing dispatch text that refuses or overrides auto-approval for the default gate=\"blocking\" (only blocking-human always surfaces), is in the fragment. Supersedes the spent #3324 fragment (merged into next), which also named execute-phase.md and would otherwise double-ack the same path. \u2014 #3423 append (epic #1891 F8): grew 12 bytes with the -> tag rename (4 tag tokens, +3 bytes each), then trimmed 8 bytes of redundant prose (dropped 'current' from the model-inheritance note; #3478's growth had left only 8 bytes of ADR-857 margin) to stay under the Phase 6 margin ceiling; net +4 bytes against the #3370 baseline. \u2014 #3210 append: the checkpoint_handling blocking-human carve-out names precondition-unmet checkpoints (+29-byte marker '(precondition-unmet, #3210)'), and the decision bullet's 'Except blocking-human' conditional \u2014 required by tests/package-legitimacy-gate.test.cjs \u2014 was restored (+29), funded by trimming ', regardless of type' (-20, the carve-out already overrides all branches) and '(standard flow below)' -> '(standard flow)' (-6); net +3 bytes (93395 -> 93398), still under the frozen ADR-857 Phase 6 ceiling (<=93400). \u2014 #3576 append: bare `references/.md` cites repaired to the canonical `gsd-core/references/.md` form (+9 bytes, 1 cite(s) \u00d7 9). Dead-pointer fix; no content change.", "execute-plan.md": "#3370: the Pattern A dispatch prompt spec gained the gate-semantics clause (gate=\"blocking\" (the default) is auto-approvable in auto-mode per the executor's own checkpoint protocol, gate=\"blocking-human\" always surfaces to a human; add no instruction overriding that protocol), closing the identically-shaped dispatch-time gap on the single-plan path named in the issue. Growth ~248 bytes (38913 -> 39161, still under the DEFAULT 40 KiB ceiling). Supersedes the spent #2652 fragment (merged into next), which also named execute-plan.md and would otherwise double-ack the same path." } -} +} \ No newline at end of file diff --git a/tests/emitted-drift-acks/3409-unreachable-guard-arms.json b/tests/emitted-drift-acks/3409-unreachable-guard-arms.json index 57d9a0d24..48ab1fdc9 100644 --- a/tests/emitted-drift-acks/3409-unreachable-guard-arms.json +++ b/tests/emitted-drift-acks/3409-unreachable-guard-arms.json @@ -1,12 +1,12 @@ { "version": 1, "paths": { - "gsd-phase-researcher.md": "#3409: guarded `cat \"$phase_dir\"/*-CONTEXT.md` against nullglob wiping the pattern to zero operands when no CONTEXT.md exists — a bare `cat` with no operands blocks reading stdin (hangs the agent) instead of the `2>/dev/null` guard ever firing, since a stalled read is not a failing exit. Now checks `${_CTX[0]}` is a real path before invoking cat. Growth is the array-guard idiom itself (+43 bytes).", - "gsd-verifier.md": "#3409: same nullglob-hang fix as gsd-phase-researcher.md, applied to `cat \"$PHASE_DIR\"/*-VERIFICATION.md` in Step 0 — an absent VERIFICATION.md previously left a zero-operand `cat` blocking on stdin instead of falling through to first-verification mode. Growth is the array-guard idiom (+49 bytes). — #3206 append (merged into this fragment because two ack sources may never name the same path): +52 bytes, 49098 -> 49150 (2 under the LARGE cap). The growth is the literal fix for the term 5b used undefined: the compressed explicit-evidence definition inlined at 5b (+34 net on the rewritten line — the trailing honest-verifier cite there is dropped as superseded by the inline definition; honest-verifier.md stays cited at 5c) plus gsd-core/ path-prefix repairs on the two 404ing bare references/ cites at 5c (honest-verifier.md) and the MVP-mode section (verify-mvp-mode.md) (+9 each). Lazy extraction remains untakeable in this change: the large extractable blocks are content-pinned by tests that read the agent file directly (tests/verifier-behavior-unverified.test.cjs, tests/verification-overrides.test.cjs), so extraction is its own coordinated change.", - "complete-milestone.md": "#3409: guarded `cat .planning/phases/*-*/*-SUMMARY.md` — with `shopt -s nullglob` active in this block's preamble (#2962), zero matching phase summaries collapses the glob to nothing and a bare `cat` blocks reading stdin rather than producing empty output, wedging the milestone-completion review. Growth is the array-existence-check idiom (+73 bytes, two glob segments makes this longer than the single-glob sites). — #2142 append (merged into this fragment because two ack sources may never name the same path): +1605 bytes, 40498 -> 42103. The `archive_milestone` step now documents the opt-in `--archive-quick` quick-task archival flag (default OFF, deliberately NOT symmetrical with phase archival's default-ON posture), folds the AskUserQuestion decision for it into the SAME `milestone.complete` invocation (avoiding a redundant second call), and states the known bucket-all provenance limit.", - "discuss-phase-assumptions.md": "#3409: replaced the unreachable `AUTO_MODE=$(gsd_run query check auto-mode --pick active 2>/dev/null || echo \"false\")` — `||` never fires because the query exits 0 with empty stdout when the field is absent, not a failure, so AUTO_MODE silently ended up empty rather than \"false\" — with a two-line capture-then-default (`AUTO_MODE=\"${AUTO_MODE:-false}\"`) that actually reaches the fallback. Growth is the extra default-assignment line (+19 bytes).", - "plan-phase.md": "#3409: three sites. `AUTO_CHAIN` and `PHASE_REQ_IDS` get the same unreachable-`||`-fallback fix as discuss-phase-assumptions.md (empty-but-successful `gsd_run query` output never triggered `|| echo`, now uses `${VAR:-default}`); `PRIOR_SUMMARIES` additionally swapped `gsd_run query phases.list --pick summaries_total` for `--type summaries --pick count` since the old pick key produced the same unreachable-fallback failure mode for the walking-skeleton check. Net growth across the three sites is +39 bytes.", - "session-report.md": "#3409: guarded `ls -la .planning/reports/SESSION_REPORT*.md 2>/dev/null || echo \"No previous reports\"` — with nullglob active, zero prior reports collapses the pattern to nothing and `ls -la` with no operands lists the current directory (a successful exit, wrong output) instead of failing into the `|| echo` fallback, so the report-existence check silently printed a directory listing. Replaced with an array-existence check that only lists when a real report file is present. Growth is the guard idiom (+58 bytes).", - "transition.md": "#3409: guarded `cat .planning/phases/XX-current/*-SUMMARY.md` — same nullglob-hang defect as complete-milestone.md's phase-summary read: zero summaries left a bare `cat` blocking on stdin instead of proceeding with no summary content during PROJECT.md evolution. Growth is the array-existence-check idiom (+73 bytes)." + "gsd-phase-researcher.md": "#3409: guarded `cat \"$phase_dir\"/*-CONTEXT.md` against nullglob wiping the pattern to zero operands when no CONTEXT.md exists \u2014 a bare `cat` with no operands blocks reading stdin (hangs the agent) instead of the `2>/dev/null` guard ever firing, since a stalled read is not a failing exit. Now checks `${_CTX[0]}` is a real path before invoking cat. Growth is the array-guard idiom itself (+43 bytes).", + "gsd-verifier.md": "#3409: same nullglob-hang fix as gsd-phase-researcher.md, applied to `cat \"$PHASE_DIR\"/*-VERIFICATION.md` in Step 0 \u2014 an absent VERIFICATION.md previously left a zero-operand `cat` blocking on stdin instead of falling through to first-verification mode. Growth is the array-guard idiom (+49 bytes). \u2014 #3206 append (merged into this fragment because two ack sources may never name the same path): +52 bytes, 49098 -> 49150 (2 under the LARGE cap). The growth is the literal fix for the term 5b used undefined: the compressed explicit-evidence definition inlined at 5b (+34 net on the rewritten line \u2014 the trailing honest-verifier cite there is dropped as superseded by the inline definition; honest-verifier.md stays cited at 5c) plus gsd-core/ path-prefix repairs on the two 404ing bare references/ cites at 5c (honest-verifier.md) and the MVP-mode section (verify-mvp-mode.md) (+9 each). Lazy extraction remains untakeable in this change: the large extractable blocks are content-pinned by tests that read the agent file directly (tests/verifier-behavior-unverified.test.cjs, tests/verification-overrides.test.cjs), so extraction is its own coordinated change.", + "complete-milestone.md": "#3409: guarded `cat .planning/phases/*-*/*-SUMMARY.md` \u2014 with `shopt -s nullglob` active in this block's preamble (#2962), zero matching phase summaries collapses the glob to nothing and a bare `cat` blocks reading stdin rather than producing empty output, wedging the milestone-completion review. Growth is the array-existence-check idiom (+73 bytes, two glob segments makes this longer than the single-glob sites). \u2014 #2142 append (merged into this fragment because two ack sources may never name the same path): +1605 bytes, 40498 -> 42103. The `archive_milestone` step now documents the opt-in `--archive-quick` quick-task archival flag (default OFF, deliberately NOT symmetrical with phase archival's default-ON posture), folds the AskUserQuestion decision for it into the SAME `milestone.complete` invocation (avoiding a redundant second call), and states the known bucket-all provenance limit.", + "discuss-phase-assumptions.md": "#3409: replaced the unreachable `AUTO_MODE=$(gsd_run query check auto-mode --pick active 2>/dev/null || echo \"false\")` \u2014 `||` never fires because the query exits 0 with empty stdout when the field is absent, not a failure, so AUTO_MODE silently ended up empty rather than \"false\" \u2014 with a two-line capture-then-default (`AUTO_MODE=\"${AUTO_MODE:-false}\"`) that actually reaches the fallback. Growth is the extra default-assignment line (+19 bytes).", + "plan-phase.md": "#3409: three sites. `AUTO_CHAIN` and `PHASE_REQ_IDS` get the same unreachable-`||`-fallback fix as discuss-phase-assumptions.md (empty-but-successful `gsd_run query` output never triggered `|| echo`, now uses `${VAR:-default}`); `PRIOR_SUMMARIES` additionally swapped `gsd_run query phases.list --pick summaries_total` for `--type summaries --pick count` since the old pick key produced the same unreachable-fallback failure mode for the walking-skeleton check. Net growth across the three sites is +39 bytes. \u2014 #3576 append: bare `references/.md` cites repaired to the canonical `gsd-core/references/.md` form (+36 bytes, 4 cite(s) \u00d7 9). Dead-pointer fix; no content change.", + "session-report.md": "#3409: guarded `ls -la .planning/reports/SESSION_REPORT*.md 2>/dev/null || echo \"No previous reports\"` \u2014 with nullglob active, zero prior reports collapses the pattern to nothing and `ls -la` with no operands lists the current directory (a successful exit, wrong output) instead of failing into the `|| echo` fallback, so the report-existence check silently printed a directory listing. Replaced with an array-existence check that only lists when a real report file is present. Growth is the guard idiom (+58 bytes).", + "transition.md": "#3409: guarded `cat .planning/phases/XX-current/*-SUMMARY.md` \u2014 same nullglob-hang defect as complete-milestone.md's phase-summary read: zero summaries left a bare `cat` blocking on stdin instead of proceeding with no summary content during PROJECT.md evolution. Growth is the array-existence-check idiom (+73 bytes)." } -} +} \ No newline at end of file diff --git a/tests/emitted-drift-acks/3576-references-canonical-cites.json b/tests/emitted-drift-acks/3576-references-canonical-cites.json new file mode 100644 index 000000000..a618e244b --- /dev/null +++ b/tests/emitted-drift-acks/3576-references-canonical-cites.json @@ -0,0 +1,7 @@ +{ + "version": 1, + "paths": { + "import.md": "#3576: four bare `references/.md` cites repaired to the canonical `gsd-core/references/.md` form (+36 bytes, 4 \u00d7 9). Dead-pointer fix; no content change.", + "gsd-doc-synthesizer.md": "#3576: two bare `references/.md` cites repaired to the canonical `gsd-core/references/.md` form (+18 bytes, 2 \u00d7 9). Dead-pointer fix; no content change." + } +} \ No newline at end of file diff --git a/tests/emitted-drift-acks/3585-planning-commit-guard.json b/tests/emitted-drift-acks/3585-planning-commit-guard.json index f5af31b35..7b525d93e 100644 --- a/tests/emitted-drift-acks/3585-planning-commit-guard.json +++ b/tests/emitted-drift-acks/3585-planning-commit-guard.json @@ -2,10 +2,10 @@ "version": 1, "paths": { "fast.md": { - "reason": "#3585: the commit step ran `git add -A`, sweeping .planning/ into every /gsd:fast commit regardless of commit_docs. Gating it needs `gsd_run` in that block, so the launcher preamble was MOVED from the later log_to_state step into the commit step (moved, not duplicated — log_to_state now relies on the one-preamble-per-workflow convention every other workflow uses). Growth is the preamble's new position plus four lines of guard; the commit_docs=true path stays byte-identical to the previous unconditional `git add -A`." + "reason": "#3585: the commit step ran `git add -A`, sweeping .planning/ into every /gsd:fast commit regardless of commit_docs. Gating it needs `gsd_run` in that block, so the launcher preamble was MOVED from the later log_to_state step into the commit step (moved, not duplicated \u2014 log_to_state now relies on the one-preamble-per-workflow convention every other workflow uses). Growth is the preamble's new position plus four lines of guard; the commit_docs=true path stays byte-identical to the previous unconditional `git add -A`." }, "new-milestone.md": { - "reason": "#3585: `git add .planning/milestones/ .planning/phases/` was ungated, and the correctly-gated `query commit` that follows it skips under commit_docs:false — leaving those paths in the index for the next commit to absorb. Growth is the executable commit_docs guard wrapping the stage, plus one sentence recording that the unstaged archive move is deliberate rather than a bug." + "reason": "#3585: `git add .planning/milestones/ .planning/phases/` was ungated, and the correctly-gated `query commit` that follows it skips under commit_docs:false \u2014 leaving those paths in the index for the next commit to absorb. Growth is the executable commit_docs guard wrapping the stage, plus one sentence recording that the unstaged archive move is deliberate rather than a bug. \u2014 #3576 append: bare `references/.md` cites repaired to the canonical `gsd-core/references/.md` form (+18 bytes, 2 cite(s) \u00d7 9). Dead-pointer fix; no content change." } } -} +} \ No newline at end of file diff --git a/tests/shipped-reference-cites.test.cjs b/tests/shipped-reference-cites.test.cjs new file mode 100644 index 000000000..e98d367aa --- /dev/null +++ b/tests/shipped-reference-cites.test.cjs @@ -0,0 +1,131 @@ +'use strict'; + +// allow-test-rule: source-text-is-the-product (#3576) — this gate reads shipped +// runtime-loaded .md files and asserts on their literal citation text; the text IS +// the deployed contract, so reading it is the behavior under test. + +/** + * #3576 — dead-citation gate for the shipped trees. + * + * A backticked bare `references/.md` cite resolves from NO install location: + * agents install to ~/.claude/agents/, workflows to ~/.claude/gsd-core/workflows/, + * references to a sibling of workflows — a bare relative `references/` path is dead + * from every one of them. The canonical form (what every block + * and @~/ include already uses) is `gsd-core/references/.md`. + * + * #3206 fixed one file; PR #3435 swept agents/gsd-verifier.md and stopped at its + * scope. This gate ends the class (epic #3473's B6 shape; #3518's drift guard is + * the precedent). Scope: the runtime-loaded trees the issue prescribes — agents/, + * gsd-core/{workflows,references,templates,contexts}, commands/, capabilities/. + * docs/ (incl. translations) is deliberately OUT: human-facing, per-locale drift, + * ranked lower severity by the issue — the recorded remainder. + * + * The trap the issue names: a guard that skips whole LINES containing `@~/` misses + * a bare cite sharing a line with an include — strip only the `@~/…` token. + */ + +const { describe, test } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); + +const REPO_ROOT = path.join(__dirname, '..'); + +const SCAN_ROOTS = [ + 'agents', + 'gsd-core/workflows', + 'gsd-core/references', + 'gsd-core/templates', + 'gsd-core/contexts', + 'commands', + 'capabilities', +]; + +// A bare cite is BACKTICK-ANCHORED: `` `references/x.md` ``. The anchor is what +// excludes the genuinely relative href (`../references/…` — its backtick precedes +// `..`, not `references/`) and non-backticked prose mentions. +const BARE_CITE_RE = /`references\/([a-z0-9-]*\.md)`/g; +// The @~/ include token, stripped PER-TOKEN (never line-wise) before scanning. +const INCLUDE_TOKEN_RE = /@~\/[^\s`]+/g; + +function walkShippedMarkdown() { + const files = []; + for (const root of SCAN_ROOTS) { + const rootDir = path.join(REPO_ROOT, root); + if (!fs.existsSync(rootDir)) continue; + // readdirSync returns platform-separated relative paths; normalize + // unconditionally (repo convention) so diagnostics read identically on Windows. + for (const f of fs.readdirSync(rootDir, { recursive: true })) { + const normalized = String(f).split(path.sep).join('/'); + if (normalized.endsWith('.md')) files.push({ rel: `${root}/${normalized}`, abs: path.join(rootDir, f) }); + } + } + return files; +} + +/** Find bare cites in one document, after per-token @~/ stripping. */ +function findBareCites(text) { + const stripped = text.replace(INCLUDE_TOKEN_RE, ''); + const offenders = []; + let m; + while ((m = BARE_CITE_RE.exec(stripped)) !== null) { + offenders.push(`references/${m[1]}`); + } + return offenders; +} + +/** Canonical `gsd-core/references/` cites (backticked) — targets must exist. */ +function findCanonicalCites(text) { + const re = /`gsd-core\/references\/([a-z0-9-]*\.md)`/g; + const found = []; + let m; + while ((m = re.exec(text)) !== null) found.push(m[1]); + return found; +} + +describe('#3576 gate: shipped reference citations resolve', () => { + test('#3576 gate: no bare references/ cites across shipped trees', () => { + const offenders = []; + for (const { rel, abs } of walkShippedMarkdown()) { + // allow-test-rule: source-text-is-the-product (#3576) — shipped text is the runtime contract + const text = fs.readFileSync(abs, 'utf-8'); + for (const cite of findBareCites(text)) { + offenders.push(`${rel}: \`${cite}\` — bare cite resolves from no install location; use \`gsd-core/${cite}\``); + } + } + assert.deepEqual( + offenders, + [], + 'Bare `references/.md` cites are dead pointers at runtime (#3576). ' + + 'Rewrite to the canonical `gsd-core/references/.md` form:\n' + + offenders.join('\n'), + ); + }); + + test('#3576 gate: every canonical reference cite target exists on disk', () => { + const missing = []; + for (const { rel, abs } of walkShippedMarkdown()) { + // allow-test-rule: source-text-is-the-product (#3576) — shipped text is the runtime contract + const text = fs.readFileSync(abs, 'utf-8'); + for (const name of findCanonicalCites(text)) { + if (!fs.existsSync(path.join(REPO_ROOT, 'gsd-core', 'references', name))) { + missing.push(`${rel}: \`gsd-core/references/${name}\` — target does not exist`); + } + } + } + assert.deepEqual(missing, [], 'Canonical cites must name files that exist:\n' + missing.join('\n')); + }); + + test('#3576 gate unit: @~/ token stripped per-token, never line-skipped; relative and canonical forms pass', () => { + const includePlusBare = 'Read @~/gsd-core/references/tdd.md and `references/tdd.md` too'; + assert.deepEqual( + findBareCites(includePlusBare), + ['references/tdd.md'], + 'a bare cite sharing a line with an @~/ include must still be flagged (the issue-named trap)', + ); + assert.deepEqual(findBareCites('see `../references/mvp-concepts.md`'), [], 'genuinely relative href is not a bare cite'); + assert.deepEqual(findBareCites('see `gsd-core/references/tdd.md`'), [], 'canonical cite is not a bare cite'); + assert.deepEqual(findBareCites('the references/ directory'), [], 'non-backticked prose mention is not a cite'); + assert.deepEqual(findBareCites('Read @~/gsd-core/references/tdd.md now'), [], 'a lone @~/ include line is clean after stripping'); + }); +});