@dzhechkov/keysarium 1.5.36 → 1.5.37

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.
Files changed (30) hide show
  1. package/.dz-manifest.json +59 -19
  2. package/README.md +41 -4
  3. package/package.json +1 -1
  4. package/sbom.json +118 -18
  5. package/templates/.claude/skills/feature-adr/SKILL.md +48 -7
  6. package/templates/.claude/skills/feature-adr/modules/01-requirements.md +26 -0
  7. package/templates/.claude/skills/feature-adr/modules/02-research.md +26 -0
  8. package/templates/.claude/skills/feature-adr/modules/03-adr.md +26 -0
  9. package/templates/.claude/skills/feature-adr/modules/03.5-ideation-swarm.md +25 -0
  10. package/templates/.claude/skills/feature-adr/modules/04-ddd.md +25 -0
  11. package/templates/.claude/skills/feature-adr/modules/05-architecture.md +26 -0
  12. package/templates/.claude/skills/feature-adr/modules/06-implementation-plan.md +74 -4
  13. package/templates/.claude/skills/feature-adr/modules/08-qe.md +14 -1
  14. package/templates/.claude/skills/feature-adr/scripts/check-plan-completeness.mjs +166 -0
  15. package/templates/.claude/skills/goap-research-ed25519/SKILL.md +340 -47
  16. package/templates/.claude/skills/goap-research-ed25519/scripts/check_report_evidence.py +359 -3
  17. package/templates/.claude/skills/goap-research-ed25519/scripts/ed25519_verifier.py +386 -13
  18. package/templates/.claude/skills/goap-research-ed25519/scripts/fixture_legacy_v2_fact.json +23 -0
  19. package/templates/.claude/skills/goap-research-ed25519/scripts/fixtures_field_cases.json +133 -0
  20. package/templates/.claude/skills/goap-research-ed25519/scripts/goap_planner.py +314 -44
  21. package/templates/.claude/skills/goap-research-ed25519/scripts/learning_bridge.py +890 -303
  22. package/templates/.claude/skills/goap-research-ed25519/scripts/population_match.py +591 -0
  23. package/templates/.claude/skills/goap-research-ed25519/scripts/risk_statement.py +289 -0
  24. package/templates/.claude/skills/goap-research-ed25519/scripts/test_ed25519_verifier.py +57 -2
  25. package/templates/.claude/skills/goap-research-ed25519/scripts/test_evidence_provenance.py +969 -344
  26. package/templates/.claude/skills/goap-research-ed25519/scripts/test_goap_planner.py +420 -0
  27. package/templates/.claude/skills/goap-research-ed25519/scripts/test_population_match.py +544 -0
  28. package/templates/.claude/skills/goap-research-ed25519/scripts/test_risk_absolute.py +239 -0
  29. package/templates/.claude/skills/goap-research-ed25519/scripts/test_signature_v3.py +554 -0
  30. package/templates/.claude/skills/goap-research-ed25519/scripts/test_suite_completeness.py +90 -0
@@ -77,6 +77,32 @@ Explicitly state:
77
77
  - **Dependencies:** What must exist before this feature works
78
78
  - **Dependents:** What will break if this feature changes
79
79
 
80
+ ## Write discipline (the 180-second rule)
81
+
82
+ An executor that returns from a tool call and then thinks in silence past **180 seconds** is killed by the
83
+ runtime. Thinking time grows with the history already accumulated, so on a large repo "read everything,
84
+ then write the document" is not a risk — it is a deterministic death, and nothing survives it, because
85
+ nothing was ever on disk.
86
+
87
+ MEASURED on this harness: the writing steps died **18 times out of 18** in the reading phase without ever
88
+ writing a file. The control — same slice, same model, one added instruction to write a skeleton early —
89
+ landed the skeleton 8 minutes in, on the first attempt, after six consecutive deaths.
90
+
91
+ So, in this step:
92
+
93
+ 1. **Skeleton first — inside your first ~12 tool calls.** Write `features/<slug>/01_requirements.md` containing only the headings this step
94
+ requires (Stakeholders · Functional requirements · Non-functional requirements · Constraints ·
95
+ Scope boundaries · Open questions), one line of intent under each. (S-tier is inline and has no
96
+ file — this section does not apply to it.)
97
+ 2. **Then fill it one section per edit.** No single edit longer than ~120 lines. Every edit leaves the
98
+ file readable; none of them is allowed to wait for the section after it.
99
+ 3. **Never go more than 2 minutes without a tool call.** A thought that is getting long is the signal to
100
+ stop and write what you have — an edit is a checkpoint, not an interruption.
101
+ 4. **When you are unsure whether to read more or to write, WRITE.** A thin section refined later survives;
102
+ a perfect section you never reached does not.
103
+
104
+ The skeleton is not a draft to apologise for. It is the artifact, opened early.
105
+
80
106
  ## Output
81
107
 
82
108
  ### For S-tier (inline)
