cabloy 5.1.201 → 5.1.202

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.cabloy-version CHANGED
@@ -1 +1 @@
1
- 5.1.201
1
+ 5.1.202
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.1.202
4
+
5
+ ### Features
6
+
7
+ - Add a traceability gate for planning baseline reviews.
8
+
3
9
  ## 5.1.201
4
10
 
5
11
  ### Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cabloy",
3
- "version": "5.1.201",
3
+ "version": "5.1.202",
4
4
  "gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
5
5
  "description": "A Node.js fullstack framework",
6
6
  "keywords": [
@@ -179,21 +179,21 @@
179
179
  {
180
180
  "adapter": "codex",
181
181
  "category": "skill",
182
- "sha256": "788f9c94a93753f2ad16d0a1f772fe61785514ab69e2564a2a2454f4efb46086",
182
+ "sha256": "e3441608051b6383385a9c4bd7fd77c8fc9e48a89293c4da81f05e067f9a8e8a",
183
183
  "source": "skills/cabloy-spec-execution/references/execution-protocol.md",
184
184
  "target": ".agents/skills/cabloy-spec-execution/references/execution-protocol.md"
185
185
  },
186
186
  {
187
187
  "adapter": "codex",
188
188
  "category": "skill",
189
- "sha256": "ec93b6999fcf87f35f8f1f18eed54bb08ee2753862b34f6c474480f2c88ee7e0",
189
+ "sha256": "d9bc04fff4781daed907508efb36efe7e60fcf8d2de98e0089b175273f29e14e",
190
190
  "source": "skills/cabloy-spec-execution/references/status-and-evidence.md",
191
191
  "target": ".agents/skills/cabloy-spec-execution/references/status-and-evidence.md"
192
192
  },
193
193
  {
194
194
  "adapter": "codex",
195
195
  "category": "skill",
196
- "sha256": "52a1a0ae16f72345a761d657130fd65b681535c20937a86d1d6f279772c9bb31",
196
+ "sha256": "60e20ef75f390a25464208470b6862f7a559d9eddf4d6f8012d2b99cc992c887",
197
197
  "source": "skills/cabloy-spec-execution/SKILL.md",
198
198
  "target": ".agents/skills/cabloy-spec-execution/SKILL.md"
199
199
  },
@@ -221,7 +221,7 @@
221
221
  {
222
222
  "adapter": "codex",
223
223
  "category": "skill",
224
- "sha256": "df49ea0a8485de0ea6cf345199481836924eed0b68608abf4e6ed829f620b72e",
224
+ "sha256": "f4051409e04e5557a3d45415b8111e6a70b7bfa013b52ee75553ace29d28531c",
225
225
  "source": "skills/cabloy-spec-generation/references/canonical-spec-input.md",
226
226
  "target": ".agents/skills/cabloy-spec-generation/references/canonical-spec-input.md"
227
227
  },
@@ -235,21 +235,21 @@
235
235
  {
236
236
  "adapter": "codex",
237
237
  "category": "skill",
238
- "sha256": "95fb6ad6f1f85373a6565442e4f0f50582acafd2bef5c8f1f0056123b0996bc8",
238
+ "sha256": "17aaee04e0db0bce9b09d72eb55181d062b0024974ed72014681be1c8490a6a1",
239
239
  "source": "skills/cabloy-spec-generation/references/repo-specs-document-set.md",
240
240
  "target": ".agents/skills/cabloy-spec-generation/references/repo-specs-document-set.md"
241
241
  },
242
242
  {
243
243
  "adapter": "codex",
244
244
  "category": "skill",
245
- "sha256": "45b83917c079306fb56e0c2cf406a894f7c18c9fd8732f97f9902ed02cf32594",
245
+ "sha256": "4174bef895433d6bdfd6238c6a90391e825e1dce90194cfd47bd6aa2d7a02494",
246
246
  "source": "skills/cabloy-spec-generation/references/traceability-and-status-rules.md",
247
247
  "target": ".agents/skills/cabloy-spec-generation/references/traceability-and-status-rules.md"
248
248
  },
249
249
  {
250
250
  "adapter": "codex",
251
251
  "category": "skill",
252
- "sha256": "1fae802d5b5f2fac97c4b223870d933e9fd551fdc37b5ac3a0acaa4f40efd45b",
252
+ "sha256": "4d7c6a45eb8854ac50a04e8cbf878c7b47dc5253dad05b9cea7ed0ec2b28b1d0",
253
253
  "source": "skills/cabloy-spec-generation/SKILL.md",
254
254
  "target": ".agents/skills/cabloy-spec-generation/SKILL.md"
255
255
  },
@@ -515,21 +515,21 @@
515
515
  {
516
516
  "adapter": "claude",
517
517
  "category": "skill",
518
- "sha256": "788f9c94a93753f2ad16d0a1f772fe61785514ab69e2564a2a2454f4efb46086",
518
+ "sha256": "e3441608051b6383385a9c4bd7fd77c8fc9e48a89293c4da81f05e067f9a8e8a",
519
519
  "source": "skills/cabloy-spec-execution/references/execution-protocol.md",
520
520
  "target": ".claude/skills/cabloy-spec-execution/references/execution-protocol.md"
521
521
  },
522
522
  {
523
523
  "adapter": "claude",
524
524
  "category": "skill",
525
- "sha256": "ec93b6999fcf87f35f8f1f18eed54bb08ee2753862b34f6c474480f2c88ee7e0",
525
+ "sha256": "d9bc04fff4781daed907508efb36efe7e60fcf8d2de98e0089b175273f29e14e",
526
526
  "source": "skills/cabloy-spec-execution/references/status-and-evidence.md",
527
527
  "target": ".claude/skills/cabloy-spec-execution/references/status-and-evidence.md"
528
528
  },
529
529
  {
530
530
  "adapter": "claude",
531
531
  "category": "skill",
532
- "sha256": "52a1a0ae16f72345a761d657130fd65b681535c20937a86d1d6f279772c9bb31",
532
+ "sha256": "60e20ef75f390a25464208470b6862f7a559d9eddf4d6f8012d2b99cc992c887",
533
533
  "source": "skills/cabloy-spec-execution/SKILL.md",
534
534
  "target": ".claude/skills/cabloy-spec-execution/SKILL.md"
535
535
  },
@@ -557,7 +557,7 @@
557
557
  {
558
558
  "adapter": "claude",
559
559
  "category": "skill",
560
- "sha256": "df49ea0a8485de0ea6cf345199481836924eed0b68608abf4e6ed829f620b72e",
560
+ "sha256": "f4051409e04e5557a3d45415b8111e6a70b7bfa013b52ee75553ace29d28531c",
561
561
  "source": "skills/cabloy-spec-generation/references/canonical-spec-input.md",
562
562
  "target": ".claude/skills/cabloy-spec-generation/references/canonical-spec-input.md"
563
563
  },
@@ -571,21 +571,21 @@
571
571
  {
572
572
  "adapter": "claude",
573
573
  "category": "skill",
574
- "sha256": "95fb6ad6f1f85373a6565442e4f0f50582acafd2bef5c8f1f0056123b0996bc8",
574
+ "sha256": "17aaee04e0db0bce9b09d72eb55181d062b0024974ed72014681be1c8490a6a1",
575
575
  "source": "skills/cabloy-spec-generation/references/repo-specs-document-set.md",
576
576
  "target": ".claude/skills/cabloy-spec-generation/references/repo-specs-document-set.md"
577
577
  },
578
578
  {
579
579
  "adapter": "claude",
580
580
  "category": "skill",
581
- "sha256": "45b83917c079306fb56e0c2cf406a894f7c18c9fd8732f97f9902ed02cf32594",
581
+ "sha256": "4174bef895433d6bdfd6238c6a90391e825e1dce90194cfd47bd6aa2d7a02494",
582
582
  "source": "skills/cabloy-spec-generation/references/traceability-and-status-rules.md",
583
583
  "target": ".claude/skills/cabloy-spec-generation/references/traceability-and-status-rules.md"
584
584
  },
585
585
  {
586
586
  "adapter": "claude",
587
587
  "category": "skill",
588
- "sha256": "1fae802d5b5f2fac97c4b223870d933e9fd551fdc37b5ac3a0acaa4f40efd45b",
588
+ "sha256": "4d7c6a45eb8854ac50a04e8cbf878c7b47dc5253dad05b9cea7ed0ec2b28b1d0",
589
589
  "source": "skills/cabloy-spec-generation/SKILL.md",
590
590
  "target": ".claude/skills/cabloy-spec-generation/SKILL.md"
591
591
  },
@@ -865,21 +865,21 @@
865
865
  {
866
866
  "adapter": "cursor",
867
867
  "category": "skill",
868
- "sha256": "788f9c94a93753f2ad16d0a1f772fe61785514ab69e2564a2a2454f4efb46086",
868
+ "sha256": "e3441608051b6383385a9c4bd7fd77c8fc9e48a89293c4da81f05e067f9a8e8a",
869
869
  "source": "skills/cabloy-spec-execution/references/execution-protocol.md",
870
870
  "target": ".cursor/skills/cabloy-spec-execution/references/execution-protocol.md"
871
871
  },
872
872
  {
873
873
  "adapter": "cursor",
874
874
  "category": "skill",
875
- "sha256": "ec93b6999fcf87f35f8f1f18eed54bb08ee2753862b34f6c474480f2c88ee7e0",
875
+ "sha256": "d9bc04fff4781daed907508efb36efe7e60fcf8d2de98e0089b175273f29e14e",
876
876
  "source": "skills/cabloy-spec-execution/references/status-and-evidence.md",
877
877
  "target": ".cursor/skills/cabloy-spec-execution/references/status-and-evidence.md"
878
878
  },
879
879
  {
880
880
  "adapter": "cursor",
881
881
  "category": "skill",
882
- "sha256": "52a1a0ae16f72345a761d657130fd65b681535c20937a86d1d6f279772c9bb31",
882
+ "sha256": "60e20ef75f390a25464208470b6862f7a559d9eddf4d6f8012d2b99cc992c887",
883
883
  "source": "skills/cabloy-spec-execution/SKILL.md",
884
884
  "target": ".cursor/skills/cabloy-spec-execution/SKILL.md"
885
885
  },
@@ -907,7 +907,7 @@
907
907
  {
908
908
  "adapter": "cursor",
909
909
  "category": "skill",
910
- "sha256": "df49ea0a8485de0ea6cf345199481836924eed0b68608abf4e6ed829f620b72e",
910
+ "sha256": "f4051409e04e5557a3d45415b8111e6a70b7bfa013b52ee75553ace29d28531c",
911
911
  "source": "skills/cabloy-spec-generation/references/canonical-spec-input.md",
912
912
  "target": ".cursor/skills/cabloy-spec-generation/references/canonical-spec-input.md"
913
913
  },
@@ -921,21 +921,21 @@
921
921
  {
922
922
  "adapter": "cursor",
923
923
  "category": "skill",
924
- "sha256": "95fb6ad6f1f85373a6565442e4f0f50582acafd2bef5c8f1f0056123b0996bc8",
924
+ "sha256": "17aaee04e0db0bce9b09d72eb55181d062b0024974ed72014681be1c8490a6a1",
925
925
  "source": "skills/cabloy-spec-generation/references/repo-specs-document-set.md",
926
926
  "target": ".cursor/skills/cabloy-spec-generation/references/repo-specs-document-set.md"
927
927
  },
928
928
  {
929
929
  "adapter": "cursor",
930
930
  "category": "skill",
931
- "sha256": "45b83917c079306fb56e0c2cf406a894f7c18c9fd8732f97f9902ed02cf32594",
931
+ "sha256": "4174bef895433d6bdfd6238c6a90391e825e1dce90194cfd47bd6aa2d7a02494",
932
932
  "source": "skills/cabloy-spec-generation/references/traceability-and-status-rules.md",
933
933
  "target": ".cursor/skills/cabloy-spec-generation/references/traceability-and-status-rules.md"
934
934
  },
935
935
  {
936
936
  "adapter": "cursor",
937
937
  "category": "skill",
938
- "sha256": "1fae802d5b5f2fac97c4b223870d933e9fd551fdc37b5ac3a0acaa4f40efd45b",
938
+ "sha256": "4d7c6a45eb8854ac50a04e8cbf878c7b47dc5253dad05b9cea7ed0ec2b28b1d0",
939
939
  "source": "skills/cabloy-spec-generation/SKILL.md",
940
940
  "target": ".cursor/skills/cabloy-spec-generation/SKILL.md"
941
941
  },
@@ -97,8 +97,8 @@ Stop and route back to the relevant planning authority if any of the following a
97
97
  - the suite identity, ownership, or target boundary is unresolved;
98
98
  - PRD/SRS/ADR/WBS/test-plan records contradict one another;
99
99
  - a controlling `TODO(confirm)` or unaccepted ADR remains;
100
- - a predecessor is not complete according to the WBS, or its required evidence is absent;
101
- - the selected task is already `verified`, explicitly `deferred`, or currently `blocked`;
100
+ - a predecessor is neither `verified` nor formally opted-in `planning-complete` with a named-reviewer, revision-scoped documentary closure disposition, or its required evidence is absent; a planning-complete edge does not authorize execution;
101
+ - the selected task is already `verified` or `planning-complete`, explicitly `deferred`, or currently `blocked`;
102
102
  - a persisted field/schema change lacks an explicit decision about incrementing `vonaModule.fileVersion`;
103
103
  - the working tree contains overlapping unclassified changes that make attribution or rollback unclear;
104
104
  - authorization, tenant isolation, privacy, lifecycle, transaction, concurrency, idempotency, or ownership behavior is unspecified for a material risk;
@@ -139,6 +139,7 @@ Record proof under `references/status-and-evidence.md`, preserving the establish
139
139
  Set status accurately:
140
140
 
141
141
  - `in-progress` while work or verification remains open;
142
+ - `planning-complete` only for an opted-in documentary/design task after explicit named-reviewer, revision-scoped planning-closure disposition and linked planning evidence; never infer source/ATP closure or next-task approval;
142
143
  - `implementation-complete` when source work is complete but ATP/release proof remains;
143
144
  - `verified` only after all applicable WBS checks and ATPs have durable linked redacted evidence;
144
145
  - `blocked`, `waived`, or `deferred` only with the required details.
@@ -61,7 +61,7 @@ Before implementation, verify:
61
61
 
62
62
  - the WBS target exists and is bounded;
63
63
  - every material linked requirement has a technical contract and ATP;
64
- - predecessor status satisfies the WBS dependency rule and required evidence exists;
64
+ - each predecessor is `verified`, or is `planning-complete` with its own formal `Completion mode: planning-only.` declaration, named-reviewer revision-scoped disposition and documentary proof; neither `implementation-complete` nor a traceability exception satisfies this edge;
65
65
  - no controlling `TODO(confirm)`, unaccepted ADR, waiver, or failed gate remains unresolved;
66
66
  - the selected task is not already `verified`, `deferred`, or `blocked`;
67
67
  - source ownership and the target API/state/page boundary are unambiguous;
@@ -56,15 +56,16 @@ Do not create an empty `evidence/` directory, placeholder `EVD-*` record, or fab
56
56
 
57
57
  Use the suite’s canonical status meanings:
58
58
 
59
- | Status | Meaning |
60
- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
61
- | `not-started` | Defined, but implementation or acceptance evidence has not started. |
62
- | `in-progress` | Work or verification has started, but closure checks remain. |
63
- | `implementation-complete` | Source work is reported complete, but required ATP or release evidence is incomplete. |
64
- | `verified` | Applicable WBS checks and ATPs passed, with durable traceable evidence containing revision, environment, exact procedure, result, and redacted artifact location. |
65
- | `blocked` | A dependency, unresolved decision, failed gate, attribution problem, or missing required proof prevents closure. |
66
- | `waived` | A temporary exception has an owner, reason, affected scope, and expiry; expiry becomes a release blocker. |
67
- | `deferred` | Explicitly postponed scope; it is not complete or verified. |
59
+ | Status | Meaning |
60
+ | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
61
+ | `not-started` | Defined, but implementation or acceptance evidence has not started. |
62
+ | `in-progress` | Work or verification has started, but closure checks remain. |
63
+ | `planning-complete` | An opted-in documentary/design-only WBS task has a named-reviewer, revision-scoped planning-closure disposition and retained planning proof; not source or ATP closure. |
64
+ | `implementation-complete` | Source work is reported complete, but required ATP or release evidence is incomplete. |
65
+ | `verified` | Applicable WBS checks and ATPs passed, with durable traceable evidence containing revision, environment, exact procedure, result, and redacted artifact location. |
66
+ | `blocked` | A dependency, unresolved decision, failed gate, attribution problem, or missing required proof prevents closure. |
67
+ | `waived` | A temporary exception has an owner, reason, affected scope, and expiry; expiry becomes a release blocker. |
68
+ | `deferred` | Explicitly postponed scope; it is not complete or verified. |
68
69
 
69
70
  The normal path is:
70
71
 
@@ -72,7 +73,7 @@ The normal path is:
72
73
  not-started -> in-progress -> implementation-complete -> verified
73
74
  ```
74
75
 
75
- `blocked`, `waived`, and `deferred` are explicit states, not shortcuts to completion. Mark a task `in-progress` only after approved implementation or verification actually begins. A generated file, successful scaffold, passing typecheck, code review, manual walkthrough, screenshot, or unrelated broad test pass cannot by itself produce `verified`.
76
+ A genuinely documentary/design-only task can instead follow `not-started -> in-progress -> planning-complete`, but only when its formal WBS declaration contains `Completion mode: planning-only.`, its own checks have revision-scoped documentary evidence, and a named reviewer explicitly records the planning-closure disposition. `authority-only` traceability is not this opt-in. `planning-complete` satisfies a predecessor dependency edge but never grants successor execution approval, accepts another ADR, completes source, or passes a linked prospective runtime ATP. The Gantt shows its status separately; verified totals and the burndown's active-minus-verified remainder still count only `verified`. `implementation-complete`, `waived` and `deferred` are not completed prerequisites. `blocked`, `waived`, and `deferred` are explicit states, not shortcuts to completion. Mark an implementation task `in-progress` only after approved implementation or verification actually begins. A generated file, successful scaffold, passing typecheck, code review, manual walkthrough, screenshot, or unrelated broad test pass cannot by itself produce `verified`.
76
77
 
77
78
  A task may be `implementation-complete` when source work is complete but an ATP, browser check, paired build, release gate, redaction decision, or durable artifact remains. A phase can be verified without implying that a separately defined integration or release phase is verified.
78
79
 
@@ -128,7 +128,7 @@ npm run spec:check -- <suite> --lightweight
128
128
 
129
129
  For incremental legacy gaps, report the missing definition/owner/link and affected chain without silently changing business meaning. If correction needs a new decision, request it and report the update as incomplete at that gate. Lightweight results must state skipped owners/chain coverage; never advertise a full-suite pass.
130
130
 
131
- Planning creation alone initializes delivery as `not-started`, `deferred`, or specifically `blocked`. Preserve carried-forward observed evidence with its revision/authority limits. Do not run init, database reset, scaffolding, deployment/provider operations, or acceptance tests as an automatic consequence of planning.
131
+ Planning creation alone initializes delivery as `not-started`, `deferred`, or specifically `blocked`. `planning-complete` is only available to a formally opted-in documentary/design task after its own checks receive revision-scoped proof and a named-reviewer closure disposition; it is not ATP verification or permission to execute a successor. Preserve carried-forward observed evidence with its revision/authority limits. Do not run init, database reset, scaffolding, deployment/provider operations, or acceptance tests as an automatic consequence of planning.
132
132
 
133
133
  ## 10. Finish with a bounded execution handoff
134
134
 
@@ -19,6 +19,8 @@ Use exact instantiated IDs in declaration-body Traceability. A wildcard or abbre
19
19
 
20
20
  For a genuinely cross-cutting engineering contract without a product requirement, an atomic SRS bullet may carry `Traceability exception: technical-only — <record-specific rationale>` on its definition line. This exempts only the incoming PRD association; its outgoing WBS mapping remains required. For a documentary planning or release gate with no independent executable scenario, a WBS task may carry `Traceability exception: authority-only — <record-specific rationale>` in its declaration body. This exempts only the outgoing ATP association; its incoming SRS mapping remains required. Use exactly one well-formed field on the correct definition and explain the alternative authority or retained documentary checks. The auditor rejects malformed, empty, duplicated, or wrong-kind exceptions. Neither classification waives the underlying contract, delivery checks, applicable ATPs, revision-scoped evidence, or release approval; never use one merely to silence a missing real link.
21
21
 
22
+ For a Phase 10 task that reviews the entire planning baseline rather than implementing one SRS or executing one ATP, use the separate `Traceability mode: planning-baseline-review` classification and accepted local ADR review authority described in `traceability-and-status-rules.md`. It exempts only that WBS task's incoming SRS and outgoing ATP checks. Do not combine it with a `Traceability exception` or apply it to product-delivery, contract-loop, migration, or release work. Classification alone does not establish approval, proof, `planning-complete` eligibility, or `verified` status; a separate task-local `Completion mode: planning-only.` declaration is required for planning closure.
23
+
22
24
  ## Minimal connected example
23
25
 
24
26
  These are neutral **syntax examples**, not business requirements to copy into a real suite. All four IDs are exact and unique. Production bodies must describe the approved domain.
@@ -69,7 +71,7 @@ Acceptance checks:
69
71
  - The selected ATP passes and retains its required proof.
70
72
  ```
71
73
 
72
- Each phase has explicit Dependencies, using `none` when no predecessor exists. Task-level Dependencies override the phase Dependencies for that task; they are not accumulated automatically. Do not rely on an empty dependency label to mean none. Keep phases dependency-ordered and task scope bounded. WBS bodies own linked IDs, tasks, and checks; progress does not add them.
74
+ Each phase has explicit Dependencies, using `none` when no predecessor exists. Task-level Dependencies override the phase Dependencies for that task; they are not accumulated automatically. Do not rely on an empty dependency label to mean none. Keep phases dependency-ordered and task scope bounded. WBS bodies own linked IDs, tasks, and checks; progress does not add them. For a genuinely documentary/design-only task, the formal task declaration may add exactly one `Completion mode: planning-only.` field. This opt-in is not a phase default or a traceability exception: its own checks still need a named reviewer, a revision-scoped planning-closure disposition and linked documentary proof before progress can say `planning-complete`. Keep linked prospective runtime ATPs intact and unpassed.
73
75
 
74
76
  ### test-plan.md
75
77
 
@@ -109,7 +111,7 @@ Setup, Procedure, Expected result, Minimum proof, and Traceability must each be
109
111
  | `WBS-DEMO-10-01` | `not-started` | None; execution has not begun. | Confirm the bounded execution dossier. |
110
112
  ```
111
113
 
112
- Resolve columns by header names (`WBS ID` and `Status`), never fixed cell positions. Additional/reordered columns are allowed. Require one row for every formal WBS task when constructing the complete chart model. Preserve the established status vocabulary and explain blockers/waivers precisely.
114
+ Resolve columns by header names (`WBS ID` and `Status`), never fixed cell positions. Additional/reordered columns are allowed. Require one row for every formal WBS task when constructing the complete chart model. Preserve the established status vocabulary and explain blockers/waivers precisely. `planning-complete` is valid only for a task declaring `Completion mode: planning-only.` in its WBS body; it satisfies a predecessor dependency edge but does not grant successor execution approval, count as verified, or reduce the verified-based burndown remainder.
113
115
 
114
116
  ## Three gates, not one success signal
115
117
 
@@ -137,10 +137,12 @@ Every WBS entry should state:
137
137
  - primary source areas or ownership boundaries;
138
138
  - bounded tasks;
139
139
  - acceptance/completion checks;
140
- - linked PRD, SRS, and ATP identifiers where a real adjacent-authority association exists; a genuine documentary planning/release gate without an independent ATP uses the explicit `authority-only` classification and retains its SRS association and review evidence as specified in `canonical-spec-input.md`;
140
+ - linked PRD, SRS, and ATP identifiers where a real adjacent-authority association exists; a genuine documentary planning/release gate without an independent ATP uses the explicit `authority-only` classification and retains its SRS association and review evidence as specified in `canonical-spec-input.md`. A whole-baseline planning review with neither a specific SRS nor a formal ATP instead uses `planning-baseline-review` and an accepted local ADR review authority as specified in `traceability-and-status-rules.md`;
141
141
  - whether it is planned, implemented, or awaiting evidence.
142
142
 
143
- Begin with a documentation/decision implementation gate before feature work. For differing Web/Admin strategies, split shared-site integration and independent-site delivery into separate frontend tasks when their source facts, dependencies, or proof differ. An unresolved strategy or exact runtime identifier may block only the affected implementation task; preserve runnable discovery work and unaffected backend or audience work as accurately actionable. Prefer vertical, verifiable increments. Include migration and release hardening as explicit work. Keep `implementation-complete` distinct from `verified`.
143
+ For a documentary/design-only task, declare `Completion mode: planning-only.` in that task's formal WBS body. The declaration makes `planning-complete` eligible only after the task's own checks receive a named-reviewer, revision-scoped closure disposition and retained documentary proof; it does not close linked future runtime ATPs or approve subsequent implementation. This is independent of `planning-baseline-review` and the `authority-only` traceability exception. Other tasks keep ordinary implementation and ATP closure rules.
144
+
145
+ Begin with a documentation/decision implementation gate before feature work. A planning-baseline review gate does not execute an ATP or acquire `verified` status from its audit classification. For differing Web/Admin strategies, split shared-site integration and independent-site delivery into separate frontend tasks when their source facts, dependencies, or proof differ. An unresolved strategy or exact runtime identifier may block only the affected implementation task; preserve runnable discovery work and unaffected backend or audience work as accurately actionable. Prefer vertical, verifiable increments. Include migration and release hardening as explicit work. Keep `implementation-complete` distinct from `verified`.
144
146
 
145
147
  ## Test-plan template contract
146
148
 
@@ -188,17 +190,18 @@ Use:
188
190
 
189
191
  Initialize a new suite with statuses such as `not-started`, `deferred`, or explicitly `blocked`; do not mark planning work as verified. Use this vocabulary:
190
192
 
191
- | Status | Meaning |
192
- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
193
- | `not-started` | Defined, but implementation or acceptance evidence has not started. |
194
- | `in-progress` | Work started, but closure checks are incomplete. |
195
- | `implementation-complete` | Source work is reported complete, but required ATP evidence or release gates are incomplete. |
196
- | `verified` | Required ATP evidence is observed and retained with revision, environment, procedure, result, and redacted artifact location. |
197
- | `blocked` | A dependency, unresolved decision, or failed gate prevents closure. |
198
- | `waived` | A temporary exception with owner, reason, and expiry; expiry makes it a release blocker. |
199
- | `deferred` | Explicitly postponed scope, not completed scope. |
200
-
201
- Progress must remain a derived register. It may point to evidence but must not become a second requirements document. Include a decision-register entry for each material site strategy and show strategy/identifier deferral as a blocker only on affected frontend/site WBS branches; source reading, planning, or a selected strategy alone is not implementation evidence.
193
+ | Status | Meaning |
194
+ | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
195
+ | `not-started` | Defined, but implementation or acceptance evidence has not started. |
196
+ | `in-progress` | Work started, but closure checks are incomplete. |
197
+ | `planning-complete` | An explicitly opted-in planning task's own documentary checks have a named-reviewer, revision-scoped closure disposition and linked proof; not runtime verification. |
198
+ | `implementation-complete` | Source work is reported complete, but required ATP evidence or release gates are incomplete. |
199
+ | `verified` | Required ATP evidence is observed and retained with revision, environment, procedure, result, and redacted artifact location. |
200
+ | `blocked` | A dependency, unresolved decision, or failed gate prevents closure. |
201
+ | `waived` | A temporary exception with owner, reason, and expiry; expiry makes it a release blocker. |
202
+ | `deferred` | Explicitly postponed scope, not completed scope. |
203
+
204
+ A `planning-complete` predecessor satisfies its dependency edge, not the next task's design/approval gates; keep a blocked successor blocked. Verified/remaining burndown counts still count only `verified` as complete. Progress must remain a derived register. It may point to evidence but must not become a second requirements document. Include a decision-register entry for each material site strategy and show strategy/identifier deferral as a blocker only on affected frontend/site WBS branches; source reading, planning, or a selected strategy alone is not implementation evidence.
202
205
 
203
206
  ## ADR 0001 template contract
204
207
 
@@ -49,6 +49,12 @@ Minimum cardinality for a material in-scope requirement:
49
49
 
50
50
  A single ATP may prove several related requirements, but the matrices must make the relationship explicit. A WBS item may cover several contracts, but it still needs bounded completion checks. The full-chain minimum applies to material in-scope product requirements; a separately sourced engineering obligation or a documentary coordination gate need not invent a product parent or an executable ATP. Use the narrowly scoped declaration-local `Traceability exception` classifications in `canonical-spec-input.md` only for those genuine cases. The unaffected direction still needs an exact-ID link, and review/closure still needs applicable retained proof. An exception is not a deferred status, evidence waiver, acceptance result, or release approval.
51
51
 
52
+ ### Planning-baseline review gate
53
+
54
+ A WBS task that solely reviews and approves the whole PRD/SRS/WBS/ATP baseline before feature work may declare `Traceability mode: planning-baseline-review` in its own Phase 10 task body. It must name a local accepted boundary ADR under `decisions/` as `Review authority: [ADR](./decisions/<file>.md)`, and retain substantive Tasks and Acceptance checks for the baseline review. It must not claim to implement a specific SRS contract or execute a formal ATP, including in its title, Tasks, or Acceptance checks. The ADR may use the existing `## Status` followed by `Accepted` or `Accepted.` convention; the auditor does not require new owner or acceptance fields in the ADR, and acceptance of the ADR alone does not prove the review occurred. This explicit process gate alone is exempt from the WBS-specific SRS incoming and ATP outgoing exact-ID checks in the complete audit; all product-chain definitions, ordinary WBS tasks, references, dependencies, and progress remain checked. It cannot classify implementation, migration, contract-loop, release, or mixed-scope work. Do not infer this type from a title, task number, review status, or a summary table, and do not combine it with `Traceability exception: authority-only`.
55
+
56
+ This differs from `authority-only`, which still requires an exact SRS incoming link and exempts only the ATP outgoing link. Both traceability classifications are independent of `Completion mode: planning-only.`; neither makes a task eligible for `planning-complete`. An audit exception is not approval or proof: the ADR decision owner must separately accept the baseline, and the progress record must retain that review's attributable approval before `verified` is appropriate. It does not create an ATP execution record, change any business traceability, or relax the evidence rule for other WBS work.
57
+
52
58
  ## Authority-first updates
53
59
 
54
60
  When a requirement or durable boundary changes:
@@ -67,17 +73,18 @@ Downstream records do not silently override authority. Charts cannot authorize s
67
73
 
68
74
  ## Status semantics
69
75
 
70
- | Status | Meaning |
71
- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
72
- | `not-started` | Defined, but implementation or acceptance evidence has not started. |
73
- | `in-progress` | Work or verification has started, but closure checks are incomplete. |
74
- | `implementation-complete` | Source work is reported complete, but required ATP or release evidence is incomplete. |
75
- | `verified` | Applicable WBS checks and ATPs passed, and durable evidence contains revision, environment, exact procedure, result, and redacted artifact location. |
76
- | `blocked` | A failed gate, dependency, or unresolved decision prevents closure. |
77
- | `waived` | A temporary exception explicitly approved with owner, reason, and expiry. |
78
- | `deferred` | Explicitly postponed scope; it is not complete or verified. |
79
-
80
- For a newly created plan, initialize delivery rows as `not-started`, `deferred`, or `blocked` as appropriate. Creating Markdown files never makes implementation `implementation-complete` or `verified`.
76
+ | Status | Meaning |
77
+ | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
78
+ | `not-started` | Defined, but implementation or acceptance evidence has not started. |
79
+ | `in-progress` | Work or verification has started, but closure checks are incomplete. |
80
+ | `planning-complete` | Opted-in documentary/design-only WBS checks have a named-reviewer, revision-scoped closure disposition and retained planning proof; source and ATP remain unproven. |
81
+ | `implementation-complete` | Source work is reported complete, but required ATP or release evidence is incomplete. |
82
+ | `verified` | Applicable WBS checks and ATPs passed, and durable evidence contains revision, environment, exact procedure, result, and redacted artifact location. |
83
+ | `blocked` | A failed gate, dependency, or unresolved decision prevents closure. |
84
+ | `waived` | A temporary exception explicitly approved with owner, reason, and expiry. |
85
+ | `deferred` | Explicitly postponed scope; it is not complete or verified. |
86
+
87
+ For a newly created plan, initialize delivery rows as `not-started`, `deferred`, or `blocked` as appropriate. Creating Markdown files never makes a task `planning-complete`, implementation `implementation-complete`, or ATP `verified`. Only a formally declared `Completion mode: planning-only.` task with documentary acceptance checks, revision-scoped evidence and an explicit named-reviewer closure disposition may reach `planning-complete`; `authority-only` traceability does not establish this eligibility. A planning-complete predecessor satisfies its WBS dependency edge, but blocked/deferred/waived successors stay gated and require their own decisions and approvals. `implementation-complete`, `waived` and `deferred` do not satisfy predecessor completion. Only `verified` contributes to verified totals and the verified-based burndown remainder.
81
88
 
82
89
  ### README and ADR decision-status consistency
83
90
 
@@ -57,6 +57,267 @@ test('complete baseline checks exact traceability without treating a Proposed AD
57
57
  assert.match(result.limitations, /approval.*evidence authenticity/);
58
58
  });
