* test(#2830): add failing regression tests for halted-plan dependent blocking Add tests/fix-2830-halted-plan-dependents.test.cjs covering direct, transitive (2 and 3 hop), and diamond dependents of a halted plan across both independent "which plans are incomplete" readers (phase-plan-index's cmdPhasePlanIndex and findPhaseInternal/searchPhaseInDir), the negative case (an unrelated decoupled plan stays runnable), and a parity check that the two readers agree. Uses only modules that already exist at this commit (gsd-tools.cjs via subprocess, the pre-existing phase-locator.cjs) so the test file loads and runs cleanly on a fresh clone of this exact commit. These fail against current behavior: neither reader has any concept of a halted plan or a blocked_by/runnable view yet. * fix(#2830): a halted plan no longer leaves its dependents on the runnable work list A plan that reaches a designed stop still writes a SUMMARY, so both "which plans are incomplete" readers saw it as an ordinary completion and reported its dependents as ordinary runnable work — never checking whether an upstream plan had halted rather than finished. - New `status: halted` frontmatter value, documented in all four SUMMARY templates alongside the existing `status: complete`. - New shared src/plan-dependency-graph.cts: a single computeHaltPropagation pass that both phase.cts's cmdPhasePlanIndex (wave-grouping) and phase-locator.cts's searchPhaseInDir (the phase-location primitive, ~50 dependent symbols across 5 command routers) now call, so the two-implementation divergence that caused this bug cannot recur. It accepts an optional precomputedOrder so cmdPhasePlanIndex — which already runs Kahn's algorithm in computeDependencyLevels for wave assignment — passes that order straight through instead of a second traversal; searchPhaseInDir (no prior traversal) lets the module derive its own. The two small duplicated predicates each reader would otherwise carry (is this status "halted"?, which summary file matches which plan id?) are centralized in the same module as isHaltedStatus/buildSummaryFileIndex. - Additive fields only: `halted`/`blocked_by`/`runnable` on cmdPhasePlanIndex's plans[] and top level, `halted_plans`/`blocked_by`/ `runnable_plans` on searchPhaseInDir's result. The pre-existing `incomplete`/`incomplete_plans` fields are unchanged in meaning and membership. - execute-phase.md's discover_and_group_plans step now also skips any plan whose `blocked_by` is non-empty, reporting it by name with its blocking chain, in addition to (not instead of) the existing has_summary skip rule. Extends tests/fix-2830-halted-plan-dependents.test.cjs (introduced in the prior commit) with direct unit coverage of computeHaltPropagation (including the precomputedOrder call shape) and a fast-check property test — both only possible once this commit's new module exists. Closes #2830 * fix(#2830): surface the halt-aware view from init execute-phase The adopted work made phase-locator compute halted_plans / blocked_by / runnable_plans, but cmdInitExecutePhase builds its output by explicitly enumerating fields, so all three were computed and then silently dropped at the exact consumer the issue names as regressed. Forwards them additively -- incomplete_plans and incomplete_count keep their name, type and semantics byte-for-byte -- and adds the same three empty defaults to the roadmap-only fallback so the shape is consistent in both branches. Covered by a new test that drives the real CLI end to end rather than the locator function, since the locator already worked. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(#2830): fail closed on dependency cycles and stop the templates inviting the defect Three review findings, all fixed: - BLOCKER (isolated adversarial). Cycle participants never reach indegree 0 in the Kahn pass, so they were excluded from the topological order, never visited by the forward pass, and vanished from blocked_by entirely -- i.e. reported as runnable. The wave-grouping reader hard-fails on a cycle so it never hit this, but the phase-location reader does not, so init execute-phase offered a plan depending directly on a halted plan. Reproduced, then fixed in the shared engine so every consumer is safe regardless of pre-checks: a node absent from the order is now blocked with a deterministic, non-empty named cause. A plan silently missing from both blocked_by and runnable is the exact disappearance this issue exists to prevent. - MAJOR (isolated adversarial). All four summary templates showed the field as an inline comment on the value line. Frontmatter parsing does not strip trailing comments, so an executor copying the templates' own presentation wrote a halt that parsed as a non-halted string, silently reproducing the original bug. Guidance moved off the value line, and the halt predicate now tolerates an unquoted trailing comment. - HARD standards violation. A test regex-matched child-process stderr prose for /cycle/i, which CONTRIBUTING bans. Replaced with the structured failure signal plus a differential assertion (same fixture without the cycle edge must succeed), so it stays cycle-specific without matching prose. Also folds the duplicated read-summary-and-check-halted wrapper out of both readers into the shared module -- centralizing only the predicate left the exact two-copies-that-drift pattern the module exists to prevent -- and commits the artifact-types documentation for the new status value. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(#2830): stop the property generator hanging the whole suite The remote runner did not fail -- it hung. Two containers sat in this file for 31+ minutes, and an earlier attempt ran 9 hours before I killed it. The runner passes --test-timeout=0, so nothing ever reaps it: this would have hung CI indefinitely, not reported a failure. Root cause: the DAG generator built edges by rejection -- from: fc.integer({ min: 0, max: n - 1 }) to: fc.integer({ min: 0, max: n - 1 }) .filter(({ from, to }) => from < to) With n === 1 both integers are forced to 0, so the predicate is unsatisfiable and fast-check retries value generation forever. n is drawn from 1..12 and fast-check biases toward boundary values, so n === 1 is reached almost at once. This also explains why the failing-first run completed normally while the fixed run hung: before the fix the graph module did not exist, so the property test threw on import and never reached generation. It only starts hanging once the code under test works. Generates the DAG by construction instead -- `to` is drawn strictly above `from`, with the degenerate single-node case short-circuited to an empty edge list -- so no rejection sampling is involved. Switches the import to the shared fast-check setup so the seed and run count are pinned per CONTRIBUTING, and adds a bounded regression guard that samples the arbitrary directly, so a future reintroduction fails loudly instead of hanging. Verified: the file now completes in 2 seconds, 29 tests started and 29 finished, zero failures, against an indefinite hang before. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(#2830): restore the depends_on display contract and acknowledge the workflow growth Full-suite run surfaced two things the focused harnesses could not. 1. Regression of a pinned pre-existing contract (#3785). A refactor routed the EMITTED depends_on field through the new dependency resolver, which also consults the canonical-prefix map. The original consulted the plan map only, so a short canonical prefix passed through verbatim -- '24-01' stayed '24-01' rather than becoming '24-01-auth-hardening'. The emitted field is a DISPLAY mapping, not the DAG resolution, and #3785 pins that. Reverted with a comment recording why it must not use the resolver; full resolution is still used for the wave DAG and halt propagation, which is what needs it. 2. The workflow file grew 518 bytes without an acknowledgment, from the halt-aware skip rule and the widened parse contract. Acknowledged. Note on where the acknowledgment landed: the guidance is to add a NEW fragment, but execute-phase.md is already named by an existing fragment and the linter hard-fails when two ack sources name the same path. Appending to the owning fragment, following its own established multi-PR pattern, was the only lint-clean option. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(#2830): backfill changeset pr number Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: sim <sim@local> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
12 KiB
Summary Template
Template for .planning/phases/XX-name/{phase}-{plan}-SUMMARY.md - phase completion documentation.
File Template
---
phase: XX-name
plan: YY
subsystem: [primary category: auth, payments, ui, api, database, infra, testing, etc.]
tags: [searchable tech: jwt, stripe, react, postgres, prisma]
# Dependency graph
requires:
- phase: [prior phase this depends on]
provides: [what that phase built that this uses]
provides:
- [bullet list of what this phase built/delivered]
affects: [list of phase names or keywords that will need this context]
# Actuals (#2632) — pairs with the plan's `estimate` to calibrate future estimates.
# Same estimateTokens scale (chars/4 over the realized diff), never a harness token count.
actuals:
tokens: [chars/4 over files actually changed]
tasks: [tasks completed]
commits: [commits made]
# Tech tracking
tech-stack:
added: [libraries/tools added in this phase]
patterns: [architectural/code patterns established]
key-files:
created: [important files created]
modified: [important files modified]
key-decisions:
- "Decision 1"
- "Decision 2"
patterns-established:
- "Pattern 1: description"
- "Pattern 2: description"
requirements-completed: [] # REQUIRED — Copy ALL requirement IDs from this plan's `requirements` frontmatter field.
# Coverage metadata (#1602) — one entry per shipped deliverable. Drives DETERMINISTIC UAT routing in verify-work.
# OMIT this whole block for legacy/prose-only SUMMARYs — verify-work then falls back to the ## Accomplishments bullets
# (byte-identical behavior for un-migrated phases). See <coverage_guidance> below for the contract.
coverage:
- id: D1
description: "[deliverable in human-readable form — what would have been a prose ## Accomplishments bullet]"
requirement: "[REQ-ID from this plan's `requirements`, or omit if none]"
verification:
- kind: unit # unit | integration | e2e | automated_ui | manual_procedural | other
ref: "[tests/path.test.ts#test name | playwright:shot.png | command invocation]"
status: pass # pass | fail | unknown — from the latest run
human_judgment: false # REQUIRED boolean. false => may auto-pass IF every verification status is `pass`.
- id: D2
description: "[a deliverable that needs a human to sign off]"
verification: []
human_judgment: true
rationale: "[REQUIRED when human_judgment: true — why automation is insufficient]"
# Metrics
duration: Xmin
completed: YYYY-MM-DD
status: complete
---
# Phase [X]: [Name] Summary
**[Substantive one-liner describing outcome - NOT "phase complete" or "implementation finished"]**
## Performance
- **Duration:** [time] (e.g., 23 min, 1h 15m)
- **Started:** [ISO timestamp]
- **Completed:** [ISO timestamp]
- **Tasks:** [count completed]
- **Files modified:** [count]
## Accomplishments
- [Most important outcome]
- [Second key accomplishment]
- [Third if applicable]
## Task Commits
Each task was committed atomically:
1. **Task 1: [task name]** - `abc123f` (feat/fix/test/refactor)
2. **Task 2: [task name]** - `def456g` (feat/fix/test/refactor)
3. **Task 3: [task name]** - `hij789k` (feat/fix/test/refactor)
**Plan metadata:** `lmn012o` (docs: complete plan)
_Note: TDD tasks may have multiple commits (test → feat → refactor)_
## Files Created/Modified
- `path/to/file.ts` - What it does
- `path/to/another.ts` - What it does
## Decisions Made
[Key decisions with brief rationale, or "None - followed plan as specified"]
## Deviations from Plan
[If no deviations: "None - plan executed exactly as written"]
[If deviations occurred:]
### Auto-fixed Issues
**1. [Rule X - Category] Brief description**
- **Found during:** Task [N] ([task name])
- **Issue:** [What was wrong]
- **Fix:** [What was done]
- **Files modified:** [file paths]
- **Verification:** [How it was verified]
- **Committed in:** [hash] (part of task commit)
[... repeat for each auto-fix ...]
---
**Total deviations:** [N] auto-fixed ([breakdown by rule])
**Impact on plan:** [Brief assessment - e.g., "All auto-fixes necessary for correctness/security. No scope creep."]
## Issues Encountered
[Problems and how they were resolved, or "None"]
[Note: "Deviations from Plan" documents unplanned work that was handled automatically via deviation rules. "Issues Encountered" documents problems during planned work that required problem-solving.]
## User Setup Required
[If USER-SETUP.md was generated:]
**External services require manual configuration.** See [{phase}-USER-SETUP.md](./{phase}-USER-SETUP.md) for:
- Environment variables to add
- Dashboard configuration steps
- Verification commands
[If no USER-SETUP.md:]
None - no external service configuration required.
## Next Phase Readiness
[What's ready for next phase]
[Any blockers or concerns]
---
*Phase: XX-name*
*Completed: [date]*
<frontmatter_guidance> Purpose: Enable automatic context assembly via dependency graph. Frontmatter makes summary metadata machine-readable so plan-phase can scan all summaries quickly and select relevant ones based on dependencies.
Fast scanning: Frontmatter is first ~25 lines, cheap to scan across all summaries without reading full content.
Dependency graph: requires/provides/affects create explicit links between phases, enabling transitive closure for context selection.
Subsystem: Primary categorization (auth, payments, ui, api, database, infra, testing) for detecting related phases.
Tags: Searchable technical keywords (libraries, frameworks, tools) for tech stack awareness.
Key-files: Important files for @context references in PLAN.md.
Patterns: Established conventions future phases should maintain.
Population: Frontmatter is populated during summary creation in execute-plan.md. See <step name="create_summary"> for field-by-field guidance.
Status (#2830): status: complete is the default — the plan finished. Use status: halted instead when the plan reached a designed stop (a gate failure, a spike concluding without expanding into the full build, or any other intentional non-completion) and intentionally left tasks unfinished. halted is machine-read: any plan whose depends_on (directly or transitively) names a halted plan is reported as blocked, not offered to the executor, until the halt is resolved and re-summarized as complete.
</frontmatter_guidance>
<coverage_guidance>
Purpose (#1602): The coverage: block is a per-deliverable Requirements Traceability Matrix. It lets verify-work's extract_tests step route deliverables DETERMINISTICALLY — auto-passing those proven by passing tests and reserving human UAT for genuine judgment — instead of re-deriving coverage from prose. Consumed via gsd-tools uat classify-coverage --summary <SUMMARY>.
Field semantics:
| Field | Purpose |
|---|---|
id |
Stable identifier (D1, D2…) for cross-referencing from UAT.md and audit reports. Must be unique within the SUMMARY. |
description |
The deliverable in human-readable form — what would have been a prose bullet. |
requirement |
Links back to a REQUIREMENTS.md REQ-ID (joins requirements-completed). Optional. |
verification[].kind |
Enum: unit | integration | e2e | automated_ui | manual_procedural | other. |
verification[].ref |
Test path + descriptor (file#test name), Playwright screenshot ref, or command invocation. Required per entry. |
verification[].status |
pass | fail | unknown — populated from the latest test run. |
human_judgment |
Explicit boolean; REQUIRED. true always routes to a human. |
rationale |
REQUIRED when human_judgment: true. The audit trail for why automation is insufficient. |
Deterministic contract (what the classifier does):
- A deliverable auto-passes (no human prompt) only when
human_judgment: falseANDverificationis non-empty AND everyverification[].statusispass. This is the narrow, fully-proven case. - Everything else is presented to a human —
human_judgment: true, an emptyverification:, any non-pass/unknownstatus, or any schema error. A false-negative is a redundant prompt (the status quo); a false-positive ships a bug UAT existed to catch. - Fail-safe default: if you cannot determine coverage for a deliverable, you MUST set
human_judgment: truewithrationale: "Coverage not determined at authoring time — verifier must classify". Never leave a deliverable'shuman_judgmentempty, and never set itfalsejust to skip the prompt — auto-pass additionally requires a passingverificationentry, so the flag alone cannot skip the human. coverage: []means "no deliverables to classify" (the single-confirmation path). OMITTING the block entirely means "legacy" —verify-workfalls back to prose## Accomplishmentsextraction unchanged. </coverage_guidance>
<one_liner_rules> The one-liner MUST be substantive:
Good:
- "JWT auth with refresh rotation using jose library"
- "Prisma schema with User, Session, and Product models"
- "Dashboard with real-time metrics via Server-Sent Events"
Bad:
- "Phase complete"
- "Authentication implemented"
- "Foundation finished"
- "All tasks done"
The one-liner should tell someone what actually shipped. </one_liner_rules>
```markdown # Phase 1: Foundation SummaryJWT auth with refresh rotation using jose library, Prisma User model, and protected API middleware
Performance
- Duration: 28 min
- Started: 2025-01-15T14:22:10Z
- Completed: 2025-01-15T14:50:33Z
- Tasks: 5
- Files modified: 8
Accomplishments
- User model with email/password auth
- Login/logout endpoints with httpOnly JWT cookies
- Protected route middleware checking token validity
- Refresh token rotation on each request
Files Created/Modified
prisma/schema.prisma- User and Session modelssrc/app/api/auth/login/route.ts- Login endpointsrc/app/api/auth/logout/route.ts- Logout endpointsrc/middleware.ts- Protected route checkssrc/lib/auth.ts- JWT helpers using jose
Decisions Made
- Used jose instead of jsonwebtoken (ESM-native, Edge-compatible)
- 15-min access tokens with 7-day refresh tokens
- Storing refresh tokens in database for revocation capability
Deviations from Plan
Auto-fixed Issues
1. [Rule 2 - Missing Critical] Added password hashing with bcrypt
- Found during: Task 2 (Login endpoint implementation)
- Issue: Plan didn't specify password hashing - storing plaintext would be critical security flaw
- Fix: Added bcrypt hashing on registration, comparison on login with salt rounds 10
- Files modified: src/app/api/auth/login/route.ts, src/lib/auth.ts
- Verification: Password hash test passes, plaintext never stored
- Committed in: abc123f (Task 2 commit)
2. [Rule 3 - Blocking] Installed missing jose dependency
- Found during: Task 4 (JWT token generation)
- Issue: jose package not in package.json, import failing
- Fix: Ran
npm install jose - Files modified: package.json, package-lock.json
- Verification: Import succeeds, build passes
- Committed in: def456g (Task 4 commit)
Total deviations: 2 auto-fixed (1 missing critical, 1 blocking) Impact on plan: Both auto-fixes essential for security and functionality. No scope creep.
Issues Encountered
- jsonwebtoken CommonJS import failed in Edge runtime - switched to jose (planned library change, worked as expected)
Next Phase Readiness
- Auth foundation complete, ready for feature development
- User registration endpoint needed before public launch
Phase: 01-foundation Completed: 2025-01-15
</example>
<guidelines>
**Frontmatter:** MANDATORY - complete all fields. Enables automatic context assembly for future planning.
**One-liner:** Must be substantive. "JWT auth with refresh rotation using jose library" not "Authentication implemented".
**Decisions section:**
- Key decisions made during execution with rationale
- Extracted to STATE.md accumulated context
- Use "None - followed plan as specified" if no deviations
**After creation:** STATE.md updated with position, decisions, issues.
</guidelines>