@@ -73,6 +73,32 @@ Alternative considered: [Y], rejected because [reasons].
73
73
 
74
74
  After both complete, synthesize results sequentially.
75
75
 
76
+ ## Write discipline (the 180-second rule)
77
+
78
+ An executor that returns from a tool call and then thinks in silence past **180 seconds** is killed by the
79
+ runtime. Thinking time grows with the history already accumulated, so on a large repo "read everything,
80
+ then write the document" is not a risk — it is a deterministic death, and nothing survives it, because
81
+ nothing was ever on disk.
82
+
83
+ MEASURED on this harness: the writing steps died **18 times out of 18** in the reading phase without ever
84
+ writing a file. The control — same slice, same model, one added instruction to write a skeleton early —
85
+ landed the skeleton 8 minutes in, on the first attempt, after six consecutive deaths.
86
+
87
+ So, in this step:
88
+
89
+ 1. **Skeleton first — inside your first ~12 tool calls.** Write `features/<slug>/02_research.md` containing only the headings this step
90
+ requires (Codebase patterns · External patterns · Library evaluation · Anti-patterns ·
91
+ Recommended approach), one line of intent under each — before the research agents report back,
92
+ not after.
93
+ 2. **Then fill it one section per edit.** No single edit longer than ~120 lines. Every edit leaves the
94
+ file readable; none of them is allowed to wait for the section after it.
95
+ 3. **Never go more than 2 minutes without a tool call.** A thought that is getting long is the signal to
96
+ stop and write what you have — an edit is a checkpoint, not an interruption.
97
+ 4. **When you are unsure whether to read more or to write, WRITE.** A thin section refined later survives;
98
+ a perfect section you never reached does not.
99
+
100
+ The skeleton is not a draft to apologise for. It is the artifact, opened early.
101
+
76
102
  ## Output
77
103
 
78
104
  Create `features/<slug>/02_research.md` with:
@@ -167,6 +167,32 @@ Each ADR must trace back to specific requirements:
167
167
  | Placeholder text | Template hints or TODOs remain | Remove or fill before checkpoint |
168
168
  | Supersession in-place | Existing ADR rewritten | Mint a new ADR and link it |
169
169
 
170
+ ## Write discipline (the 180-second rule)
171
+
172
+ An executor that returns from a tool call and then thinks in silence past **180 seconds** is killed by the
173
+ runtime. Thinking time grows with the history already accumulated, so on a large repo "read everything,
174
+ then write the document" is not a risk — it is a deterministic death, and nothing survives it, because
175
+ nothing was ever on disk.
176
+
177
+ MEASURED on this harness: the writing steps died **18 times out of 18** in the reading phase without ever
178
+ writing a file. The control — same slice, same model, one added instruction to write a skeleton early —
179
+ landed the skeleton 8 minutes in, on the first attempt, after six consecutive deaths.
180
+
181
+ So, in this step:
182
+
183
+ 1. **Skeleton first — inside your first ~12 tool calls.** For each decision you have identified, write
184
+ `features/<slug>/03_adr/NNN-{decision-slug}.md` containing only the invariant-core headings
185
+ (Status · Context · Decision Drivers · Considered Options · Decision · Rationale · Consequences ·
186
+ Confirmation), one line of intent under each. A named-but-empty option beats an unwritten ADR.
187
+ 2. **Then fill it one section per edit.** No single edit longer than ~120 lines. Every edit leaves the
188
+ file readable; none of them is allowed to wait for the section after it.
189
+ 3. **Never go more than 2 minutes without a tool call.** A thought that is getting long is the signal to
190
+ stop and write what you have — an edit is a checkpoint, not an interruption.
191
+ 4. **When you are unsure whether to read more or to write, WRITE.** A thin section refined later survives;
192
+ a perfect section you never reached does not.
193
+
194
+ The skeleton is not a draft to apologise for. It is the artifact, opened early.
195
+
170
196
  ## Output
171
197
 
172
198
  Create `features/<slug>/03_adr/` directory with:
@@ -154,6 +154,31 @@ AM-N (source): <change>. Confirmation: <property> → test `test_name` (fails if
154
154
  an existence test and never fires in production.
155
155
  - A cheap lint holds the shape: every `AM-N` line must contain a `→ test ` token naming a test that exists.
156
156
 
157
+ ## Write discipline (the 180-second rule)
158
+
159
+ An executor that returns from a tool call and then thinks in silence past **180 seconds** is killed by the
160
+ runtime. Thinking time grows with the history already accumulated, so on a large repo "read everything,
161
+ then write the document" is not a risk — it is a deterministic death, and nothing survives it, because
162
+ nothing was ever on disk.
163
+
164
+ MEASURED on this harness: the writing steps died **18 times out of 18** in the reading phase without ever
165
+ writing a file. The control — same slice, same model, one added instruction to write a skeleton early —
166
+ landed the skeleton 8 minutes in, on the first attempt, after six consecutive deaths.
167
+
168
+ So, in this step:
169
+
170
+ 1. **Skeleton first — inside your first ~12 tool calls.** Write `features/<slug>/03.5_ideation_report.md` containing only the eight
171
+ section headings of §5 (Executive summary … Amendments), one line of intent under each — while
172
+ the swarm is still running, not after every agent has returned.
173
+ 2. **Then fill it one section per edit.** No single edit longer than ~120 lines. Every edit leaves the
174
+ file readable; none of them is allowed to wait for the section after it.
175
+ 3. **Never go more than 2 minutes without a tool call.** A thought that is getting long is the signal to
176
+ stop and write what you have — an edit is a checkpoint, not an interruption.
177
+ 4. **When you are unsure whether to read more or to write, WRITE.** A thin section refined later survives;
178
+ a perfect section you never reached does not.
179
+
180
+ The skeleton is not a draft to apologise for. It is the artifact, opened early.
181
+
157
182
  ## Output
158
183
 
159
184
  Create `features/<slug>/03.5_ideation_report.md` with full QCSD report.
@@ -92,6 +92,31 @@ Check that proposed domain model is compatible with existing code:
92
92
  - Can aggregates be implemented with existing ORM/framework?
93
93
  - Do relationships align with existing data access patterns?
94
94
 
95
+ ## Write discipline (the 180-second rule)
96
+
97
+ An executor that returns from a tool call and then thinks in silence past **180 seconds** is killed by the
98
+ runtime. Thinking time grows with the history already accumulated, so on a large repo "read everything,
99
+ then write the document" is not a risk — it is a deterministic death, and nothing survives it, because
100
+ nothing was ever on disk.
101
+
102
+ MEASURED on this harness: the writing steps died **18 times out of 18** in the reading phase without ever
103
+ writing a file. The control — same slice, same model, one added instruction to write a skeleton early —
104
+ landed the skeleton 8 minutes in, on the first attempt, after six consecutive deaths.
105
+
106
+ So, in this step:
107
+
108
+ 1. **Skeleton first — inside your first ~12 tool calls.** Write `features/<slug>/04_domain_model.md` containing only the headings this step
109
+ requires (Bounded context map · Ubiquitous language · Aggregates/Entities/VOs · Relationships ·
110
+ Codebase mapping), one line of intent under each.
111
+ 2. **Then fill it one section per edit.** No single edit longer than ~120 lines. Every edit leaves the
112
+ file readable; none of them is allowed to wait for the section after it.
113
+ 3. **Never go more than 2 minutes without a tool call.** A thought that is getting long is the signal to
114
+ stop and write what you have — an edit is a checkpoint, not an interruption.
115
+ 4. **When you are unsure whether to read more or to write, WRITE.** A thin section refined later survives;
116
+ a perfect section you never reached does not.
117
+
118
+ The skeleton is not a draft to apologise for. It is the artifact, opened early.
119
+
95
120
  ## Output
96
121
 
97
122
  Create `features/<slug>/04_domain_model.md` with:
@@ -118,6 +118,32 @@ GET /api/v1/{resource}/:id
118
118
  Errors: 401, 404
119
119
  ```
120
120
 
121
+ ## Write discipline (the 180-second rule)
122
+
123
+ An executor that returns from a tool call and then thinks in silence past **180 seconds** is killed by the
124
+ runtime. Thinking time grows with the history already accumulated, so on a large repo "read everything,
125
+ then write the document" is not a risk — it is a deterministic death, and nothing survives it, because
126
+ nothing was ever on disk.
127
+
128
+ MEASURED on this harness: the writing steps died **18 times out of 18** in the reading phase without ever
129
+ writing a file. The control — same slice, same model, one added instruction to write a skeleton early —
130
+ landed the skeleton 8 minutes in, on the first attempt, after six consecutive deaths.
131
+
132
+ So, in this step:
133
+
134
+ 1. **Skeleton first — inside your first ~12 tool calls.** Write `features/<slug>/05_architecture.md` containing only the headings this step
135
+ requires at your tier (C4 Context · C4 Container · C4 Component · Sequence flows · Data & storage ·
136
+ API design), one line of intent under each. An empty `mermaid` fence with a caption is a
137
+ heading; fill it later.
138
+ 2. **Then fill it one section per edit.** No single edit longer than ~120 lines. Every edit leaves the
139
+ file readable; none of them is allowed to wait for the section after it.
140
+ 3. **Never go more than 2 minutes without a tool call.** A thought that is getting long is the signal to
141
+ stop and write what you have — an edit is a checkpoint, not an interruption.
142
+ 4. **When you are unsure whether to read more or to write, WRITE.** A thin section refined later survives;
143
+ a perfect section you never reached does not.
144
+
145
+ The skeleton is not a draft to apologise for. It is the artifact, opened early.
146
+
121
147
  ## Output
122
148
 
123
149
  ### M-tier
@@ -4,8 +4,11 @@
4
4
 
5
5
  ## When
6
6
 
7
- Always runs. Adapts depth by tier:
8
- - **S:** Inline checklist (3-5 items, no file)
7
+ Always runs, and ALWAYS writes `features/<slug>/06_implementation_plan.md` — every tier. Depth adapts:
8
+ - **S:** minimal plan FILE (3-5 tasks + an `EXPECTED_CODE_TARGETS:` block). An inline-only checklist is
9
+ no longer permitted: the K2 gate below is mandatory at every tier and it reads the file, so a plan
10
+ that exists only in the conversation makes the gate answer NOT-ESTABLISHED and the run stops. The
11
+ file must clear the gate's own floor: >200 characters and a non-empty targets block.
9
12
  - **M/L/XL:** Full structured plan document
10
13
 
11
14
  ## Model
@@ -132,15 +135,45 @@ Before finalizing the plan, validate completeness:
132
135
 
133
136
  This gap-check loop ensures the implementation plan is complete before coding begins.
134
137
 
138
+ ## Write discipline (the 180-second rule)
139
+
140
+ An executor that returns from a tool call and then thinks in silence past **180 seconds** is killed by the
141
+ runtime. Thinking time grows with the history already accumulated, so on a large repo "read everything,
142
+ then write the document" is not a risk — it is a deterministic death, and nothing survives it, because
143
+ nothing was ever on disk.
144
+
145
+ MEASURED on this harness: the writing steps died **18 times out of 18** in the reading phase without ever
146
+ writing a file. The control — same slice, same model, one added instruction to write a skeleton early —
147
+ landed the skeleton 8 minutes in, on the first attempt, after six consecutive deaths.
148
+
149
+ So, in this step:
150
+
151
+ 1. **Skeleton first — inside your first ~12 tool calls.** Write `features/<slug>/06_implementation_plan.md` containing only the headings
152
+ this step requires (Goal state · Tasks · Dependency order · Parallel groups · Checkpoints · Risk
153
+ assessment · Amendments · `EXPECTED_CODE_TARGETS:`), one line of intent under each. The K2 gate
154
+ reads this file — a plan that lives only in the conversation cannot pass it.
155
+ 2. **Then fill it one section per edit.** No single edit longer than ~120 lines. Every edit leaves the
156
+ file readable; none of them is allowed to wait for the section after it.
157
+ 3. **Never go more than 2 minutes without a tool call.** A thought that is getting long is the signal to
158
+ stop and write what you have — an edit is a checkpoint, not an interruption.
159
+ 4. **When you are unsure whether to read more or to write, WRITE.** A thin section refined later survives;
160
+ a perfect section you never reached does not.
161
+
162
+ The skeleton is not a draft to apologise for. It is the artifact, opened early.
163
+
135
164
  ## Output
136
165
 
137
- ### S-tier (inline)
138
- Inline checklist in conversation:
166
+ ### S-tier (minimal file — NOT inline-only)
167
+ Write `features/<slug>/06_implementation_plan.md` with the checklist plus the machine-read block, and
168
+ show the same checklist inline at the checkpoint:
139
169
  ```
140
170
  Implementation plan:
141
171
  - [ ] {task 1}
142
172
  - [ ] {task 2}
143
173
  - [ ] {task 3}
174
+
175
+ EXPECTED_CODE_TARGETS:
176
+ - {repo-relative path Step 7 will create or modify}
144
177
  ```
145
178
 
146
179
  ### M/L/XL
@@ -157,6 +190,41 @@ Create `features/<slug>/06_implementation_plan.md` with:
157
190
 
158
191
  Set `{IMPL_PLAN}` variable.
159
192
 
193
+ ## K2 plan-completeness gate (MANDATORY — Step 7 does not start until it exits 0)
194
+
195
+ After the plan is written, run the gate. It is a script, not a judgement call:
196
+
197
+ ```bash
198
+ node .claude/skills/feature-adr/scripts/check-plan-completeness.mjs features/<slug>
199
+ ```
200
+
201
+ | Exit | Verdict | What you do |
202
+ |------|---------|-------------|
203
+ | `0` | PASS | proceed to Step 7 |
204
+ | `1` | FAIL | **return to Step 6** — fix the plan for every `FAIL C*` line, rerun the gate |
205
+ | `3` | NOT-ESTABLISHED | **INCONCLUSIVE** — the gate could not read its inputs (no plan, plan under the size floor, ADR claims it cannot see). Fix the inputs and rerun. Never proceed. |
206
+
207
+ Never proceed on a non-zero exit, and never treat empty output as a pass — the last line
208
+ (`K2 plan-completeness: PASS|FAIL|NOT-ESTABLISHED`) is the verdict, and its absence is not one.
209
+ What it checks: C1 every ADR has a plan task citing it · C2 every ADR Confirmation test path is named
210
+ in the plan · C3 the `EXPECTED_CODE_TARGETS:` block parses line by line · C4 the feature's declared
211
+ acid corpus is named · C5 (WARN) the `Inputs read:` line. An S-tier run with no `03_adr/` skips C1/C2
212
+ with a note (it cannot be failed for ADRs it never had) — unless the plan itself cites `ADR-<n>`,
213
+ which is NOT-ESTABLISHED. C1 is a grep: it catches "forgot entirely", not "mentioned but not tasked".
214
+
215
+ Pass the run's tier so the check cannot be dodged: `--tier=S|M|L|XL`. An M/L/XL feature with no
216
+ `03_adr/` FAILS C1/C2 (an M+ feature owes ADRs); only `--tier=S` — or no tier at all, and then the
217
+ skip note says so — takes the skip. A declared-but-malformed acid table (lowercase `| a1 |`, a header
218
+ with no parsable rows) FAILS as `C4-malformed`; it is not a skip.
219
+
220
+ **If the gate fails in the ultracode workflow:** the run returns `phase: 'plan-gate-failed'` and the
221
+ plan stage is CHECKPOINTED, so a bare re-invoke resumes the same failing plan. Repair it from outside:
222
+ edit `features/<slug>/06_implementation_plan.md` to fix each `FAIL` line and re-invoke (the checkpoint
223
+ is keyed on run INPUTS, not on the file, so your edit survives), or re-invoke with `args.resume:
224
+ 'never'` (or delete `features/<slug>/.fa-state/`) to force a fresh plan.
225
+
226
+ The checkpoint banner's Gates line carries the verdict: `K2 plan-completeness ✓ | ✗ | inconclusive`.
227
+
160
228
  ## Checkpoint Format
161
229
 
162
230
  ```
@@ -167,6 +235,7 @@ Tier: {COMPLEXITY_TIER}
167
235
 
168
236
  {N} tasks in {M} parallel groups
169
237
  Estimated {K} files to create/modify
238
+ 🚦 Gates: K2 plan-completeness ✓ (exit 0) ← ✗/inconclusive ⇒ do NOT start Step 7
170
239
 
171
240
  • "ок" — start coding
172
241
  • "разбей [TASK-N]" — split task further
@@ -196,3 +265,4 @@ re-flagging it. In the ultracode workflow this врезка runs automatically a
196
265
  - [ ] Parallel groups correctly identified
197
266
  - [ ] Each task touches identifiable files
198
267
  - [ ] Total file count matches complexity tier expectation
268
+ - [ ] `check-plan-completeness.mjs features/<slug>` exited 0 (its output quoted at the checkpoint)
@@ -152,7 +152,7 @@ Fail the ADR gate for any miss:
152
152
  | Follow-up discipline | Consequences link follow-up ADRs or state none; after-action review is scheduled |
153
153
  | Confirmation stanza | Names verification method, monitoring, success metric, and owner |
154
154
  | Confirmation-to-test link | The named load-bearing property has a real automated test/fitness check by file/name |
155
- | No placeholders | No TODO, template hint text, raw prompt/generation scaffold, or empty tail sections |
155
+ | No placeholders | No `TODO`, template hint text, raw prompt/generation scaffold, or empty tail sections |
156
156
 
157
157
  Reject explainer-masquerading-as-ADR: if the document describes a problem space or technology landscape but has no concrete `Decision`, record a BLOCKER.
158
158
 
@@ -182,6 +182,19 @@ paths — especially startup/lifespan/health: require a negative resource-down t
182
182
  path degrades per its declared contract: fail-open for advisory, explicit fail-fast for load-bearing).