59
59
 
60
+ const PLANNING_GATE = {
61
+ ...BASELINE,
62
+ 'srs.md': BASELINE['srs.md'].replace(', `WBS-DEMO-10-01`', ''),
63
+ 'test-plan.md': BASELINE['test-plan.md'].replace('Traceability: `WBS-DEMO-10-01`', ''),
64
+ 'pdp-wbs.md':
65
+ '# Delivery\n\n### Phase 10: Baseline\n\nDependencies: none.\n\n#### WBS-DEMO-10-01: Review planning baseline\n\nTraceability mode: planning-baseline-review\n\nReview authority: [ADR](./decisions/0001-boundary.md).\n\nTasks:\n\n- Reconcile the whole requirements, contracts, work and acceptance catalogue.\n\nAcceptance checks:\n\n- Confirm the boundary ADR was accepted by its decision owner.\n',
66
+ 'decisions/0001-boundary.md':
67
+ '# Boundary\n\n## Status\n\nAccepted.\n\n## Decision\n\nAccept the planning boundary.\n',
68
+ };
69
+
70
+ test('explicit planning-baseline gate omits only its WBS adjacency checks', async t => {
71
+ const result = await auditPlanning(await fixture(t, PLANNING_GATE));
72
+ assert.deepEqual(
73
+ result.diagnostics.map(diagnostic => [diagnostic.id, diagnostic.code]),
74
+ [
75
+ ['SRS-DEMO-01', 'traceability-outgoing'],
76
+ ['ATP-DEMO-01', 'traceability-incoming'],
77
+ ],
78
+ );
79
+ assert.equal(result.ok, false);
80
+ });
81
+
82
+ test('a planning gate coexists with a fully connected product chain', async t => {
83
+ const records = {
84
+ ...BASELINE,
85
+ 'pdp-wbs.md': `${PLANNING_GATE['pdp-wbs.md']}\n#### WBS-DEMO-10-02: Implement contract\n\nTraceability: \`SRS-DEMO-01\`, \`ATP-DEMO-01\`.\n`,
86
+ 'srs.md': BASELINE['srs.md'].replace('WBS-DEMO-10-01', 'WBS-DEMO-10-02'),
87
+ 'test-plan.md': BASELINE['test-plan.md'].replace('WBS-DEMO-10-01', 'WBS-DEMO-10-02'),
88
+ 'progress.md': `${BASELINE['progress.md']}| WBS-DEMO-10-02 | Implement contract | not-started |\n`,
89
+ 'decisions/0001-boundary.md': PLANNING_GATE['decisions/0001-boundary.md'],
90
+ };
91
+ const result = await auditPlanning(await fixture(t, records));
92
+ assert.deepEqual(result.diagnostics, []);
93
+ assert.equal(result.ok, true);
94
+ });
95
+
96
+ test('planning closure and baseline traceability classification remain independent', async t => {
97
+ const records = {
98
+ ...BASELINE,
99
+ 'pdp-wbs.md': `${PLANNING_GATE['pdp-wbs.md']}\n#### WBS-DEMO-10-02: Implement contract\n\nTraceability: \`SRS-DEMO-01\`, \`ATP-DEMO-01\`.\n`,
100
+ 'srs.md': BASELINE['srs.md'].replace('WBS-DEMO-10-01', 'WBS-DEMO-10-02'),
101
+ 'test-plan.md': BASELINE['test-plan.md'].replace('WBS-DEMO-10-01', 'WBS-DEMO-10-02'),
102
+ 'progress.md': `${BASELINE['progress.md'].replace('not-started |', 'planning-complete |')}| WBS-DEMO-10-02 | Implement contract | not-started |\n`,
103
+ 'decisions/0001-boundary.md': PLANNING_GATE['decisions/0001-boundary.md'],
104
+ };
105
+ const missingMode = await auditPlanning(await fixture(t, records));
106
+ assert.deepEqual(codes(missingMode), ['completion-mode']);
107
+ const withMode = {
108
+ ...records,
109
+ 'pdp-wbs.md': records['pdp-wbs.md'].replace(
110
+ 'Traceability mode: planning-baseline-review',
111
+ 'Traceability mode: planning-baseline-review\n\nCompletion mode: planning-only.',
112
+ ),
113
+ };
114
+ const eligible = await auditPlanning(await fixture(t, withMode));
115
+ assert.equal(eligible.ok, true, JSON.stringify(eligible.diagnostics));
116
+ const invalidGate = await auditPlanning(
117
+ await fixture(t, {
118
+ ...withMode,
119
+ 'pdp-wbs.md': withMode['pdp-wbs.md'].replace(
120
+ 'Traceability mode: planning-baseline-review',
121
+ 'Traceability mode: unknown',
122
+ ),
123
+ }),
124
+ );
125
+ assert.ok(codes(invalidGate).includes('traceability-mode'));
126
+ assert.ok(!codes(invalidGate).includes('completion-mode'));
127
+ });
128
+
129
+ test('planning review accepts existing and legacy accepted ADR status formats', async t => {
130
+ for (const status of [
131
+ '## Status\n\nAccepted.\n',
132
+ '## Status\n\nAccepted\n',
133
+ '- **Status:** Accepted\n',
134
+ ]) {
135
+ await t.test(status.trim().replace(/\n/g, ' '), async subtest => {
136
+ const records = {
137
+ ...PLANNING_GATE,
138
+ 'decisions/0001-boundary.md': `# Boundary\n\n${status}`,
139
+ };
140
+ const result = await auditPlanning(await fixture(subtest, records));
141
+ assert.ok(!codes(result).includes('traceability-mode'), JSON.stringify(result.diagnostics));
142
+ });
143
+ }
144
+ });
145
+
146
+ test('baseline review prose may refer to the work catalogue without doing delivery work', async t => {
147
+ const records = {
148
+ ...PLANNING_GATE,
149
+ 'pdp-wbs.md': PLANNING_GATE['pdp-wbs.md'].replace(
150
+ '- Reconcile the whole requirements, contracts, work and acceptance catalogue.',
151
+ '- Review the implementation plan, build risks and migration strategy as planning inputs.',
152
+ ),
153
+ };
154
+ const result = await auditPlanning(await fixture(t, records));
155
+ assert.ok(!codes(result).includes('traceability-mode'), JSON.stringify(result.diagnostics));
156
+ });
157
+
158
+ test('baseline classification must be declared inside a bounded Phase 10 WBS task', async t => {
159
+ const cases = [
160
+ [
161
+ 'unknown',
162
+ { 'pdp-wbs.md': PLANNING_GATE['pdp-wbs.md'].replace('planning-baseline-review', 'skip') },
163
+ ],
164
+ [
165
+ 'duplicate',
166
+ {
167
+ 'pdp-wbs.md': PLANNING_GATE['pdp-wbs.md'].replace(
168
+ 'Tasks:',
169
+ 'Traceability mode: planning-baseline-review\n\nTasks:',
170
+ ),
171
+ },
172
+ ],
173
+ ['wrong phase', { 'pdp-wbs.md': PLANNING_GATE['pdp-wbs.md'].replace('Phase 10', 'Phase 20') }],
174
+ ['no tasks', { 'pdp-wbs.md': PLANNING_GATE['pdp-wbs.md'].replace('Tasks:', 'Work:') }],
175
+ [
176
+ 'no checks',
177
+ { 'pdp-wbs.md': PLANNING_GATE['pdp-wbs.md'].replace('Acceptance checks:', 'Proof:') },
178
+ ],
179
+ [
180
+ 'mixed scope',
181
+ {
182
+ 'pdp-wbs.md': `${PLANNING_GATE['pdp-wbs.md']}\n- Implement SRS-DEMO-01 through ATP-DEMO-01.\n`,
183
+ },
184
+ ],
185
+ [
186
+ 'no authority',
187
+ { 'pdp-wbs.md': PLANNING_GATE['pdp-wbs.md'].replace('Review authority:', 'Reviewer:') },
188
+ ],
189
+ [
190
+ 'unaccepted authority',
191
+ {
192
+ 'decisions/0001-boundary.md': PLANNING_GATE['decisions/0001-boundary.md'].replace(
193
+ 'Accepted.',
194
+ 'Proposed.',
195
+ ),
196
+ },
197
+ ],
198
+ [
199
+ 'implementation in title',
200
+ {
201
+ 'pdp-wbs.md': PLANNING_GATE['pdp-wbs.md'].replace(
202
+ 'Review planning baseline',
203
+ 'Review baseline and implement SRS-DEMO-01',
204
+ ),
205
+ },
206
+ ],
207
+ [
208
+ 'release task',
209
+ {
210
+ 'pdp-wbs.md': PLANNING_GATE['pdp-wbs.md'].replace(
211
+ '- Reconcile the whole requirements, contracts, work and acceptance catalogue.',
212
+ '- Review the baseline and release the feature.',
213
+ ),
214
+ },
215
+ ],
216
+ [
217
+ 'implementation task',
218
+ {
219
+ 'pdp-wbs.md': PLANNING_GATE['pdp-wbs.md'].replace(
220
+ '- Reconcile the whole requirements, contracts, work and acceptance catalogue.',
221
+ '- Review the baseline and implement the feature.',
222
+ ),
223
+ },
224
+ ],
225
+ [
226
+ 'implementation acceptance check',
227
+ {
228
+ 'pdp-wbs.md': PLANNING_GATE['pdp-wbs.md'].replace(
229
+ '- Confirm the boundary ADR was accepted by its decision owner.',
230
+ '- Confirm the boundary ADR was accepted and deploy the feature.',
231
+ ),
232
+ },
233
+ ],
234
+ ...['Build the checkout backend.', 'Create a service.', 'Run a migration.'].map(instruction => [
235
+ instruction,
236
+ {
237
+ 'pdp-wbs.md': PLANNING_GATE['pdp-wbs.md'].replace(
238
+ '- Reconcile the whole requirements, contracts, work and acceptance catalogue.',
239
+ `- ${instruction}`,
240
+ ),
241
+ },
242
+ ]),
243
+ [
244
+ 'unmapped contract in title',
245
+ {
246
+ 'pdp-wbs.md': PLANNING_GATE['pdp-wbs.md'].replace(
247
+ 'Review planning baseline',
248
+ 'Review planning baseline for SRS-DEMO-01',
249
+ ),
250
+ },
251
+ ],
252
+ [
253
+ 'contradictory ADR statuses',
254
+ {
255
+ 'decisions/0001-boundary.md': '## Status\n\nAccepted.\n\n- **Status:** Proposed\n',
256
+ },
257
+ ],
258
+ [
259
+ 'ADR status inside unrelated section',
260
+ {
261
+ 'decisions/0001-boundary.md': '# Boundary\n\n## Background\n\n- **Status:** Accepted\n',
262
+ },
263
+ ],
264
+ ['mapped from SRS', { 'srs.md': BASELINE['srs.md'] }],
265
+ ['mapped to ATP', { 'test-plan.md': BASELINE['test-plan.md'] }],
266
+ [
267
+ 'misplaced',
268
+ {
269
+ 'README.md': `${PLANNING_GATE['README.md']}\nTraceability mode: planning-baseline-review\n`,
270
+ },
271
+ ],
272
+ [
273
+ 'competing exception',
274
+ {
275
+ 'pdp-wbs.md': PLANNING_GATE['pdp-wbs.md'].replace(
276
+ 'Tasks:',
277
+ 'Traceability exception: authority-only — Review the whole baseline.\n\nTasks:',
278
+ ),
279
+ },
280
+ ],
281
+ ];
282
+ for (const [name, changed] of cases) {
283
+ await t.test(name, async subtest => {
284
+ const result = await auditPlanning(await fixture(subtest, { ...PLANNING_GATE, ...changed }));
285
+ assert.ok(codes(result).includes('traceability-mode'), JSON.stringify(result.diagnostics));
286
+ if (name !== 'misplaced' && name !== 'mapped from SRS') {
287
+ assert.ok(
288
+ result.diagnostics.some(
289
+ diagnostic =>
290
+ diagnostic.id === 'WBS-DEMO-10-01' && diagnostic.code === 'traceability-incoming',
291
+ ),
292
+ );
293
+ }
294
+ if (name !== 'misplaced' && name !== 'mapped to ATP' && name !== 'competing exception') {
295
+ assert.ok(
296
+ result.diagnostics.some(
297
+ diagnostic =>
298
+ diagnostic.id === 'WBS-DEMO-10-01' && diagnostic.code === 'traceability-outgoing',
299
+ ),
300
+ );
301
+ }
302
+ });
303
+ }
304
+ });
305
+
306
+ test('ordinary unmapped WBS retains its adjacency diagnostics beside a planning gate', async t => {
307
+ const records = {
308
+ ...PLANNING_GATE,
309
+ 'pdp-wbs.md': `${PLANNING_GATE['pdp-wbs.md']}\n#### WBS-DEMO-10-02: Implement item\n\nTasks:\n\n- Implement the item.\n`,
310
+ 'progress.md': `${PLANNING_GATE['progress.md']}| WBS-DEMO-10-02 | Implement item | not-started |\n`,
311
+ };
312
+ const result = await auditPlanning(await fixture(t, records));
313
+ assert.deepEqual(
314
+ result.diagnostics
315
+ .filter(diagnostic => diagnostic.id === 'WBS-DEMO-10-02')
316
+ .map(diagnostic => diagnostic.code),
317
+ ['traceability-incoming', 'traceability-outgoing'],
318
+ );
319
+ });
320
+
60
321
  test('canonical atomic bullets carry their own inline traceability', async t => {
61
322
  const records = {
62
323
  ...BASELINE,
@@ -361,6 +622,68 @@ test('completed tasks cannot hide unfinished recorded prerequisites', async t =>
361
622
  );
362
623
  });
363
624
 
625
+ test('planning-complete requires a task-local mode in full and lightweight audits', async t => {
626
+ const progress = BASELINE['progress.md'].replace('not-started |', 'planning-complete |');
627
+ for (const lightweight of [false, true]) {
628
+ const rejected = await auditPlanning(
629
+ await fixture(t, { ...BASELINE, 'progress.md': progress }),
630
+ { lightweight },
631
+ );
632
+ assert.ok(codes(rejected).includes('completion-mode'));
633
+ const approved = await auditPlanning(
634
+ await fixture(t, {
635
+ ...BASELINE,
636
+ 'pdp-wbs.md': BASELINE['pdp-wbs.md'].replace(
637
+ 'Completion checks:',
638
+ 'Completion mode: planning-only.\n\nCompletion checks:',
639
+ ),
640
+ 'progress.md': progress,
641
+ }),
642
+ { lightweight },
643
+ );
644
+ assert.equal(approved.ok, true, JSON.stringify(approved.diagnostics));
645
+ }
646
+ });
647
+
648
+ test('planning-complete prerequisites allow recorded closure but never unblock successors', async t => {
649
+ const wbs = `${BASELINE['pdp-wbs.md'].replace('Completion checks:', 'Completion mode: planning-only.\n\nCompletion checks:')}\n#### WBS-DEMO-10-02: Dependent review\n\nDependencies: WBS-DEMO-10-01.\n\nTraceability exception: authority-only — Retain the review decision.\n\nTraceability: \`SRS-DEMO-01\`.\n`;
650
+ const progress = `${BASELINE['progress.md'].replace('not-started |', 'planning-complete |')}| WBS-DEMO-10-02 | Dependent | planning-complete |\n`;
651
+ const records = { ...BASELINE, 'pdp-wbs.md': wbs, 'progress.md': progress };
652
+ const completed = await auditPlanning(await fixture(t, records));
653
+ assert.ok(codes(completed).includes('completion-mode'));
654
+ assert.ok(!codes(completed).includes('dependency-state'));
655
+ const blocked = await auditPlanning(
656
+ await fixture(t, {
657
+ ...records,
658
+ 'progress.md': progress.replace('Dependent | planning-complete', 'Dependent | blocked'),
659
+ }),
660
+ );
661
+ assert.ok(!codes(blocked).includes('dependency-state'));
662
+ assert.ok(!codes(blocked).includes('completion-mode'));
663
+ const unfinished = await auditPlanning(
664
+ await fixture(t, {
665
+ ...records,
666
+ 'progress.md': progress.replace('planning-complete |', 'implementation-complete |'),
667
+ }),
668
+ );
669
+ assert.ok(codes(unfinished).includes('dependency-state'));
670
+ const waived = await auditPlanning(
671
+ await fixture(t, {
672
+ ...records,
673
+ 'progress.md': progress.replace('planning-complete |', 'waived |'),
674
+ }),
675
+ );
676
+ assert.ok(codes(waived).includes('dependency-state'));
677
+ const noMode = await auditPlanning(
678
+ await fixture(t, {
679
+ ...records,
680
+ 'pdp-wbs.md': wbs.replace('Completion mode: planning-only.\n\n', ''),
681
+ }),
682
+ );
683
+ assert.ok(codes(noMode).includes('completion-mode'));
684
+ assert.ok(codes(noMode).includes('dependency-state'));
685
+ });
686
+
364
687
  test('local Markdown destinations, fragments and reference links are checked', async t => {
365
688
  const records = {
366
689
  ...BASELINE,
@@ -4,6 +4,8 @@ import { fileURLToPath } from 'node:url';
4
4
 
5
5
  import {
6
6
  findIdentifiers,
7
+ isPlanningCompleteEligible,
8
+ isPrerequisiteSatisfied,
7
9
  markdownLines,
8
10
  parseDefinitions,
9
11
  parseProgress,
@@ -160,6 +162,116 @@ function collectMappings(records, definitions) {
160
162
  return edges;
161
163
  }
162
164
 
165
+ function acceptedDecision(markdown) {
166
+ if (!markdown) return false;
167
+ const lines = markdownLines(markdown);
168
+ const statuses = [];
169
+ let section = null;
170
+ for (const [index, { text, heading }] of lines.entries()) {
171
+ if (heading?.level === 2) section = heading.text;
172
+ const field = text.match(/^\s*-\s*\*\*Status:\*\*/i);
173
+ if (field && (section === null || /^Status$/i.test(section)))
174
+ statuses.push(text.slice(field[0].length).trim());
175
+ if (!heading || !/^Status$/i.test(heading.text)) continue;
176
+ const value = lines.slice(index + 1).find(record => {
177
+ if (record.heading && record.heading.level <= heading.level) return true;
178
+ return record.text.trim();
179
+ });
180
+ if (value && !(value.heading && value.heading.level <= heading.level))
181
+ statuses.push(value.text.trim());
182
+ }
183
+ return statuses.length > 0 && statuses.every(status => /^Accepted\.?$/i.test(status));
184
+ }
185
+
186
+ function planningBaselineGates(records, wbs, edges, exceptions, report) {
187
+ const gates = new Set();
188
+ const field = /^\s*(?:-\s*)?(?:\*\*)?Traceability mode(?:\*\*)?\s*:/i;
189
+ const declarations = new Map();
190
+ for (const [file, markdown] of records) {
191
+ for (const { text, line } of markdownLines(markdown)) {
192
+ if (!field.test(text)) continue;
193
+ const task =
194
+ file === 'pdp-wbs.md'
195
+ ? wbs?.tasks.find(candidate => candidate.line < line && candidate.endLine >= line)
196
+ : null;
197
+ if (!task) {
198
+ report(
199
+ 'traceability-mode',
200
+ file,
201
+ line,
202
+ 'Traceability mode must be declared inside a WBS task.',
203
+ );
204
+ continue;
205
+ }
206
+ if (!declarations.has(task.id)) declarations.set(task.id, []);
207
+ declarations.get(task.id).push({ text, line });
208
+ }
209
+ }
210
+ for (const task of wbs?.tasks ?? []) {
211
+ const declared = declarations.get(task.id);
212
+ if (!declared) continue;
213
+ const [{ text, line }] = declared;
214
+ const mode = text.replace(field, '').trim().replace(/`/g, '');
215
+ const body = markdownLines(task.body).map(record => record.text);
216
+ const deliveryAction =
217
+ /(?:^|[,;]\s*|\b(?:and|then|to)\s+)(?:implement|deploy|migrate|release|regenerate|develop|ship|code|build\b(?!\s+risks?\b)|create|run\s+(?:an?\s+)?(?:migrations?|tests?|ATP-|scenarios?)|execute\s+(?:an?\s+)?(?:ATP-|tests?|scenarios?))\b/i;
218
+ const mixedScope =
219
+ deliveryAction.test(task.title) ||
220
+ body.some(value => {
221
+ const bullet = value.match(/^\s*[-*]\s+(.*)/);
222
+ return bullet && deliveryAction.test(bullet[1].replace(/^\*\*[^*]+\*\*\s*/, ''));
223
+ });
224
+ const hasItems = label =>
225
+ body.some((value, index) => {
226
+ if (value.trim().toLowerCase() !== `${label}:`) return false;
227
+ const next = body.slice(index + 1).find(line => line.trim());
228
+ return Boolean(next?.trim().match(/^[-*] \S/));
229
+ });
230
+ const authorities = body.flatMap(value => {
231
+ const match = value.match(/^\s*(?:-\s*)?(?:\*\*)?Review authority(?:\*\*)?\s*:/i);
232
+ return match ? [value.replace(match[0], '').trim()] : [];
233
+ });
234
+ const authority = authorities[0]?.match(
235
+ /^\[([^\]]+)\]\((\.\/decisions\/[^/()]+\.md)\)(?:\s*\([^\n]*\))?\s*\.?$/,
236
+ );
237
+ const adr = authority?.[2].slice(2);
238
+ const decision = adr && records.get(adr);
239
+ const mappedIncoming = [...edges].some(
240
+ ([id, targets]) => id.startsWith('SRS-') && targets.has(task.id),
241
+ );
242
+ const valid =
243
+ declared.length === 1 &&
244
+ mode === 'planning-baseline-review' &&
245
+ task.phase.number === '10' &&
246
+ /\b(?:review|freeze)\b/i.test(task.title) &&
247
+ /\bbaseline\b/i.test(task.title) &&
248
+ hasItems('tasks') &&
249
+ hasItems('acceptance checks') &&
250
+ !mixedScope &&
251
+ !findIdentifiers(task.title, 'SRS-').length &&
252
+ !findIdentifiers(task.title, 'ATP-').length &&
253
+ !findIdentifiers(task.body, 'SRS-').length &&
254
+ !findIdentifiers(task.body, 'ATP-').length &&
255
+ !mappedIncoming &&
256
+ !edges.get(task.id)?.size &&
257
+ !exceptions.has(task.id) &&
258
+ authorities.length === 1 &&
259
+ acceptedDecision(decision);
260
+ if (!valid) {
261
+ report(
262
+ 'traceability-mode',
263
+ 'pdp-wbs.md',
264
+ line,
265
+ 'Planning-baseline review requires one valid mode, Phase 10 baseline review, substantive review-only Tasks and Acceptance checks, no individual SRS/ATP mapping or other traceability exception, and one accepted local ADR review authority.',
266
+ task.id,
267
+ );
268
+ continue;
269
+ }
270
+ gates.add(task.id);
271
+ }
272
+ return gates;
273
+ }
274
+
163
275
  function headingIds(markdown) {
164
276
  const slugs = new Set();
165
277
  const counts = new Map();
@@ -362,10 +474,23 @@ export async function auditPlanning(
362
474
  task.id,
363
475
  );
364
476
  }
365
- if (['implementation-complete', 'verified'].includes(rows.get(task.id))) {
477
+ if (rows.get(task.id) === 'planning-complete' && !isPlanningCompleteEligible(task)) {
478
+ report(
479
+ 'completion-mode',
480
+ 'progress.md',
481
+ locations?.get(task.id)?.line ?? 1,
482
+ 'Planning-complete requires a declaration-local Completion mode: planning-only field.',
483
+ task.id,
484
+ );
485
+ }
486
+ if (
487
+ ['planning-complete', 'implementation-complete', 'verified'].includes(rows.get(task.id))
488
+ ) {
366
489
  for (const dependency of task.dependencyIds) {
490
+ const predecessor = wbs.tasks.find(item => item.id === dependency);
367
491
  if (
368
- ['not-started', 'in-progress', 'blocked', 'deferred'].includes(rows.get(dependency))
492
+ rows.has(dependency) &&
493
+ !isPrerequisiteSatisfied(predecessor, rows.get(dependency))
369
494
  ) {
370
495
  report(
371
496
  'dependency-state',
@@ -398,9 +523,10 @@ export async function auditPlanning(
398
523
  }
399
524
  if (!lightweight) {
400
525
  const edges = collectMappings(records, definitions);
526
+ const gates = planningBaselineGates(records, wbs, edges, exceptions, report);
401
527
  const incoming = new Set([...edges.values()].flatMap(values => [...values]));
402
528
  for (const [id, definition] of definitions) {
403
- if (definition.status === 'deferred' || definition.deferred) continue;
529
+ if (definition.status === 'deferred' || definition.deferred || gates.has(id)) continue;
404
530
  if (!id.startsWith('PRD-') && !incoming.has(id) && exceptions.get(id) !== 'technical-only') {
405
531
  report(
406
532
  'traceability-incoming',
@@ -2,12 +2,20 @@
2
2
  import { readFile, writeFile } from 'node:fs/promises';
3
3
  import { basename, resolve } from 'node:path';
4
4
 
5
- import { markdownLines, parseAtpIds, parseProgress, parseWbs } from './spec-parser.mjs';
5
+ import {
6
+ isPlanningCompleteEligible,
7
+ isPrerequisiteSatisfied,
8
+ markdownLines,
9
+ parseAtpIds,
10
+ parseProgress,
11
+ parseWbs,
12
+ } from './spec-parser.mjs';
6
13
  export { parseAtpIds, parseProgress, parseWbs } from './spec-parser.mjs';
7
14
 
8
15
  const STATUS_COLORS = {
9
16
  'not-started': 'var(--blue)',
10
17
  'in-progress': 'var(--orange)',
18
+ 'planning-complete': 'var(--teal)',
11
19
  'implementation-complete': 'var(--violet)',
12
20
  'verified': 'var(--aqua)',
13
21
  'blocked': 'var(--red)',
@@ -47,6 +55,7 @@ const COPY = {
47
55
  statuses: {
48
56
  'not-started': 'Not started',
49
57
  'in-progress': 'In progress',
58
+ 'planning-complete': 'Planning complete',
50
59
  'implementation-complete': 'Implementation complete',
51
60
  'verified': 'Verified',
52
61
  'blocked': 'Blocked',
@@ -104,6 +113,7 @@ const COPY = {
104
113
  statuses: {
105
114
  'not-started': '未开始',
106
115
  'in-progress': '进行中',
116
+ 'planning-complete': '规划完成',
107
117
  'implementation-complete': '实施完成',
108
118
  'verified': '已核验',
109
119
  'blocked': '受阻',
@@ -248,6 +258,11 @@ export function createChartModel({ readme, wbs, progress, testPlan }) {
248
258
  }
249
259
  task.recordedStatus = task.status;
250
260
  task.status = progressStatus;
261
+ if (task.status === 'planning-complete' && !isPlanningCompleteEligible(task)) {
262
+ throw new Error(
263
+ `${task.id} has planning-complete status without Completion mode: planning-only.`,
264
+ );
265
+ }
251
266
  if (task.deferred && task.status !== 'deferred') {
252
267
  throw new Error(
253
268
  `${task.id} is marked deferred in pdp-wbs.md but has progress status ${task.status}.`,
@@ -274,10 +289,10 @@ export function createChartModel({ readme, wbs, progress, testPlan }) {
274
289
  };
275
290
  }
276
291
 
277
- function style() {
292
+ function style(planningComplete) {
278
293
  return `<style>
279
- :root { --surface:#fcfcfb;--page:#f9f9f7;--ink:#0b0b0b;--secondary:#52514e;--muted:#898781;--grid:#e1e0d9;--axis:#c3c2b7;--blue:#2a78d6;--orange:#eb6834;--aqua:#1baf7a;--violet:#4a3aa7;--red:#e34948;--yellow:#eda100;--blue-wash:#eaf3fd;--aqua-wash:#e7f7f0; }
280
- @media (prefers-color-scheme:dark) { :root { --surface:#1a1a19;--page:#0d0d0d;--ink:#fff;--secondary:#c3c2b7;--muted:#a09f99;--grid:#2c2c2a;--axis:#383835;--blue:#3987e5;--orange:#d95926;--aqua:#199e70;--violet:#9085e9;--red:#e66767;--yellow:#c98500;--blue-wash:#172435;--aqua-wash:#122b23; } }
294
+ :root { --surface:#fcfcfb;--page:#f9f9f7;--ink:#0b0b0b;--secondary:#52514e;--muted:#898781;--grid:#e1e0d9;--axis:#c3c2b7;--blue:#2a78d6;--orange:#eb6834;${planningComplete ? '--teal:#087f83;' : ''}--aqua:#1baf7a;--violet:#4a3aa7;--red:#e34948;--yellow:#eda100;--blue-wash:#eaf3fd;--aqua-wash:#e7f7f0; }
295
+ @media (prefers-color-scheme:dark) { :root { --surface:#1a1a19;--page:#0d0d0d;--ink:#fff;--secondary:#c3c2b7;--muted:#a09f99;--grid:#2c2c2a;--axis:#383835;--blue:#3987e5;--orange:#d95926;${planningComplete ? '--teal:#29aeb0;' : ''}--aqua:#199e70;--violet:#9085e9;--red:#e66767;--yellow:#c98500;--blue-wash:#172435;--aqua-wash:#122b23; } }
281
296
  .canvas{fill:var(--page)}.card{fill:var(--surface);stroke:var(--grid);stroke-width:1}.title{fill:var(--ink);font-family:Arial,Helvetica,sans-serif;font-size:27px;font-weight:700}.subtitle{fill:var(--secondary);font-family:Arial,Helvetica,sans-serif;font-size:14px}.heading{fill:var(--ink);font-family:Arial,Helvetica,sans-serif;font-size:13px;font-weight:700}.phase{fill:var(--ink);font-family:Arial,Helvetica,sans-serif;font-size:10px;font-weight:700}.task{fill:var(--ink);font-family:Arial,Helvetica,sans-serif;font-size:10px}.small{fill:var(--muted);font-family:Arial,Helvetica,sans-serif;font-size:9px}.caption{fill:var(--secondary);font-family:Arial,Helvetica,sans-serif;font-size:11px}.num{fill:var(--ink);font-family:Arial,Helvetica,sans-serif;font-size:11px;font-weight:700;font-variant-numeric:tabular-nums}.grid{stroke:var(--grid);stroke-width:1;shape-rendering:crispEdges}.axis{stroke:var(--axis);stroke-width:1;shape-rendering:crispEdges}.row:focus{outline:2px solid var(--ink);outline-offset:2px}
282
297
  </style>`;
283
298
  }
@@ -302,13 +317,13 @@ function renderChartTextLines(className, x, y, lines, lineHeight) {
302
317
  return `<text class="${className}" x="${x}" y="${y}">${lines.map((line, lineIndex) => `<tspan x="${x}" dy="${lineIndex ? lineHeight : 0}">${escapeXml(line)}</tspan>`).join('')}</text>`;
303
318
  }
304
319
 
305
- function svgDocument(title, desc, body, height, metadata) {
320
+ function svgDocument(title, desc, body, height, metadata, planningComplete = false) {
306
321
  return `<?xml version="1.0" encoding="UTF-8"?>
307
322
  <svg xmlns="http://www.w3.org/2000/svg" width="1600" height="${height}" viewBox="0 0 1600 ${height}" role="img" aria-labelledby="chart-title chart-desc">
308
323
  <title id="chart-title">${escapeXml(title)}</title>
309
324
  <desc id="chart-desc">${escapeXml(desc)}</desc>
310
325
  <metadata>${escapeXml(metadata)}</metadata>
311
- ${style()}
326
+ ${style(planningComplete)}
312
327
  ${body}
313
328
  </svg>
314
329
  `;
@@ -392,9 +407,12 @@ export function renderGantt(model, suiteName) {
392
407
  },
393
408
  )
394
409
  .join('\n');
395
- const completed = new Set(tasks.filter(task => task.status === 'verified').map(task => task.id));
410
+ const completed = new Set(
411
+ tasks.filter(task => isPrerequisiteSatisfied(task, task.status)).map(task => task.id),
412
+ );
396
413
  const nextTask = tasks.find(
397
414
  task =>
415
+ task.status !== 'planning-complete' &&
398
416
  task.status !== 'verified' &&
399
417
  task.status !== 'deferred' &&
400
418
  task.status !== 'waived' &&
@@ -418,6 +436,7 @@ export function renderGantt(model, suiteName) {
418
436
  body,
419
437
  height,
420
438
  t.metadata,
439
+ tasks.some(task => task.status === 'planning-complete'),
421
440
  );
422
441
  }
423
442
 
@@ -152,6 +152,88 @@ test('creates a mixed-status model with deferred scope separated', () => {
152
152
  assert.equal(model.activeTasks.length, 2);
153
153
  assert.equal(model.deferredTasks.length, 1);
154
154
  assert.equal(model.verified, 0);
155
+ assert.doesNotMatch(renderGantt(model, 'Demo'), /--teal:/);
156
+ });
157
+
158
+ test('planning completion requires an explicit task mode and preserves verified-only metrics', () => {
159
+ const wbs = files['pdp-wbs.md'].replace(
160
+ '#### WBS-10-01: Freeze the baseline',
161
+ '#### WBS-10-01: Freeze the baseline\n\nCompletion mode: planning-only.',
162
+ );
163
+ const progress = files['progress.md'].replace('`not-started`', '`planning-complete`');
164
+ assert.throws(
165
+ () =>
166
+ createChartModel({
167
+ readme: files['README.md'],
168
+ wbs: files['pdp-wbs.md'],
169
+ progress,
170
+ testPlan: files['test-plan.md'],
171
+ }),
172
+ /planning-complete status without Completion mode/,
173
+ );
174
+ const model = createChartModel({
175
+ readme: files['README.md'],
176
+ wbs,
177
+ progress,
178
+ testPlan: files['test-plan.md'],
179
+ });
180
+ assert.equal(model.tasks[0].completionMode, 'planning-only');
181
+ assert.equal(model.verified, 0);
182
+ const gantt = renderGantt(model, 'Demo');
183
+ assert.match(gantt, /Planning complete/);
184
+ assert.match(gantt, /--teal:#087f83/);
185
+ assert.doesNotMatch(renderBurndown(model, 'Demo'), /--teal:/);
186
+ assert.match(gantt, /class="caption" x="96" y="195">WBS-20-01: Deliver/);
187
+ assert.doesNotMatch(gantt, /class="caption" x="96" y="195">WBS-10-01/);
188
+ assert.match(renderBurndown(model, 'Demo'), />2 remaining · 0 verified</);
189
+ const blocked = createChartModel({
190
+ readme: files['README.md'],
191
+ wbs,
192
+ progress: progress.replace('`in-progress`', '`blocked`'),
193
+ testPlan: files['test-plan.md'],
194
+ });
195
+ assert.match(renderGantt(blocked, 'Demo'), /No dependency-ready WBS candidate is recorded/);
196
+ assert.equal(blocked.verified, 0);
197
+ const chinese = createChartModel({
198
+ readme: '# 中文规划\n\n完整中文记录。',
199
+ wbs,
200
+ progress,
201
+ testPlan: files['test-plan.md'],
202
+ });
203
+ assert.match(renderGantt(chinese, '示例'), /规划完成/);
204
+ assert.match(renderBurndown(chinese, '示例'), /2 剩余 · 0 已核验/);
205
+ });
206
+
207
+ test('completion mode must be unique, supported and task-local', () => {
208
+ const wbs = files['pdp-wbs.md'];
209
+ for (const value of ['unknown.', '', 'planning-only.\nCompletion mode: planning-only.']) {
210
+ assert.throws(
211
+ () => parseWbs(wbs.replace('Tasks:', `Completion mode: ${value}\n\nTasks:`)),
212
+ /Completion mode/,
213
+ );
214
+ }
215
+ assert.throws(
216
+ () =>
217
+ parseWbs(
218
+ wbs.replace('Dependencies: none.', 'Dependencies: none.\nCompletion mode: planning-only.'),
219
+ ),
220
+ /Completion mode must belong to a formal WBS task/,
221
+ );
222
+ assert.throws(
223
+ () => parseWbs(`Completion mode: planning-only.\n${wbs}`),
224
+ /Completion mode must belong to a formal WBS task/,
225
+ );
226
+ const progress = files['progress.md'].replace('`not-started`', '`planning-complete`');
227
+ assert.throws(
228
+ () =>
229
+ createChartModel({
230
+ readme: files['README.md'],
231
+ wbs: wbs.replace('Tasks:', 'Status: in-progress.\nCompletion mode: planning-only.\nTasks:'),
232
+ progress,
233
+ testPlan: files['test-plan.md'],
234
+ }),
235
+ /explicit.*Status in-progress.*planning-complete/,
236
+ );
155
237
  });
156
238
 
157
239
  test('selects the earliest dependency-ready WBS item', async t => {
@@ -2,6 +2,7 @@
2
2
  export const STATUS_ORDER = [
3
3
  'not-started',
4
4
  'in-progress',
5
+ 'planning-complete',
5
6
  'implementation-complete',
6
7
  'verified',
7
8
  'blocked',
@@ -306,6 +307,16 @@ function normalizeStatus(value) {
306
307
  return normalized;
307
308
  }
308
309
 
310
+ export function isPlanningCompleteEligible(task) {
311
+ return task.completionMode === 'planning-only';
312
+ }
313
+
314
+ export function isPrerequisiteSatisfied(task, status) {
315
+ return (
316
+ status === 'verified' || (status === 'planning-complete' && isPlanningCompleteEligible(task))
317
+ );
318
+ }
319
+
309
320
  function reviewDateFromLine(line) {
310
321
  return (
311
322
  line.match(/(?:last reviewed|最后审查日期)\s*(?:[::]\s*)?(\d{4}-\d{2}-\d{2})/i)?.[1] ?? null
@@ -360,7 +371,7 @@ export function parseProgress(markdown, { file } = {}) {
360
371
  }
361
372
 
362
373
  function labeledValue(record, labels) {
363
- const match = record.text.trim().match(/^([A-Z]+)\s*:(.*)$/i);
374
+ const match = record.text.trim().match(/^([A-Z]+(?:\s+[A-Z]+)*)\s*:(.*)$/i);
364
375
  return match && labels.includes(match[1].toLowerCase()) ? match[2].trim() : null;
365
376
  }
366
377
 
@@ -502,6 +513,11 @@ export function parseWbs(markdown, { file } = {}) {
502
513
  task = null;
503
514
  inPhase = false;
504
515
  }
516
+ if (!task && labeledValue(record, ['completion mode']) !== null) {
517
+ throw new Error(
518
+ `Completion mode must belong to a formal WBS task at ${atLocation(record, file)}.`,
519
+ );
520
+ }
505
521
  if (!inPhase) continue;
506
522
  if (task) task.body.push(record);
507
523
  else if (!phase.tasks.length) phase.preamble.push(record);
@@ -519,6 +535,29 @@ export function parseWbs(markdown, { file } = {}) {
519
535
  ? { line: dependency.line, ...(file ? { file } : {}) }
520
536
  : undefined;
521
537
  task.phase = { number: phase.number, title: phase.title };
538
+ const modeRecords = task.body.filter(
539
+ record => labeledValue(record, ['completion mode']) !== null,
540
+ );
541
+ if (modeRecords.length > 1) {
542
+ throw new Error(
543
+ `Multiple ${task.id} Completion mode fields at ${atLocation(modeRecords[1], file)}.`,
544
+ );
545
+ }
546
+ const mode = modeRecords.length
547
+ ? labeledValue(modeRecords[0], ['completion mode'])
548
+ .replace(/`/g, '')
549
+ .replace(/\.$/, '')
550
+ .toLowerCase()
551
+ : undefined;
552
+ if (mode && mode !== 'planning-only') {
553
+ throw new Error(
554
+ `Unsupported ${task.id} Completion mode at ${atLocation(modeRecords[0], file)}.`,
555
+ );
556
+ }
557
+ if (modeRecords.length && !mode) {
558
+ throw new Error(`Empty ${task.id} Completion mode at ${atLocation(modeRecords[0], file)}.`);
559
+ }
560
+ task.completionMode = mode;
522
561
  const statusRecords = task.body.filter(record => labeledValue(record, ['status']) !== null);
523
562
  if (statusRecords.length > 1) {
524
563
  throw new Error(
@@ -53,6 +53,7 @@ For the detailed authority table, identifiers, and chart boundary, see [Generate
53
53
 
54
54
  **Evidence-gated delivery** means delivery status stays precise about what is known and what has been proven.
55
55
 
56
+ - `planning-complete` means an explicitly `planning-only` WBS task's documentary checks have a named-reviewer, revision-scoped closure disposition and retained planning proof. It satisfies that task's prerequisite edge, not implementation, ATP, successor approval, or release.
56
57
  - `implementation-complete` means the source work is complete, while required ATP or release proof remains.
57
58
  - `verified` means all applicable WBS checks and ATPs have durable, linked, redacted observed evidence.
58
59
 
@@ -109,11 +109,12 @@ Use the narrowest approved check first, then follow the linked ATP procedures an
109
109
 
110
110
  Use status precisely:
111
111
 
112
- - `in-progress` — approved implementation or verification has actually begun
112
+ - `in-progress` — approved work or verification has actually begun
113
+ - `planning-complete` — only an opted-in documentary/design WBS task with a named-reviewer, revision-scoped closure disposition and linked planning proof; no source or ATP closure
113
114
  - `implementation-complete` — source work is complete, but required ATP or release proof remains
114
115
  - `verified` — all applicable WBS checks and ATPs have durable, linked, redacted observed evidence
115
116
 
116
- A successful build, generation command, hook, manual walkthrough, screenshot, or unrelated test run is not by itself `verified` unless the authoritative test plan defines it as sufficient retained proof. Evidence is revision- and authority-scoped; mark old proof superseded or requiring rerun when relevant source or authority changes.
117
+ `planning-complete` satisfies a predecessor dependency edge but does not unblock a task still recorded `blocked` or grant an execution dossier. Charts count only `verified` toward verified/remaining metrics. A successful build, generation command, hook, manual walkthrough, screenshot, or unrelated test run is not by itself `verified` unless the authoritative test plan defines it as sufficient retained proof. Evidence is revision- and authority-scoped; mark old proof superseded or requiring rerun when relevant source or authority changes.
117
118
 
118
119
  ## Refresh derived charts last
119
120
 
@@ -115,7 +115,7 @@ PRD → SRS → WBS → ATP → Evidence
115
115
 
116
116
  A product or technical change belongs in its PRD, SRS, or accepted ADR before its WBS, acceptance, progress, evidence, and chart implications are updated. A progress entry or chart cannot introduce a requirement, resolve a contract conflict, or accept an ADR.
117
117
 
118
- Planning records and derived charts do not establish `implementation-complete` or `verified`. `verified` requires the applicable acceptance procedure and retained, redacted observed evidence. A generated plan, planned command, scaffold, screenshot, or unrelated check is not automatically sufficient proof.
118
+ A WBS task may declare `Completion mode: planning-only.` only for documentary/design-only checks. It can reach `planning-complete` after an explicit named-reviewer, revision-scoped closure disposition and linked planning proof; this satisfies its dependency edge without executing source, passing future ATPs, unblocking a recorded `blocked` successor, or counting as `verified` in charts. Planning records and derived charts do not establish `implementation-complete` or `verified`. `verified` requires the applicable acceptance procedure and retained, redacted observed evidence. A generated plan, planned command, scaffold, screenshot, or unrelated check is not automatically sufficient proof.
119
119
 
120
120
  ## Check planning without claiming implementation
121
121