feat(#1169): complete ADR-857 phase 6 — migrate features to Capabilities, revive dead gates, harden conformance gate (#1183)
* test(#1168): make phase-6 gate un-gameable — reject empty stubs + require loop shrink The migration assertion previously checked only role==feature, so a registration-only stub (empty hooks, logic left inline) would turn the gate green while phase 6 stayed incomplete — the exact false-completion pattern this gate exists to prevent. Strengthen it: each ADR-named feature must OWN its behavior (>=1 hook, or a command family); and plan-phase.md/execute-phase.md must shrink strictly below their frozen pre-phase-6 sizes (94519/93166 LF bytes), which also defeats double-run gaming (declare a hook but keep the inline block -> file does not shrink -> red). Gate now 5 pass / 4 fail (orphaned execute:wave:post, empty/unregistered features, config-key leaks, no shrink). Green is now reachable only by REAL migration. Refs #1168, #1169. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(#1169): migrate gap-analysis to a Capability (plan:post gate) First real ADR-857 phase-6 migration (pattern-defining tracer). gap-analysis moves from an inline post_planning_gaps branch in plan-phase.md to a real plan:post gate Capability: - capabilities/gap-analysis/capability.json: role:feature, plan:post gate (when=workflow.post_planning_gaps, blocking:false advisory), OWNS workflow.post_planning_gaps (federated out of central schema). - plan-phase.md: inline config-get + gsd_run gap-analysis block replaced with a plan:post render-hooks call site dispatching the gate; file shrinks 94519->93279. - src/check-command-router.cts: cmdGapAnalysisPlanPost runs the real gap analysis via gap-checker. - post_planning_gaps removed from central manifest; resolves via federated config (default true preserved). - tests/post-planning-gaps-2493: re-pointed to assert capability ownership. Verified: gate 5 pass / 4 fail (gap-analysis cleared from migration, plan:post-orphan, config-leak, and plan-phase shrink checks); loadConfig still returns post_planning_gaps=true; check command runs real analysis; 392/392 in the config/registry/federation/router net. Refs #1169. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(#1169): migrate profile-pipeline to a command-family Capability ADR-857 Decision 7: profile-pipeline becomes a command-family Capability (like audit/intel/graphify). capabilities/profile-pipeline/capability.json declares an 8-command family (scan-sessions, extract-messages, profile-sample, write-profile, profile-questionnaire, generate-dev-preferences, generate-claude-profile, generate-claude-md) backed by a new gsd-core/bin/lib/profile-pipeline-command-router.cjs; the inline case arms are removed from gsd-tools.cjs. Owns profile-pipeline.enabled (federated). Verified: registry shows role:feature with commands.length=8; scan-sessions/profile-sample run live via the family; gate cleared profile-pipeline from the empty-stub failure (only tdd/schema-gate/drift remain); 296/296 registry+inventory+gsd-tools tests; lint 0 errors. Refs #1169. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#1167): wire execute:wave:post + implement ui.safety-gate check Revives the second dead gate from #1167: ui.gates@execute:wave:post was declared but never dispatched AND its check.query (ui.safety-gate) was unimplemented. Adds the per-wave execute:wave:post render-hooks call site in execute-phase.md (fires after each wave's merge/cleanup, before the next forks) and implements cmdUiSafetyGate (frontend + UI-SPEC aware, mirrors cmdUiPlanGate) in check-command-router. +17 regression tests. Verified: phase-6 orphaned-points conformance test now PASSES (gate 6 pass / 3 fail); ui-safety-gate routable in dot+hyphen forms; check-ui-safety-gate 17/17, check-ui-plan-gate 18/18; lint 0 errors. Refs #1167, #1168. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(#1169): migrate drift (schema + codebase) to execute:wave:post gates Removes the inline schema_drift_gate + codebase_drift_gate steps (77 lines) from execute-phase.md; drift becomes a Capability with two execute:wave:post gates (verify.schema-drift blocking, verify.codebase-drift advisory) dispatched via the per-wave render-hooks call site. check-command-router routes verify.schema-drift / verify.codebase-drift to the real detectors. Federates workflow.drift_threshold / drift_action / schema_drift_gate out of central. Also fixes the execute:wave:post dispatch prose to run NON-blocking (advisory) gates too — the prior version only ran blocking gates, which would have silently dropped the codebase-drift advisory after its inline step was removed. Behavior preserved. Verified: gate 7 pass / 2 fail (drift cleared from stub + config-leak; execute-phase.md 92297 < 93166 frozen -> shrink passes); both drift checks run real detection; loadConfig defaults preserved (threshold=3, action=warn, gate=true); drift-detection 56/56 + schema-drift 34/34; lint 0 errors. Refs #1169. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(#1169): migrate tdd to a Capability (plan:pre contribution + execute:post gate) tdd becomes a real Capability: a plan:pre contribution injects the <tdd_mode_active> planner guidance (rendered from PLAN_PRE_HOOKS_JSON like security's contribution), and an execute:post gate (tdd.review-checkpoint, advisory) runs the real end-of-phase RED/GREEN review via a new check-command handler. Inline tdd_mode reads + the inline planner block + the tdd_review_checkpoint step are removed; workflow.tdd_mode is federated out of central. The MVP+TDD per-task RED-commit gate is preserved — TDD_MODE is now derived from the execute:post hooks (capId==tdd active), not an inline config-get. BEHAVIOR CHANGE (documented, not silent): the --tdd CLI flag now persists workflow.tdd_mode=true via config-set instead of being per-invocation. Rationale: tdd is now a config-toggled Capability, and env vars do not persist across the workflow's separate bash blocks (config does), so an ephemeral override isn't cleanly achievable; --tdd therefore enables the tdd capability, consistent with how all capabilities are toggled. Verified: gate 7 pass / 2 fail (tdd cleared from stub + config-leak; plan-phase + execute-phase both < frozen sizes); contribution injection + execute:post gate dispatch wired; MVP+TDD gate preserved; tdd.review-checkpoint runs real review; full unit suite 556/0; lint 0 errors. Refs #1169. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(#1169): migrate schema-gate to a plan:pre contribution Capability The plan-time schema-push detection (former plan-phase.md §5.7) becomes a schema-gate Capability: a plan:pre contribution (into:planner, when:workflow.schema_push_detection) whose fragment carries the full ORM-detection + [BLOCKING] schema-push-task injection logic, rendered into the planner via the existing plan:pre render-hooks dispatch. The inline §5.7 block is removed (plan-phase.md 94519->90445). workflow.schema_push_detection is a new capability-owned (federated) key, default true. (The execute-side schema-drift gate was migrated separately into the drift capability.) Verified: registry inlines the fragment (len 2704) so it is actually delivered at plan:pre; gate 8 pass / 1 fail — all 5 ADR-named features now real Capabilities, only the config-leak test remains (intel/security, next unit). Refs #1169. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(#1169): close the 3 capability config-key leaks — phase-6 gate now GREEN Removes the last inline config-get reads of capability-owned keys from plan-phase.md. security_asvs_level/security_block_on now flow through the security plan:pre contribution via a new loop-resolver configValues mechanism (resolves declared config keys with the same 4-level precedence as activation and attaches them to the rendered hook); the §5.55 banner reads them from PLAN_PRE_HOOKS_JSON. intel.enabled becomes a real intel plan:pre step (ref.command: intel api-surface) dispatched via render-hooks; the inline intel branch is gone. gen-capability-registry now validates ref.command as a third dispatch shape. Verified: phase-6 capstone conformance gate is FULLY GREEN (9/0); 3 leaks gone (grep=0); security configValues resolve to {2,medium}/default {1,high}; intel step present only when enabled; loop-render-hooks 62/0, capability-registry 287/0, capability-state/federated-config 113/0; lint 0 errors. Closes the migration half of #1169. Refs #1139, #1167, #1168. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#1169): address adversarial review — restore schema-drift block, generic planner injection, uniform gate contract Adversarial review caught 2 real regressions the green gate missed: (1) schema-drift no longer blocked — the execute:wave:post dispatch read GATE_RESULT.block but verify.schema-drift emitted drift_detected/blocking, and onError:skip wrongly bypassed positive blocks; (2) only tdd's plan:pre contribution was injected into the planner, dropping schema-gate's schema-push detection and security's threat-model guidance. Fixes: (A) every gate check returns a uniform boolean 'block' under --raw (the dispatch form), with advisory gates (tdd/gap) carrying their report in 'message'; (B) gate-dispatch contract corrected at all sites — onError governs command errors only, a blocking gate's positive block always halts; (C) generic planner injection of all plan:pre contributions where into=='planner' (tdd + schema-gate + security incl configValues); (D) two new conformance assertions: planner contributions injected generically + every gate check.query returns boolean block under --raw. Verified: gate 11/11; all 6 gate checks return boolean block under --raw; full suite 595/0; lint 0 errors. Refs #1167, #1168, #1169. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#1169): restore MVP+TDD end-of-phase blocking escalation (2nd adversarial pass) The migrated tdd execute:post gate is statically blocking:false, but the contract (references/execute-mvp-tdd.md + CONTEXT.md) requires the end-of-phase TDD review to ESCALATE from advisory to blocking when MVP_MODE && TDD_MODE && a TDD plan misses a RED/GREEN commit. The migration prose had downgraded this to a 'strong advisory recommendation' — silent loss of the blocking escalation. Restore it: the tdd-gate dispatch now refuses to mark the phase complete (Phase blocked message) under MVP+TDD when GATE_RESULT.block is true; advisory otherwise. Also strengthen tests/execute-mvp-tdd-gate.test.cjs: hasBlockingEscalation previously matched any line with 'blocking'+'mvp+tdd' (so 'advisory (blocking: false) ... under MVP+TDD' was a false green); now it requires the real refusal semantics ('refuse to mark the phase complete' / 'phase blocked'). Caught by 2nd adversarial review pass. Verified: execute-phase.md 92702 < 93166 frozen; mvp-tdd-gate + phase-6 gate 19/0; full suite green; lint 0 errors. Refs #1169. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#1169): restore MVP+TDD proceed-block, codebase auto-remap, schema skip-flag (3rd adversarial pass) 3rd adversarial pass found 4 more silent regressions: (1) the tdd MVP+TDD 'refuse to mark complete' was nullified by a downstream 'ALWAYS proceed regardless of gate results' line — proceed is now conditional (stops on an active MVP+TDD block); (2) the test now asserts the proceed is NOT an unconditional override; (3) codebase-drift auto-remap (spawn gsd-codebase-mapper when drift_action=auto-remap) was dropped — the execute:wave:post advisory dispatch now consumes spawn_mapper/directive; (4) GSD_SKIP_SCHEMA_CHECK bypass was lost from the gate path — cmdVerifySchemaDrift now honors the env var (block:false when set). Verified: no unconditional proceed; GSD_SKIP_SCHEMA_CHECK=true -> block:false; gate 11/11 + mvp-tdd 9/9; full suite 569/0; lint 0; execute-phase.md 93109 < 93166. Refs #1169. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#1169): init.cts reads federated config keys from nested path (4th adversarial pass) Config federation moved tdd_mode/research/nyquist_validation from flat config.<key> to nested config.workflow.<key>, but src/init.cts still read them flat — so init.plan-phase/init.execute-phase emitted tdd_mode:false / research_enabled:undefined / nyquist:undefined regardless of config (a public command-contract regression; the migrated loops use render-hooks so enforcement was unaffected). Read via config.workflow (type-safe Record cast). Now init reflects the same resolved values + federated defaults (research/nyquist default true) as the render-hooks path. Verified: build clean; init.plan-phase emits tdd_mode:true/research:false/nyquist:false for set config, defaults true for empty; full suite 591/0; lint 0. Refs #1169. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(#1169): add changeset for ADR-857 phase-6 completion (PR #1183) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#1169): complete phase-6 migration fallout — restore TEXT_MODE, fix registry .claude leak, re-point stale workflow-contract tests The capability migration left real regressions and stale consumer tests that the per-module unit suite missed but the full cross-platform suite caught (27 failing tests): Real source regressions (fixed): - execute-phase.md lost its AskUserQuestion TEXT_MODE plain-text fallback when the inline schema_drift_gate step was removed — non-Claude runtimes would stall. Restored, and the execute:post gate-dispatch prose de-duplicated to cite the execute:wave:post contract (loop body shrinks below the frozen pre-phase-6 ceiling while keeping every onError/blocking nuance). - capabilities/tdd inline fragment hardcoded `@~/.claude/gsd-core/references/tdd.md`, baked verbatim into the committed capability-registry.cjs and leaked the install path on 11 non-Claude runtimes (registry .cjs is copied, not path-converted). Made the fragment path-free; regenerated the registry. The phase-6 conformance gate now guards this (no ~/.claude install path in any capability source or the generated registry). - plan-phase.md: removed a §5.7 stub re-added in error and routed Branch 2 to step 6 (schema-gate is a plan:pre capability, §5.7 is gone). Stale workflow-contract tests re-pointed to the capability dispatch they now must assert (behavior verified preserved in source first, assertions kept equal-or-stronger): bug-621 + bug-2851 (gap-analysis via gsd_run render-hooks plan:post + registry binding), feat-2527 (tdd_mode federated out of central), phase6-planning + plan-phase-ui-redirect (§5.6 bounded by ## 6.), plan-phase-drift-guard (intel when:intel.enabled skip branch). profile-pipeline-command-router.cjs un-ignored from eslint (hand-written, no TS source) + stale disable comments removed. Size baseline regenerated. Verified: full suite 15140 tests / 0 fail; lint 0 errors; conformance gate green legitimately. Refs #1139, #1167, #1168, #1169. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(#1169): add ADR-857 E2E content-test coverage for the 12 loop points + capability deliverables Grounds the capability engine in behavioral E2E tests (drive the real render-hooks/check CLI + the real registry, assert typed result content — no source-grep), structured around what ADR-857 says to deliver. 207 tests; each genuineness-checked (flip the expectation, confirm it fails). Per-loop-point dispatch (7 files): empty-point negative-space across the 6 no-hook points; verify:post 3-step resolution+ordering+onError; plan:pre contribution/configValues + ui.plan-gate + intel; plan:post gap-analysis; execute:wave:post drift+ui gates via the check route (schema-drift block/skip, codebase-drift threshold BVA, auto-remap); execute:post tdd.review-checkpoint RED/GREEN; ship:pre security gate resolution + frontmatter-get predicate pieces. ADR-deliverable coverage (4 files): predicate boundary held (edge/prohibition probes stay core, not off-by-default Feature Capabilities — phase-6 exception); core loop runs with zero capabilities (all 12 points empty, init bundles resolve); contribution merge (multiple ordered <contribution from=> blocks); federated-config key removal on uninstall. federated-config allowlisted for its 3-file split (unit + integration + lifecycle). Refs #1139, #1167, #1168, #1169. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#1169): remove dead drifted converter dups + address adversarial review Lint cleanup (root-caused, not waved off): src/runtime-artifact-conversion.cts carried 11 agent-converter functions (+5 orphaned consts/helpers) that were never exported, never called, and had silently DRIFTED from the live hand-authored copies in bin/install.js (one even referenced an undefined `claudeToCopilotTools`). Deleted the dead duplicates; install.js's live copies are untouched (it never imported these). Lint now 0 errors / 0 warnings. Adversarial-review (Codex) findings fixed: - HIGH: execute-phase.md TDD_MODE used `jq ... || echo false`, silently disabling the MVP+TDD blocking gate on jq-less runtimes. Reverted to the `node -e` form (node is guaranteed; matches the file's other node-e usages) so a missing optional tool can no longer fail-open a blocking safety path. - MEDIUM: federated-config-key-removal orphan-key test was vacuous (it skipped the orphan assertion). Now asserts the removed capability's key is genuinely not surfaced/validated after uninstall. - LOW: phase-6 conformance leak regex broadened to catch absolute-home and Windows-backslash `.claude/(gsd-core|commands|agents|hooks)` paths, not only `~`/`$HOME` forward-slash forms. - LOW: bug-2851 plan:post dispatch assertion now requires `--raw` (matched its stated contract). - nit: plan-pre intel-step test duplicate assertion replaced with a distinct structured-output check. Size baseline regenerated (execute-phase.md 93089 < 93166 frozen). Refs #1167, #1169. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(#1169): make runtime-homes-descriptor-drive titles environment-independent The descriptor-equivalence test embedded the absolute golden config path (`os.homedir()`-derived) directly in each `test(...)` title, so titles differed between macOS (`/Users/x/.claude`) and Docker (`/home/gsdtest/.claude`). Every test PASSES on both platforms (15885/0 leaf tests each), but gsd-test-summary compares results by title and reported 29+29 false "only in Mac / only in Docker" discrepancies for tests that actually pass everywhere. Move the golden path out of the title and into the assertion message (still shown on failure); titles are now byte-identical across platforms so the cross-platform comparator matches them. No assertion logic or golden values changed. Refs #1169. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(#1169): derive TDD_MODE via gsd_run --active-cap, not node -e (fix prompt-injection CI gate) The prior fix reverted execute-phase.md:181 from jq to `node -e` to close a Codex HIGH (jq||echo-false silently disabling the MVP+TDD blocking gate on jq-less runtimes) — but the CI prompt-injection scanner BLOCKS new `node -e` in workflow markdown (inline code-exec = injection vector), turning the security gate red. Both forms were wrong: node -e fails the scanner; jq fail-opens a blocking safety gate; `config-get workflow.tdd_mode` is forbidden by the conformance leak gate (tdd_mode is capability-owned). Correct fix (what Codex recommended): a gsd_run-native boolean. Add an `--active-cap <capId>` flag to `loop render-hooks <point>` that resolves hooks the normal way and prints exactly `true`/`false` for whether a capId is active — scanner-safe (canonical launcher, no inline code), node-reliable (no optional jq to fail-open), and leak-free (render-hooks resolution, not config-get). execute-phase.md:181 now `TDD_MODE=$(gsd_run loop render-hooks execute:post --active-cap tdd)`. +5 behavioral tests for the flag. Verified: prompt-injection-scan --diff origin/next → 0 findings; conformance gate 13/13 (execute-phase.md 92934 < 93166); execute-mvp-tdd + tdd-mode + loop-render-hooks 87/0; lint 0/0. Refs #1167, #1169. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -177,7 +177,8 @@ Resolve `MVP_MODE` once via the centralized `phase.mvp-mode` query verb (precede
|
||||
MVP_FLAG_ARG=""
|
||||
if [[ "$ARGUMENTS" =~ (^|[[:space:]])--mvp([[:space:]]|$) ]]; then MVP_FLAG_ARG="--cli-flag"; fi
|
||||
MVP_MODE=$(gsd_run query phase.mvp-mode "${PHASE_NUMBER}" $MVP_FLAG_ARG --pick active)
|
||||
TDD_MODE=$(gsd_run query config-get workflow.tdd_mode 2>/dev/null || echo "false")
|
||||
EXECUTE_POST_HOOKS_JSON=$(gsd_run loop render-hooks execute:post --raw)
|
||||
TDD_MODE=$(gsd_run loop render-hooks execute:post --active-cap tdd)
|
||||
```
|
||||
|
||||
<step name="safe_resume_gate">
|
||||
@@ -898,6 +899,51 @@ increases monotonically across waves. `{status}` is `complete` (success),
|
||||
|
||||
**If no plan in this wave used worktrees** (project-level `USE_WORKTREES=false` OR `WAVE_WORKTREE_PLANS` is empty): sequential agents already updated STATE.md and ROADMAP.md themselves — skip this step.
|
||||
|
||||
5.75. **Execute:wave:post capability dispatch:**
|
||||
|
||||
After worktree merge, post-merge tests, and tracking updates, dispatch capability hooks registered at `execute:wave:post`. The primary hook is the `ui.safety-gate` gate from the UI capability — it verifies that any frontend files changed in this wave conform to the UI-SPEC contract.
|
||||
|
||||
```bash
|
||||
WAVE_POST_HOOKS_JSON=$(gsd_run loop render-hooks execute:wave:post --raw)
|
||||
```
|
||||
|
||||
Read the `activeHooks` array from `WAVE_POST_HOOKS_JSON` in-context (do NOT pipe through a shell parser).
|
||||
|
||||
**If `activeHooks` is empty or absent:** Skip silently to step 5.8.
|
||||
|
||||
**For each active entry where `kind == "gate"`** (process in array order), run the gate check:
|
||||
|
||||
```bash
|
||||
GATE_RESULT=$(gsd_run check ${hook.check.query} "${PHASE_NUMBER}" --raw)
|
||||
CHECK_EXIT=$?
|
||||
```
|
||||
|
||||
**Step 1 — did the CHECK COMMAND itself succeed?**
|
||||
|
||||
If the check command failed (non-zero `CHECK_EXIT`, empty output, or unparseable JSON):
|
||||
- `onError == "halt"` → treat as a fatal error: stop wave completion, do NOT proceed to step 5.8, and surface: `⚠ Gate check command failed ({hook.capId}): command error. Resolve before continuing.`
|
||||
- `onError == "skip"` → log a warning and continue to the next hook. Do NOT read `GATE_RESULT.block`.
|
||||
|
||||
**Step 2 — read `GATE_RESULT.block` (boolean).** This step is only reached when the command succeeded.
|
||||
|
||||
- **Blocking gate (`hook.blocking == true`) AND `GATE_RESULT.block == true`:** HALT — stop wave completion, do NOT proceed to step 5.8, and present:
|
||||
|
||||
```
|
||||
⚠ Wave {N} blocked by capability gate ({hook.capId}): {GATE_RESULT.message}
|
||||
Resolve before continuing to next wave.
|
||||
```
|
||||
|
||||
This halt is **not** bypassed by `onError` — `onError` only covers command errors (step 1 above), not the gate's block decision.
|
||||
|
||||
- **Non-blocking gate (`hook.blocking == false`):** never halts. If `GATE_RESULT.block` is `true` (or non-empty `message`), print `⚠ {hook.capId} advisory (wave {N}): {GATE_RESULT.message}`, then:
|
||||
- If `GATE_RESULT.spawn_mapper == true` OR `GATE_RESULT.directive == "auto-remap"`: spawn `gsd-codebase-mapper` per `execute-phase/steps/codebase-drift-gate.md`; pass `--paths {GATE_RESULT.affected_paths}`. Continue regardless (wave NOT failed by remap failure).
|
||||
- Otherwise: continue after advisory.
|
||||
- If block `false` and no `message`: continue silently.
|
||||
|
||||
- **Blocking gate (`hook.blocking == true`) AND `GATE_RESULT.block == false`:** continue silently.
|
||||
|
||||
**When all active gates are processed without a blocking halt:** continue to step 5.8.
|
||||
|
||||
5.8. **Handle test gate failures (when `WAVE_FAILURE_COUNT > 0`):**
|
||||
|
||||
```
|
||||
@@ -1083,59 +1129,6 @@ If an active secure-phase step hook exists AND SECURITY.md exists: check frontma
|
||||
```
|
||||
</step>
|
||||
|
||||
<step name="tdd_review_checkpoint">
|
||||
**Optional step — TDD collaborative review.**
|
||||
|
||||
```bash
|
||||
TDD_MODE=$(gsd_run query config-get workflow.tdd_mode 2>/dev/null || echo "false")
|
||||
```
|
||||
|
||||
**Skip if `TDD_MODE` is `false`.**
|
||||
|
||||
When `TDD_MODE` is `true`, check whether any completed plans in this phase have `type: tdd` in their frontmatter:
|
||||
|
||||
```bash
|
||||
TDD_PLANS=$(grep -rl "^type: tdd" "${PHASE_DIR}"/*-PLAN.md 2>/dev/null | wc -l | tr -d ' ')
|
||||
```
|
||||
|
||||
**If `TDD_PLANS` > 0:** Insert end-of-phase collaborative review checkpoint.
|
||||
|
||||
1. Collect all SUMMARY.md files for TDD plans
|
||||
2. For each TDD plan summary, verify the RED/GREEN/REFACTOR gate sequence:
|
||||
- RED gate: A failing test commit exists (`test(...)` commit with MUST-fail evidence)
|
||||
- GREEN gate: An implementation commit exists (`feat(...)` commit making tests pass)
|
||||
- REFACTOR gate: Optional cleanup commit (`refactor(...)` commit, tests still pass)
|
||||
3. If any TDD plan is missing the RED or GREEN gate commits, flag it:
|
||||
```
|
||||
⚠ TDD gate violation: Plan {plan_id} missing {RED|GREEN} phase commit.
|
||||
Expected commit pattern: test({phase}-{plan}): ... → feat({phase}-{plan}): ...
|
||||
```
|
||||
4. Present collaborative review summary:
|
||||
```
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
TDD REVIEW — Phase {X}
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
TDD Plans: {TDD_PLANS} | Gate violations: {count}
|
||||
|
||||
| Plan | RED | GREEN | REFACTOR | Status |
|
||||
|------|-----|-------|----------|--------|
|
||||
| {id} | ✓ | ✓ | ✓ | Pass |
|
||||
| {id} | ✓ | ✗ | — | FAIL |
|
||||
```
|
||||
|
||||
**Escalation under MVP+TDD.** When `MVP_MODE=true` AND `TDD_MODE=true`, the review verdict escalates from advisory to **blocking**: missing RED or GREEN gate commits prevent marking the phase complete.
|
||||
```text
|
||||
Phase blocked: {N} TDD plan(s) violate the RED→GREEN gate sequence under MVP+TDD.
|
||||
Resolve and re-run /gsd execute-phase, or override with
|
||||
/gsd execute-phase {phase} --force-mvp-gate to ship anyway.
|
||||
```
|
||||
`--force-mvp-gate` is the escape hatch (documented, not yet implemented). Policy is:
|
||||
- `MVP_MODE=true` AND `TDD_MODE=true`: violations are **blocking** unless explicitly overridden.
|
||||
- otherwise: violations are advisory/non-blocking and are surfaced for review.
|
||||
The verifier agent (step `verify_phase_goal`) still checks TDD discipline in both cases.
|
||||
</step>
|
||||
|
||||
<step name="handle_partial_wave_execution">
|
||||
If `WAVE_FILTER` was used, re-run plan discovery after execution:
|
||||
|
||||
@@ -1168,16 +1161,16 @@ Selected wave finished successfully. This phase still has incomplete plans, so p
|
||||
</step>
|
||||
|
||||
<step name="code_review_gate" required="true">
|
||||
**This step is REQUIRED to evaluate the capability hook.** When the code-review capability is active, auto-invoke code review on the phase's source changes. Advisory only — never blocks execution flow.
|
||||
**This step is REQUIRED to evaluate the capability hook.** When the code-review capability is active, auto-invoke code review on the phase's source changes. Advisory only — never blocks execution flow. Also dispatches advisory execute:post gate hooks (e.g. tdd.review-checkpoint).
|
||||
|
||||
**Capability gate:**
|
||||
```bash
|
||||
EXECUTE_POST_HOOKS_JSON=$(gsd_run loop render-hooks execute:post --raw)
|
||||
EXECUTE_POST_HOOKS_JSON=${EXECUTE_POST_HOOKS_JSON:-$(gsd_run loop render-hooks execute:post --raw)}
|
||||
```
|
||||
|
||||
Resolve active step hooks from `EXECUTE_POST_HOOKS_JSON` where `kind == "step"` and `ref.skill == "code-review"`.
|
||||
|
||||
If no active code-review step hook exists: display "Code review skipped (code-review capability inactive)" and proceed to next step.
|
||||
If no active code-review step hook exists: display "Code review skipped (code-review capability inactive)" and proceed to gate dispatch.
|
||||
|
||||
**Invoke review:**
|
||||
```
|
||||
@@ -1197,9 +1190,28 @@ Code review found issues. Consider running:
|
||||
/gsd:code-review ${PHASE_NUMBER} --fix
|
||||
```
|
||||
|
||||
**Error handling:** If the Skill invocation fails or throws, catch the error, display "Code review encountered an error (non-blocking): {error}" and proceed to next step. Review failures must never block execution.
|
||||
**Error handling:** If the Skill invocation fails or throws, catch the error, display "Code review encountered an error (non-blocking): {error}" and proceed to gate dispatch. Review failures must never block execution.
|
||||
|
||||
Regardless of review result, ALWAYS proceed to close_parent_artifacts → regression_gate → verify_phase_goal.
|
||||
**Execute:post gate hook dispatch.** After code review, dispatch all active gate hooks from `EXECUTE_POST_HOOKS_JSON` where `kind == "gate"`:
|
||||
|
||||
For each active gate hook:
|
||||
```bash
|
||||
GATE_RESULT=$(gsd_run check ${hook.check.query} "${PHASE_NUMBER}" --raw)
|
||||
CHECK_EXIT=$?
|
||||
```
|
||||
|
||||
**Gate evaluation** uses the same two-step contract as `execute:wave:post` above: **Step 1** — if the check command failed (non-zero `CHECK_EXIT`, empty/unparseable output), `onError == "halt"` stops and surfaces the error, `onError == "skip"` warns and continues to the next hook (do not read `block`). **Step 2** (command succeeded) — a blocking gate (`hook.blocking == true`) halts on `GATE_RESULT.block == true` with its message/table (never bypassed by `onError`); an advisory gate (`hook.blocking == false`) shows its `table`/summary when `block == true` or `message` is non-empty, then continues; a blocking gate with `block == false` continues silently.
|
||||
|
||||
**TDD review escalation (overrides the advisory default for the `tdd.review-checkpoint` gate only).** The tdd `execute:post` gate is declared `blocking: false`, so by the generic contract above it displays its `message`/table and continues. There is ONE documented exception (see `~/.claude/gsd-core/references/execute-mvp-tdd.md`): when `MVP_MODE=true` AND `TDD_MODE=true` AND `GATE_RESULT.block == true` (one or more TDD plans miss a RED or GREEN gate commit), the end-of-phase TDD review escalates from advisory to **blocking under MVP+TDD** — refuse to mark the phase complete and present:
|
||||
|
||||
```
|
||||
Phase blocked: {N} TDD plan(s) violate the RED→GREEN gate sequence under MVP+TDD.
|
||||
Resolve and re-run /gsd execute-phase, or override with /gsd execute-phase {phase} --force-mvp-gate to ship anyway.
|
||||
```
|
||||
|
||||
(`--force-mvp-gate` is the documented, not-yet-implemented escape hatch.) Outside MVP+TDD, TDD-review violations remain advisory (table shown, execution continues).
|
||||
|
||||
**Proceed rule:** If `MVP_MODE && TDD_MODE && GATE_RESULT.block == true` for `tdd.review-checkpoint`: STOP — do NOT proceed to `close_parent_artifacts`, `regression_gate`, `verify_phase_goal`, or `phase.complete`. Otherwise proceed normally.
|
||||
</step>
|
||||
|
||||
<step name="close_parent_artifacts">
|
||||
@@ -1322,84 +1334,7 @@ Options:
|
||||
3. Abort phase — roll back and re-plan
|
||||
```
|
||||
|
||||
Use AskUserQuestion to present the options.
|
||||
</step>
|
||||
|
||||
<step name="schema_drift_gate">
|
||||
Post-execution schema drift detection. Catches false-positive verification where
|
||||
build/types pass because TypeScript types come from config, not the live database.
|
||||
|
||||
**Run after execution completes but BEFORE verification marks success.**
|
||||
|
||||
```bash
|
||||
SCHEMA_DRIFT=$(gsd_run query verify.schema-drift "${PHASE_NUMBER}" 2>/dev/null)
|
||||
```
|
||||
|
||||
Parse JSON result for: `drift_detected`, `blocking`, `schema_files`, `orms`, `unpushed_orms`, `message`.
|
||||
|
||||
**If `drift_detected` is false:** Skip to verify_phase_goal.
|
||||
|
||||
**If `drift_detected` is true AND `blocking` is true:**
|
||||
|
||||
Check for override:
|
||||
```bash
|
||||
SKIP_SCHEMA=$(echo "${GSD_SKIP_SCHEMA_CHECK:-false}")
|
||||
```
|
||||
|
||||
**If `SKIP_SCHEMA` is `true`:**
|
||||
|
||||
Display:
|
||||
```
|
||||
⚠ Schema drift detected but GSD_SKIP_SCHEMA_CHECK=true — bypassing gate.
|
||||
|
||||
Schema files changed: {schema_files}
|
||||
ORMs requiring push: {unpushed_orms}
|
||||
|
||||
Proceeding to verification (database may be out of sync).
|
||||
```
|
||||
→ Continue to verify_phase_goal.
|
||||
|
||||
**If `SKIP_SCHEMA` is not `true`:**
|
||||
|
||||
BLOCK verification. Display:
|
||||
|
||||
```
|
||||
## BLOCKED: Schema Drift Detected
|
||||
|
||||
Schema-relevant files changed during this phase but no database push command
|
||||
was executed. Build and type checks pass because TypeScript types come from
|
||||
config, not the live database — verification would produce a false positive.
|
||||
|
||||
Schema files changed: {schema_files}
|
||||
ORMs requiring push: {unpushed_orms}
|
||||
|
||||
Required push commands:
|
||||
{For each unpushed ORM, show the push command from the message}
|
||||
|
||||
Options:
|
||||
1. Run push command now (recommended) — execute the push, then re-verify
|
||||
2. Skip schema check (GSD_SKIP_SCHEMA_CHECK=true) — bypass this gate
|
||||
3. Abort — stop execution and investigate
|
||||
```
|
||||
|
||||
If `TEXT_MODE` is true, present as a plain-text numbered list. Otherwise use AskUserQuestion.
|
||||
|
||||
**If user selects option 1:** Present the specific push command(s) to run. After user confirms execution, re-run the schema drift check. If it passes, continue to verify_phase_goal.
|
||||
|
||||
**If user selects option 2:** Set override and continue to verify_phase_goal.
|
||||
|
||||
**If user selects option 3:** Stop execution. Report partial completion.
|
||||
</step>
|
||||
|
||||
<step name="codebase_drift_gate">
|
||||
Post-execution structural drift detection (#2003). Non-blocking by contract:
|
||||
any internal error here MUST fall through to `verify_phase_goal`. The phase
|
||||
is never failed by this gate.
|
||||
|
||||
Load and follow the full step spec from
|
||||
`gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md` —
|
||||
covers the SDK call, JSON contract, `warn` vs `auto-remap` branches, mapper
|
||||
spawn template, and the two `workflow.drift_*` config keys.
|
||||
If `TEXT_MODE` is true, present as a plain-text numbered list and ask the user to type their choice number. Otherwise, use AskUserQuestion to present the options.
|
||||
</step>
|
||||
|
||||
<step name="verify_phase_goal">
|
||||
|
||||
@@ -74,11 +74,10 @@ AGENT_SKILLS_RESEARCHER=$(gsd_run query agent-skills gsd-phase-researcher)
|
||||
AGENT_SKILLS_PLANNER=$(gsd_run query agent-skills gsd-planner)
|
||||
AGENT_SKILLS_CHECKER=$(gsd_run query agent-skills gsd-plan-checker)
|
||||
CONTEXT_WINDOW=$(gsd_run query config-get context_window 2>/dev/null || echo "200000")
|
||||
TDD_MODE=$(gsd_run query config-get workflow.tdd_mode 2>/dev/null || echo "false")
|
||||
MVP_MODE_CFG=$(gsd_run query config-get workflow.mvp_mode 2>/dev/null || echo "false")
|
||||
```
|
||||
|
||||
When `TDD_MODE` is `true`, the planner agent is instructed to apply `type: tdd` to eligible tasks using heuristics from `references/tdd.md`. The planner's `<required_reading>` is extended to include `@~/.claude/gsd-core/references/tdd.md` so gate enforcement rules are available during planning.
|
||||
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 `CONTEXT_WINDOW >= 500000`, the planner prompt includes the 3 most recent prior phase CONTEXT.md and SUMMARY.md files PLUS any phases explicitly listed in the current phase's `Depends on:` field in ROADMAP.md. Explicit dependencies always load regardless of recency (e.g., Phase 7 declaring `Depends on: Phase 2` always sees Phase 2's context). Bounded recency keeps the planner's context budget focused on recent work.
|
||||
|
||||
@@ -165,7 +164,9 @@ Set `TEXT_MODE=true` if `--text` is present in $ARGUMENTS OR `text_mode` from in
|
||||
```bash
|
||||
MVP_FLAG_ARG=""
|
||||
if [[ "$ARGUMENTS" =~ (^|[[:space:]])--mvp([[:space:]]|$) ]]; then MVP_FLAG_ARG="--cli-flag"; fi
|
||||
if [[ "$ARGUMENTS" =~ (^|[[:space:]])--tdd([[:space:]]|$) ]]; then TDD_MODE=true; fi
|
||||
if [[ "$ARGUMENTS" =~ (^|[[:space:]])--tdd([[:space:]]|$) ]]; then
|
||||
gsd_run query config-set workflow.tdd_mode true 2>/dev/null || true
|
||||
fi
|
||||
```
|
||||
|
||||
Defer the `phase.mvp-mode` query until `PHASE` is finalized (after explicit argument parsing/fallback phase detection + validation). The verb returns `true|false`; full result also exposes `source` (`cli_flag` | `roadmap` | `config` | `none`) for diagnostics. Mode is **all-or-nothing per phase** (PRD decision Q1).
|
||||
@@ -581,15 +582,15 @@ test -f "${PHASE_DIR}/${PADDED_PHASE}-VALIDATION.md" && echo "VALIDATION_CREATED
|
||||
|
||||
```bash
|
||||
PLAN_PRE_HOOKS_JSON=$(gsd_run loop render-hooks plan:pre --raw)
|
||||
SECURITY_ASVS=$(gsd_run query config-get workflow.security_asvs_level --raw 2>/dev/null || echo "1")
|
||||
SECURITY_BLOCK=$(gsd_run query config-get workflow.security_block_on --raw 2>/dev/null || echo "high")
|
||||
```
|
||||
|
||||
Resolve active contribution hooks from `PLAN_PRE_HOOKS_JSON` where `kind == "contribution"` and `capId == "security"`.
|
||||
|
||||
**If no active security contribution hook exists:** Skip to step 5.6.
|
||||
|
||||
**If an active security contribution hook exists:** Display banner:
|
||||
**If an active security contribution hook exists:** Read `SECURITY_ASVS` from the active hook's `configValues.security_asvs_level` (default: `1`) and `SECURITY_BLOCK` from `configValues.security_block_on` (default: `"high"`). These values are resolved by the capability registry from user config using the same four-level precedence as hook activation — no inline `config-get` is needed.
|
||||
|
||||
Display banner:
|
||||
|
||||
```
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
@@ -635,7 +636,7 @@ GATE=$(gsd_run check ui-plan-gate "${PHASE}" --raw)
|
||||
|
||||
Read `frontend`, `hasUiSpec`, and `block` from `GATE`.
|
||||
|
||||
**Branch 2 — no frontend indicators (`frontend` is `false`):** Skip silently to step 5.7.
|
||||
**Branch 2 — no frontend indicators (`frontend` is `false`):** Skip silently to step 6.
|
||||
|
||||
**Branch 3 — UI-SPEC already exists (`hasUiSpec` is `true`):**
|
||||
|
||||
@@ -686,68 +687,6 @@ Also available:
|
||||
|
||||
**Exit the plan-phase workflow. Do not continue.**
|
||||
|
||||
## 5.7. Schema Push Detection Gate
|
||||
|
||||
> Detects schema-relevant files in the phase scope and injects a mandatory `[BLOCKING]` schema push task into the plan. Prevents false-positive verification where build/types pass because TypeScript types come from config, not the live database.
|
||||
|
||||
Check if any files in the phase scope match schema patterns:
|
||||
|
||||
```bash
|
||||
PHASE_SECTION=$(gsd_run query roadmap.get-phase "${PHASE}" --pick section 2>/dev/null)
|
||||
```
|
||||
|
||||
Scan `PHASE_SECTION`, `CONTEXT.md` (if loaded), and `RESEARCH.md` (if exists) for file paths matching these ORM patterns:
|
||||
|
||||
| ORM | File Patterns |
|
||||
|-----|--------------|
|
||||
| Payload CMS | `src/collections/**/*.ts`, `src/globals/**/*.ts` |
|
||||
| Prisma | `prisma/schema.prisma`, `prisma/schema/*.prisma` |
|
||||
| Drizzle | `drizzle/schema.ts`, `src/db/schema.ts`, `drizzle/*.ts` |
|
||||
| Supabase | `supabase/migrations/*.sql` |
|
||||
| TypeORM | `src/entities/**/*.ts`, `src/migrations/**/*.ts` |
|
||||
|
||||
Also check if any existing PLAN.md files for this phase already reference these file patterns in `files_modified`.
|
||||
|
||||
**If schema-relevant files detected:**
|
||||
|
||||
Set `SCHEMA_PUSH_REQUIRED=true` and `SCHEMA_ORM={detected_orm}`.
|
||||
|
||||
Determine the push command for the detected ORM:
|
||||
|
||||
| ORM | Push Command | Non-TTY Workaround |
|
||||
|-----|-------------|-------------------|
|
||||
| Payload CMS | `npx payload migrate` | `CI=true PAYLOAD_MIGRATING=true npx payload migrate` |
|
||||
| Prisma | `npx prisma db push` | `npx prisma db push --accept-data-loss` (if destructive) |
|
||||
| Drizzle | `npx drizzle-kit push` | `npx drizzle-kit push` |
|
||||
| Supabase | `supabase db push` | Set `SUPABASE_ACCESS_TOKEN` env var |
|
||||
| TypeORM | `npx typeorm migration:run` | `npx typeorm migration:run -d src/data-source.ts` |
|
||||
|
||||
Inject the following into the planner prompt (step 8) as an additional constraint:
|
||||
|
||||
```markdown
|
||||
<schema_push_requirement>
|
||||
**[BLOCKING] Schema Push Required**
|
||||
|
||||
This phase modifies schema-relevant files ({detected_files}). The planner MUST include
|
||||
a `[BLOCKING]` task that runs the database schema push command AFTER all schema file
|
||||
modifications are complete but BEFORE verification.
|
||||
|
||||
- ORM detected: {SCHEMA_ORM}
|
||||
- Push command: {push_command}
|
||||
- Non-TTY workaround: {env_hint}
|
||||
- If push requires interactive prompts that cannot be suppressed, flag the task for
|
||||
manual intervention with `autonomous: false`
|
||||
|
||||
This task is mandatory — the phase CANNOT pass verification without it. Build and
|
||||
type checks will pass without the push (types come from config, not the live database),
|
||||
creating a false-positive verification state.
|
||||
</schema_push_requirement>
|
||||
```
|
||||
|
||||
Display: `Schema files detected ({SCHEMA_ORM}) — [BLOCKING] push task will be injected into plans`
|
||||
|
||||
**If no schema-relevant files detected:** Skip silently to step 6.
|
||||
|
||||
## 6. Check Existing Plans
|
||||
|
||||
```bash
|
||||
@@ -853,16 +792,21 @@ PATTERNS_PATH="${PHASE_DIR}/${PADDED_PHASE}-PATTERNS.md"
|
||||
|
||||
## 7.9. Regenerate API-SURFACE.md (intel gate)
|
||||
|
||||
> Capability-driven dispatch. Resolves active `plan:pre` step hooks via the capability registry; the intel hook's `when: intel.enabled` condition is evaluated by the registry — no inline config-get needed.
|
||||
|
||||
Read the active intel step hook from `PLAN_PRE_HOOKS_JSON` where `kind == "step"` and `capId == "intel"`.
|
||||
|
||||
**If no active intel step hook exists:** `API_SURFACE_PATH` stays empty; skip to step 8. The step-8 planner entry for API Surface is omitted when `API_SURFACE_PATH` is empty.
|
||||
|
||||
**If an active intel step hook exists:**
|
||||
```bash
|
||||
INTEL_CFG=$(gsd_run query config-get intel.enabled 2>/dev/null || echo "false")
|
||||
# false (absent = false) → API_SURFACE_PATH stays empty; step-8 planner entry omitted
|
||||
if [ "$INTEL_CFG" = "true" ]; then
|
||||
gsd_run intel api-surface
|
||||
API_SURFACE_PATH=".planning/intel/API-SURFACE.md"
|
||||
echo "✓ API surface regenerated: ${API_SURFACE_PATH}" # injected into step 8 as HINT
|
||||
fi
|
||||
gsd_run intel api-surface
|
||||
API_SURFACE_PATH=".planning/intel/API-SURFACE.md"
|
||||
echo "✓ API surface regenerated: ${API_SURFACE_PATH}" # injected into step 8 as HINT
|
||||
```
|
||||
|
||||
Continue to step 8.
|
||||
|
||||
## 8. Spawn gsd-planner Agent
|
||||
|
||||
Display banner:
|
||||
@@ -896,7 +840,7 @@ Planner prompt:
|
||||
- {SPEC_PATH} (Phase SPEC — carries the ## Edge Coverage section to lift covered/backstop edges from, if exists)
|
||||
- {SPIKE_FINDINGS_PATH} (Spike Findings — validated patterns, constraints, landmines from experiments, if exists)
|
||||
- {SKETCH_FINDINGS_PATH} (Sketch Findings — validated design decisions, CSS patterns, visual direction, if exists)
|
||||
- {API_SURFACE_PATH} (API Surface — HINT ONLY, if intel.enabled; see <intel_surface_hint> below)
|
||||
- {API_SURFACE_PATH} (API Surface — HINT ONLY, when intel capability is active; see <intel_surface_hint> below)
|
||||
${CONTEXT_WINDOW >= 500000 ? `
|
||||
**Cross-phase context (1M model enrichment):**
|
||||
- CONTEXT.md files from the 3 most recent completed phases (locked decisions — maintain consistency)
|
||||
@@ -928,16 +872,7 @@ Historical findings already incorporated, explicitly deferred/rejected in PLAN.m
|
||||
**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
|
||||
|
||||
${TDD_MODE === 'true' ? `
|
||||
<tdd_mode_active>
|
||||
**TDD Mode is ENABLED.** Apply TDD heuristics from @~/.claude/gsd-core/references/tdd.md to all eligible tasks:
|
||||
- Business logic with defined I/O → type: tdd
|
||||
- API endpoints with request/response contracts → type: tdd
|
||||
- Data transformations, validation, algorithms → type: tdd
|
||||
- UI, config, glue code, CRUD → standard plan (type: execute)
|
||||
Each TDD plan gets one feature with RED/GREEN/REFACTOR gate sequence.
|
||||
</tdd_mode_active>
|
||||
` : ''}
|
||||
{For each active entry in `PLAN_PRE_HOOKS_JSON` where `kind == "contribution"` and `into == "planner"` (in array order): inject the entry's `fragment.inline` verbatim here. This delivers all planner-targeted contributions — including tdd's `<tdd_mode_active>` block (type:tdd heuristics), schema-gate's schema-push detection guidance (if active at plan:pre), and security's threat-model guidance. For the security contribution, also surface the resolved `configValues`: `security_asvs_level` (ASVS enforcement level) and `security_block_on` (severity threshold) so the planner uses the configured values when generating `<threat_model>` blocks. If no active planner contributions exist, omit this block entirely.}
|
||||
|
||||
**MVP_MODE:** ${MVP_MODE} (when true, follow vertical-slice rules from `~/.claude/gsd-core/references/planner-mvp-mode.md`; when false, ignore MVP guidance entirely.)
|
||||
**WALKING_SKELETON:** ${WALKING_SKELETON} (when true, the first deliverable must be a Walking Skeleton — Read the template at `~/.claude/gsd-core/references/skeleton-template.md` and produce SKELETON.md alongside PLAN.md.)
|
||||
@@ -1630,53 +1565,38 @@ gsd_run query commit "docs(${PADDED_PHASE}): create phase plan" --files "${PHASE
|
||||
|
||||
This commits all PLAN.md files for the phase plus the updated STATE.md and ROADMAP.md to version-control the planning artifacts. Skip this step if `commit_docs` is false.
|
||||
|
||||
## 13e. Post-Planning Gap Analysis
|
||||
## 13e. Post-Planning Gap Analysis (plan:post capability gate dispatch)
|
||||
|
||||
After all plans are generated, committed, and the Requirements Coverage Gate (§13)
|
||||
has run, emit a single unified gap report covering both REQUIREMENTS.md and the
|
||||
CONTEXT.md `<decisions>` section. This is a **proactive, post-hoc report** — it
|
||||
does not block phase advancement and does not re-plan. It exists so that any
|
||||
requirement or decision that slipped through the per-plan checks is surfaced in
|
||||
one place before execution begins.
|
||||
|
||||
**Skip if:** `workflow.post_planning_gaps` is `false`. Default is `true`.
|
||||
Proactive, non-blocking coverage report gated on `workflow.post_planning_gaps`
|
||||
(default `true`). Dispatched via the `plan:post` capability gate owned by the
|
||||
`gap-analysis` capability (ADR-857 §53). Reads REQUIREMENTS.md and CONTEXT.md
|
||||
`<decisions>` and cross-references each REQ-ID / D-ID against `${PHASE_DIR}/*-PLAN.md`.
|
||||
|
||||
```bash
|
||||
POST_PLANNING_GAPS=$(gsd_run query config-get workflow.post_planning_gaps --default true 2>/dev/null || echo true)
|
||||
if [ "$POST_PLANNING_GAPS" = "true" ]; then
|
||||
# Scope to this phase's mapped REQ-IDs (#447); null/TBD skips the requirements comparison (CONTEXT.md decisions still reported), mirroring §13.
|
||||
gsd_run gap-analysis --phase-dir "${PHASE_DIR}" --phase-req-ids "$(gsd_run query init.plan-phase "$PHASE" --pick phase_req_ids 2>/dev/null || echo TBD)"
|
||||
fi
|
||||
PLAN_POST_HOOKS_JSON=$(gsd_run loop render-hooks plan:post --raw)
|
||||
PHASE_REQ_IDS=$(gsd_run query init.plan-phase "$PHASE" --pick phase_req_ids 2>/dev/null || echo TBD)
|
||||
```
|
||||
|
||||
(`gsd-tools.cjs gap-analysis` reads `.planning/REQUIREMENTS.md`, `${PHASE_DIR}/CONTEXT.md`,
|
||||
and `${PHASE_DIR}/*-PLAN.md`, then prints a markdown table with one row per
|
||||
REQ-ID and D-ID. Word-boundary matching prevents `REQ-1` from being mistaken for
|
||||
`REQ-10`.)
|
||||
Read the `activeHooks` array from `PLAN_POST_HOOKS_JSON` in-context. If the
|
||||
`gap-analysis` gate hook is absent (capability inactive), skip this step.
|
||||
|
||||
**Output format (deterministic; sorted REQUIREMENTS.md → CONTEXT.md, then natural
|
||||
sort within source):**
|
||||
**For each active entry where `kind == "gate"`** (process in array order):
|
||||
|
||||
```
|
||||
## Post-Planning Gap Analysis
|
||||
|
||||
| Source | Item | Status |
|
||||
|--------|------|--------|
|
||||
| REQUIREMENTS.md | REQ-01 | ✓ Covered |
|
||||
| REQUIREMENTS.md | REQ-02 | ✗ Not covered |
|
||||
| CONTEXT.md | D-01 | ✓ Covered |
|
||||
| CONTEXT.md | D-02 | ✗ Not covered |
|
||||
|
||||
⚠ N items not covered by any plan
|
||||
```bash
|
||||
GATE_RESULT=$(gsd_run check ${hook.check.query} "${PHASE_DIR}" "${PHASE_REQ_IDS}" --raw)
|
||||
CHECK_EXIT=$?
|
||||
```
|
||||
|
||||
**Skip-gracefully behavior:**
|
||||
- REQUIREMENTS.md missing → CONTEXT-only report.
|
||||
- CONTEXT.md missing → REQUIREMENTS-only report.
|
||||
- Both missing or `<decisions>` block missing → "No requirements or decisions to check" line, no error.
|
||||
**Step 1 — did the CHECK COMMAND itself succeed?**
|
||||
If the check command failed (non-zero `CHECK_EXIT`, empty output, or unparseable JSON):
|
||||
- `onError == "halt"` → halt and surface command error.
|
||||
- `onError == "skip"` → log a warning and continue to the next hook.
|
||||
|
||||
This step is non-blocking. If items are reported as not covered, the user may
|
||||
re-run `/gsd:plan-phase --gaps` to add plans, or proceed to execute-phase as-is.
|
||||
**Step 2 — read `GATE_RESULT.block` (boolean).** Only reached when command succeeded.
|
||||
|
||||
- If `hook.blocking == true` and `GATE_RESULT.block == true`: halt. (gap-analysis is always `blocking: false` so this branch is informational only.)
|
||||
- If `hook.blocking == false` (advisory): if `GATE_RESULT.block == true` or non-empty `table`/`summary`, output the gap table and continue. Advisory gates never block phase completion.
|
||||
- If `hook.blocking == true` and `GATE_RESULT.block == false`: continue silently.
|
||||
|
||||
## 14. Present Final Status
|
||||
|
||||
|
||||
Reference in New Issue
Block a user