183
183
  Missing → HIGH gap.
184
184
 
185
+ ### 3d. No-stubs gate (backlog 0b403a0106103901 — layer 1 of the cost-of-detection ladder)
186
+
187
+ Over the files THIS RUN touched (the Step-7 change list; for a Codex coder, the landed-barrier file
188
+ list), grep for unfinished-stub markers: `TODO` / `FIXME` / `HACK` / `XXX` / `PLACEHOLDER`
189
+ (case-SENSITIVE, word-bounded — never add `-i`; `hackathon`/`todos` must not fire) plus the phrase
190
+ `implement later` (case-insensitive). **Any match = the task shipped incomplete** → HIGH gap naming
191
+ file:line, UNLESS the line carries an inline `no-stubs: <reason>` waiver WITH a non-empty reason, or
192
+ `.dz/guard.json` `stubWaivers` lists the path WITH a reason. A REASONLESS waiver is itself a HIGH gap,
193
+ never an exemption. Cross-check mechanically: `dz guard check --op publish --json` runs the same scan
194
+ as the SOFT `no-stubs` rule over the working-tree diff. When you QUOTE a marker in `08_qe_report.md`,
195
+ backtick it so the report itself scans clean (the claim-check forbidden-phrase convention). Record the
196
+ verdict in the ADR Fitness section.
197
+
185
198
  ### 4. Multi-Agent Review Panel (L/XL)
186
199
 
187
200
  For L/XL features, spawn 3 parallel review agents using brutal-honesty modes:
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env node
2
+ // K2 — machine plan-completeness gate for /feature-adr, run BETWEEN Step 6 (plan) and Step 7 (code).
3
+ // Generalized from features/wave1-instrument-repair/check-plan-completeness.mjs (that copy is the
4
+ // historical artifact of its run and stays untouched); this one is parameterized by feature dir.
5
+ //
6
+ // USAGE: node .claude/skills/feature-adr/scripts/check-plan-completeness.mjs [<feature-dir>] [--tier=M] [--acid=A1,A2]
7
+ // <feature-dir> defaults to the current working directory.
8
+ // --tier=S|M|L|XL closes the ADR-less dodge (see S-TIER HONESTY); omitting it keeps the
9
+ // heuristic, and the skip note then names the dodge out loud.
10
+ //
11
+ // VERDICT CONTRACT (unchanged from the proven copy — never a silent pass):
12
+ // PASS exit 0 last line: `K2 plan-completeness: PASS (...)`
13
+ // FAIL exit 1 last line: `K2 plan-completeness: FAIL (...)`
14
+ // NOT-ESTABLISHED exit 3 last line: `K2 plan-completeness: NOT-ESTABLISHED — <reason>`
15
+ // (the wave1 copy printed the exit-3 line as `K2: NOT-ESTABLISHED — …`; the prefix is unified here so
16
+ // ONE regex parses all three verdicts — the exit codes and their meanings are identical.)
17
+ // Set difference over IDENTIFIERS, not text similarity.
18
+ //
19
+ // KNOWN LIMITATION (measured on the discrimination twin, 2026-08-19): C1 is a grep — a PROSE
20
+ // mention of "ADR-002" satisfies it exactly like a task reference. The real N14 (backlog
21
+ // 3dbd2851-adjacent) must parse task structure. Kept honest here: C1 catches "forgot entirely",
22
+ // not "mentioned but not tasked".
23
+ //
24
+ // Checks:
25
+ // C1 every ADR file in 03_adr/ has >=1 task line in 06_implementation_plan.md citing it (ADR-00N)
26
+ // C2 every Confirmation-numbered check in each ADR is named in the plan (by its test-file path)
27
+ // C3 the plan carries an EXPECTED_CODE_TARGETS: block, non-empty, and EVERY line parses to a
28
+ // plausible repo-relative path (no spaces unless quoted, no traversal, no markdown residue)
29
+ // — SFDIPOT condition: line-level validation, reject-with-reason, not just block presence
30
+ // C4 the plan names the feature's OWN acid corpus (see "acid corpus" below)
31
+ // C5 the plan has an 'Inputs read:' line naming 03_adr, 05_architecture (wave-2 seam, cheap here)
32
+ //
33
+ // S-TIER HONESTY (no 03_adr/): an S-tier run legitimately has no ADR files, and forcing it to fail a
34
+ // plan gate it can never satisfy would make the gate a nuisance to route around. So:
35
+ // - no ADR files BUT the plan cites `ADR-<digits>` → NOT-ESTABLISHED (exit 3): the plan claims
36
+ // decisions the gate cannot see, so its completeness is unknown — that is never a pass.
37
+ // - no ADR files AND --tier is M/L/XL → FAIL: an M+ feature owes ADRs, and skipping C1/C2 for it
38
+ // would let a whole tier dodge the check by simply not writing 03_adr/ (G-F3).
39
+ // - no ADR files AND (--tier=S or no --tier) AND the plan claims no ADR work
40
+ // → C1 + C2 are SKIPPED-with-note (printed as SKIP lines; the run can still PASS on C3-C5).
41
+ // Without --tier the skip note NAMES the dodge, because the gate cannot tell an honest S-run
42
+ // from an M+ run that deleted its ADR directory.
43
+ //
44
+ // ECHO SAFETY (G-F1, reproduced by execution): this script echoes plan-controlled content (rejected
45
+ // target lines, acid tokens, the feature path). A plan line carrying the literal verdict marker was
46
+ // echoed verbatim and a first-match parser upstream read the PLANT instead of the real verdict. Every
47
+ // echoed value now goes through `safe()`, which defangs the marker and the exit trailer and truncates,
48
+ // so the only spellable verdict line in this stream is the one this script writes last.
49
+ //
50
+ // ACID CORPUS (C4): the wave1 copy hard-coded ITS run's tokens. Here the corpus is DISCOVERED from
51
+ // the feature's own `00_complexity_assessment.md` acid-case table (rows shaped `| A<N> | … |`), or
52
+ // supplied explicitly with `--acid=T1,T2,…`. If neither establishes a corpus, C4 is SKIPPED-with-note
53
+ // (a feature that declared no acid cases cannot be failed for not naming them).
54
+ import { readFileSync, readdirSync, existsSync } from 'node:fs';
55
+ import { isAbsolute, join, resolve } from 'node:path';
56
+
57
+ const argv = process.argv.slice(2);
58
+ const acidArg = argv.find((a) => a.startsWith('--acid='));
59
+ const tierArg = argv.find((a) => a.startsWith('--tier='));
60
+ const TIER = tierArg ? tierArg.slice('--tier='.length).trim().toUpperCase() : null;
61
+ const TIER_REQUIRES_ADR = TIER === 'M' || TIER === 'L' || TIER === 'XL';
62
+ const dirArg = argv.find((a) => !a.startsWith('--'));
63
+ const FDIR = resolve(dirArg && dirArg !== '' ? (isAbsolute(dirArg) ? dirArg : join(process.cwd(), dirArg)) : process.cwd());
64
+ const planPath = join(FDIR, '06_implementation_plan.md');
65
+ const adrDir = join(FDIR, '03_adr');
66
+ const complexityPath = join(FDIR, '00_complexity_assessment.md');
67
+
68
+ // Defang any value this script echoes back: a plan (or a path, or an ADR) must never be able to spell
69
+ // the verdict marker or the exit trailer into this stream. Truncated so one hostile line cannot bury
70
+ // the verdict either. The offender stays READABLE — defanged, not dropped.
71
+ const safe = (v) => String(v)
72
+ .replace(/K2 plan-completeness:/g, 'K2 plan-completeness[echoed]:')
73
+ .replace(/K2_EXIT=/g, 'K2_EXIT[echoed]=')
74
+ .replace(/[\r\n]+/g, ' ')
75
+ .slice(0, 300);
76
+ const out = (s) => console.log(s);
77
+ const notEstablished = (why) => { out(`K2 plan-completeness: NOT-ESTABLISHED — ${safe(why)}`); process.exit(3); };
78
+ let failures = [], warnings = [], skips = [];
79
+
80
+ if (!existsSync(FDIR)) notEstablished(`feature dir absent: ${FDIR}`);
81
+ if (!existsSync(planPath)) notEstablished('06_implementation_plan.md absent');
82
+ const plan = readFileSync(planPath, 'utf-8');
83
+ if (plan.trim().length < 200) notEstablished('plan suspiciously small (<200 chars)');
84
+ const adrFiles = existsSync(adrDir) ? readdirSync(adrDir).filter(f => f.endsWith('.md')).sort() : [];
85
+ const planClaimsAdrWork = /\bADR-\d+/.test(plan);
86
+ if (adrFiles.length === 0 && planClaimsAdrWork) notEstablished('no ADR files under 03_adr/, yet the plan cites ADR-<n> — completeness cannot be established');
87
+
88
+ if (adrFiles.length === 0 && TIER_REQUIRES_ADR) {
89
+ failures.push(`C1: tier ${TIER} has NO ADR files under 03_adr/ — an M/L/XL feature owes at least one ADR; the plan cannot be complete against decisions that were never written`);
90
+ failures.push(`C2: tier ${TIER} has no 03_adr/ — no Confirmation test can be checked`);
91
+ } else if (adrFiles.length === 0) {
92
+ skips.push(`C1: no 03_adr/ and the plan claims no ADR work — ADR-coverage check SKIPPED${TIER === 'S' ? ' (--tier=S, the legitimate S-tier shape)' : ' (NO --tier supplied: an M/L/XL run that simply never wrote 03_adr/ would dodge C1/C2 here — pass --tier to close it)'}`);
93
+ skips.push('C2: no 03_adr/ — Confirmation-test coverage check SKIPPED');
94
+ } else {
95
+ // C1 — ADR ids referenced by plan tasks
96
+ for (const f of adrFiles) {
97
+ const m = f.match(/^(\d{3})-/); if (!m) { warnings.push(`C1: unparseable ADR filename ${safe(f)}`); continue; }
98
+ const id = `ADR-${m[1]}`;
99
+ const re = new RegExp(`ADR-0*${Number(m[1])}\\b`);
100
+ if (!re.test(plan)) failures.push(`C1: ${id} (${safe(f)}) has NO task in the plan referencing it`);
101
+ }
102
+
103
+ // C2 — every Confirmation-listed test file path appears in the plan
104
+ const testPathRe = /(?:packages\/[\w@/.-]+\/test\/[\w./-]+\.test\.(?:ts|mjs|js)|tests?\/[\w./-]+\.test\.(?:ts|mjs|js))/g;
105
+ for (const f of adrFiles) {
106
+ const adr = readFileSync(join(adrDir, f), 'utf-8');
107
+ const confIdx = adr.search(/^##+\s*Confirmation/mi);
108
+ if (confIdx < 0) { failures.push(`C2: ${safe(f)} has no Confirmation section`); continue; }
109
+ const conf = adr.slice(confIdx);
110
+ const paths = [...new Set((conf.match(testPathRe) ?? []))];
111
+ if (paths.length === 0) { failures.push(`C2: ${safe(f)} Confirmation names no test file paths`); continue; }
112
+ for (const p of paths) if (!plan.includes(p)) failures.push(`C2: ${safe(f)} Confirmation test ${safe(p)} NOT named in the plan`);
113
+ }
114
+ }
115
+
116
+ // C3 — EXPECTED_CODE_TARGETS block, line-level validation
117
+ const blockM = plan.match(/EXPECTED_CODE_TARGETS:\s*\n((?:\s*[-*]\s*.+\n?)+)/);
118
+ if (!blockM) failures.push('C3: no EXPECTED_CODE_TARGETS: block in the plan');
119
+ else {
120
+ const lines = blockM[1].split('\n').map(s => s.trim()).filter(Boolean);
121
+ if (lines.length === 0) failures.push('C3: EXPECTED_CODE_TARGETS block is empty');
122
+ for (const ln of lines) {
123
+ const path = ln.replace(/^[-*]\s*/, '').replace(/`/g, '').trim();
124
+ const reasons = [];
125
+ if (/\s/.test(path)) reasons.push('contains whitespace');
126
+ if (/\.\./.test(path)) reasons.push('path traversal');
127
+ if (/[*#\[\]()]/.test(path)) reasons.push('markdown residue');
128
+ if (!/^[\w@][\w@/.-]*$/.test(path)) reasons.push('not a plain repo-relative path');
129
+ if (reasons.length) failures.push(`C3: target line rejected: "${safe(ln)}" — ${reasons.join(', ')}`);
130
+ }
131
+ }
132
+
133
+ // C4 — the feature's OWN acid corpus is named in the plan (discovered, or --acid=…)
134
+ // G-F5: a corpus that was DECLARED but declared BADLY (lowercase `| a1 |`, a row with trailing junk,
135
+ // or an acid-case table header with no parsable rows) is a FAILURE, not a skip. Silently skipping it
136
+ // let a malformed declaration buy the same green as an honest "no acid cases here". The strict row
137
+ // shape is `| A<digits> |`; anything that LOOKS like an acid row under the loose shape but is not
138
+ // strict is reported by name. (Honest scope: rows that look nothing like an acid row — a defect table
139
+ // `| D1 |`, say — are not acid candidates and are left alone.)
140
+ let acidTokens = [];
141
+ let acidSource = null;
142
+ if (acidArg) {
143
+ acidTokens = acidArg.slice('--acid='.length).split(',').map(s => s.trim()).filter(Boolean);
144
+ acidSource = '--acid';
145
+ if (acidTokens.length === 0) failures.push('C4-malformed: --acid was passed but declares no tokens');
146
+ } else if (existsSync(complexityPath)) {
147
+ const complexity = readFileSync(complexityPath, 'utf-8');
148
+ acidTokens = [...new Set((complexity.match(/^\|\s*(A\d+)\s*\|/gm) ?? []).map(r => r.replace(/[|\s]/g, '')))];
149
+ if (acidTokens.length) acidSource = '00_complexity_assessment.md acid-case table';
150
+ const loose = [...new Set((complexity.match(/^\|\s*([Aa]\d+[A-Za-z]?)\s*\|/gm) ?? []).map(r => r.replace(/[|\s]/g, '')))];
151
+ for (const cand of loose) if (!acidTokens.includes(cand)) failures.push(`C4-malformed: acid row "${safe(cand)}" does not match the required \`| A<n> |\` shape (case-sensitive, digits only) — it is declared but uncheckable`);
152
+ const declaresTable = /\|\s*Acid case\s*\|/i.test(complexity);
153
+ if (declaresTable && acidTokens.length === 0) failures.push('C4-malformed: 00_complexity_assessment.md declares an acid-case table but NO `| A<n> |` row parsed from it');
154
+ }
155
+ if (acidTokens.length === 0) skips.push('C4: no acid corpus declared (no --acid, no `| A<n> |` table in 00_complexity_assessment.md) — acid-naming check SKIPPED');
156
+ else for (const t of acidTokens) if (!new RegExp(`\\b${t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`, 'i').test(plan)) failures.push(`C4: acid token "${safe(t)}" (from ${acidSource}) not named in the plan`);
157
+
158
+ // C5 — Inputs read line
159
+ if (!/Inputs read:/i.test(plan)) warnings.push('C5: no "Inputs read:" line (wave-2 seam, WARN only)');
160
+ else for (const need of ['03_adr','05_architecture']) if (!plan.includes(need)) warnings.push(`C5: Inputs read line missing ${need}`);
161
+
162
+ for (const s of skips) out('SKIP ' + s);
163
+ for (const w of warnings) out('WARN ' + w);
164
+ for (const f of failures) out('FAIL ' + f);
165
+ out(`K2 plan-completeness: ${failures.length === 0 ? 'PASS' : 'FAIL'} (${failures.length} failure(s), ${warnings.length} warning(s), ${skips.length} skip(s)) over ${adrFiles.length} ADRs, tier ${TIER === null ? '(unspecified)' : safe(TIER)}, in ${safe(FDIR)}`);
166
+ process.exit(failures.length === 0 ? 0 : 1);