@dzhechkov/skills-feature-adr 1.3.65 → 1.3.66

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/.dz-manifest.json CHANGED
@@ -13,7 +13,7 @@
13
13
  },
14
14
  {
15
15
  "path": "README.md",
16
- "sha256": "4a887b8fe2a52c75be7189ddeef4393dc2a8a872d747bdbde3caba94488960dd"
16
+ "sha256": "5cca2d4a5337177ee9a072d49d14f73386af75398f52d907d757d4c052afdf81"
17
17
  },
18
18
  {
19
19
  "path": "bin/cli.js",
@@ -25,7 +25,7 @@
25
25
  },
26
26
  {
27
27
  "path": "package.json",
28
- "sha256": "1f95669ddfb76990bfeb0e1be603c27651f47a9233020ed10c37e8705543b688"
28
+ "sha256": "b76f1b1d1ce3d8b22be0c2354fe3bd22833513da8b418c7c93423ae75f4018ae"
29
29
  },
30
30
  {
31
31
  "path": "scripts/sync-templates.js",
@@ -121,7 +121,7 @@
121
121
  },
122
122
  {
123
123
  "path": "templates/.claude/skills/feature-adr/SKILL.md",
124
- "sha256": "c422d1e2b50cf7b8bd56cd5d886d96af30d8bf33a468b925396f395ebf32de53"
124
+ "sha256": "02d2a081e4bc9e7f5d42e55a4e21a68484af8b45216ff74fe5fc4322cb689a96"
125
125
  },
126
126
  {
127
127
  "path": "templates/.claude/skills/feature-adr/examples/sample-feature-output.md",
@@ -157,7 +157,7 @@
157
157
  },
158
158
  {
159
159
  "path": "templates/.claude/skills/feature-adr/modules/06-implementation-plan.md",
160
- "sha256": "7a6c830d63f73953a96f1cb125ab88b7d5c24b2d4b61e997d6d3dfea8262d495"
160
+ "sha256": "443043f6f3a3c2ebdcc4d40f9d13d799581056fb37890a2bb1fa61662825229b"
161
161
  },
162
162
  {
163
163
  "path": "templates/.claude/skills/feature-adr/modules/07-code.md",
@@ -165,7 +165,7 @@
165
165
  },
166
166
  {
167
167
  "path": "templates/.claude/skills/feature-adr/modules/08-qe.md",
168
- "sha256": "0d314eaeb69a761f68c4beb1ab7df0a2da1964538a8eb33c99cb9b819598d255"
168
+ "sha256": "97e9a7bc54f196b381c4ef71258cddcb2fb48f216df1ba015927c176bad57084"
169
169
  },
170
170
  {
171
171
  "path": "templates/.claude/skills/feature-adr/modules/09-fleet-qe.md",
@@ -255,6 +255,10 @@
255
255
  "path": "templates/.claude/skills/feature-adr/references/qe-checklist.md",
256
256
  "sha256": "238d8896996dc53559f58b24aa7eca966cc8845c3b9dda6d982c717c657b91e4"
257
257
  },
258
+ {
259
+ "path": "templates/.claude/skills/feature-adr/scripts/check-plan-completeness.mjs",
260
+ "sha256": "41d85b012a9d6cc172eaa5d7777294f2c01fb54b752ed61c7cbe2d2cd7b79b67"
261
+ },
258
262
  {
259
263
  "path": "templates/.claude/skills/frontend-design/LICENSE.txt",
260
264
  "sha256": "0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594"
@@ -317,7 +321,7 @@
317
321
  },
318
322
  {
319
323
  "path": "templates/.claude/workflows/feature-adr.js",
320
- "sha256": "8afa77bb421ea4052eec0feab5f49edc1f2b1cea69238971f8ef1b3c8328a483"
324
+ "sha256": "4904cf2040f024647c561fb5bd11aab5c7c0b70190dc4505b1bd69572cf25072"
321
325
  },
322
326
  {
323
327
  "path": "templates/lib/memory-protocol.md",
@@ -345,5 +349,5 @@
345
349
  }
346
350
  ]
347
351
  },
348
- "signature": "3OT+p3Tm5tBnn9T2RKEPlCnhCiaemKAOraeoPA4zYR28p1MpACnkeEmVyXPpwnqhG/a6uD/AjC/DrRuihMdHCw=="
352
+ "signature": "nKR64NSSz41u0CCXl4HdsXqTmS5QUqIIYZdRn4xVoEj/wuzF7Vb7X21mte94etpLINVimxi1pTdUQYOO5GlyCw=="
349
353
  }
package/README.md CHANGED
@@ -531,6 +531,15 @@ gate > reviewer judgment > memory). A miss usually means a check lived one layer
531
531
  control that proved the path was I/O-free. The bundled role-default skills gained the matching entries
532
532
  (`code-impl` P25, `code-critic` AP14).
533
533
 
534
+ - **No-stubs gate** (backlog 0b403a0106103901, Karpathy-Michaels rule XI). Step 8 greps the files THE RUN
535
+ touched for unfinished-stub markers — `TODO` / `FIXME` / `HACK` / `XXX` / `PLACEHOLDER` (case-sensitive,
536
+ word-bounded: `hackathon`/`todos` never fire) plus the `implement later` phrase — **any unwaived match =
537
+ the task shipped incomplete** (HIGH gap naming file:line). A line may carry an inline
538
+ `no-stubs: <reason>` waiver; a waiver **without** a reason is itself a HIGH gap, never an exemption. The
539
+ same scan runs mechanically at publish time as the SOFT `no-stubs` rule in `dz guard check --op publish`
540
+ (change-set scoped — a deliberate design: a tree-wide scan measured ~78% ancient-marker noise). Markers
541
+ quoted in the QE report are backticked so the report itself scans clean.
542
+
534
543
  Both forms carry all of it: the interactive skill (step modules + banner template) and the deterministic
535
544
  workflow (stage prompts + derived `gates` in its returns) — same shapes, same vocabulary.
536
545
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dzhechkov/skills-feature-adr",
3
- "version": "1.3.65",
3
+ "version": "1.3.66",
4
4
  "description": "Adaptive Feature Development skill pack for Claude Code — 11-step pipeline with Complexity Router (S/M/L/XL), ADR-driven architecture, 15 agentic-qe skills, multi-agent fleet QE. Supports --full-qe, --full-qe-extended, --with-learning, and --knowledge-extractor modes.",
5
5
  "bin": {
6
6
  "skills-feature-adr": "./bin/cli.js"
package/sbom.json CHANGED
@@ -35,7 +35,7 @@
35
35
  "hashes": [
36
36
  {
37
37
  "alg": "SHA-256",
38
- "content": "4a887b8fe2a52c75be7189ddeef4393dc2a8a872d747bdbde3caba94488960dd"
38
+ "content": "5cca2d4a5337177ee9a072d49d14f73386af75398f52d907d757d4c052afdf81"
39
39
  }
40
40
  ]
41
41
  },
@@ -65,7 +65,7 @@
65
65
  "hashes": [
66
66
  {
67
67
  "alg": "SHA-256",
68
- "content": "1f95669ddfb76990bfeb0e1be603c27651f47a9233020ed10c37e8705543b688"
68
+ "content": "b76f1b1d1ce3d8b22be0c2354fe3bd22833513da8b418c7c93423ae75f4018ae"
69
69
  }
70
70
  ]
71
71
  },
@@ -305,7 +305,7 @@
305
305
  "hashes": [
306
306
  {
307
307
  "alg": "SHA-256",
308
- "content": "c422d1e2b50cf7b8bd56cd5d886d96af30d8bf33a468b925396f395ebf32de53"
308
+ "content": "02d2a081e4bc9e7f5d42e55a4e21a68484af8b45216ff74fe5fc4322cb689a96"
309
309
  }
310
310
  ]
311
311
  },
@@ -395,7 +395,7 @@
395
395
  "hashes": [
396
396
  {
397
397
  "alg": "SHA-256",
398
- "content": "7a6c830d63f73953a96f1cb125ab88b7d5c24b2d4b61e997d6d3dfea8262d495"
398
+ "content": "443043f6f3a3c2ebdcc4d40f9d13d799581056fb37890a2bb1fa61662825229b"
399
399
  }
400
400
  ]
401
401
  },
@@ -415,7 +415,7 @@
415
415
  "hashes": [
416
416
  {
417
417
  "alg": "SHA-256",
418
- "content": "0d314eaeb69a761f68c4beb1ab7df0a2da1964538a8eb33c99cb9b819598d255"
418
+ "content": "97e9a7bc54f196b381c4ef71258cddcb2fb48f216df1ba015927c176bad57084"
419
419
  }
420
420
  ]
421
421
  },
@@ -639,6 +639,16 @@
639
639
  }
640
640
  ]
641
641
  },
642
+ {
643
+ "type": "file",
644
+ "name": "templates/.claude/skills/feature-adr/scripts/check-plan-completeness.mjs",
645
+ "hashes": [
646
+ {
647
+ "alg": "SHA-256",
648
+ "content": "41d85b012a9d6cc172eaa5d7777294f2c01fb54b752ed61c7cbe2d2cd7b79b67"
649
+ }
650
+ ]
651
+ },
642
652
  {
643
653
  "type": "file",
644
654
  "name": "templates/.claude/skills/frontend-design/LICENSE.txt",
@@ -795,7 +805,7 @@
795
805
  "hashes": [
796
806
  {
797
807
  "alg": "SHA-256",
798
- "content": "8afa77bb421ea4052eec0feab5f49edc1f2b1cea69238971f8ef1b3c8328a483"
808
+ "content": "4904cf2040f024647c561fb5bd11aab5c7c0b70190dc4505b1bd69572cf25072"
799
809
  }
800
810
  ]
801
811
  },
@@ -100,6 +100,9 @@ ROUTER → REQUIREMENTS → RESEARCH → ADR → QCSD SWARM
100
100
  SPARC-GOAP PLAN
101
101
  (all)
102
102
 
103
+ K2 PLAN-COMPLETENESS GATE
104
+ (all — exit 0 or Step 7 waits)
105
+
103
106
  Step 7
104
107
  CODE
105
108
  (all)
@@ -144,6 +147,26 @@ Step | S | M | L | XL | Model | Agentic QE Skill
144
147
  10 Delivery Gate (OPT-IN) | o | o | o | o | cross-family of coder | 4 planes: regressions ‖ security ‖ code-quality ‖ product-honesty (o = runs only when explicitly requested; absent ⇒ byte-identical) |
145
148
  ```
146
149
 
150
+ ### K2 plan-completeness gate (Step-6/7 boundary, MANDATORY, all tiers)
151
+
152
+ Step 7 does not start until the plan passes a SCRIPT — not a reviewer's impression:
153
+
154
+ ```bash
155
+ node .claude/skills/feature-adr/scripts/check-plan-completeness.mjs features/<slug>
156
+ ```
157
+
158
+ Pass the run's tier (`--tier=S|M|L|XL`): M/L/XL with no `03_adr/` FAILS rather than skipping C1/C2.
159
+ Every tier — S included — writes a real `06_implementation_plan.md`; an inline-only S-tier checklist
160
+ cannot be gated and is no longer permitted.
161
+
162
+ `exit 0` → proceed to Step 7 · `exit 1` → return to Step 6 and fix every `FAIL C*` line · `exit 3`
163
+ → INCONCLUSIVE (inputs unreadable) — fix them and rerun. **Never proceed on a non-zero exit, and
164
+ never read empty output as a pass**: the verdict is the last line
165
+ (`K2 plan-completeness: PASS|FAIL|NOT-ESTABLISHED`), and its absence is not a verdict. The checkpoint
166
+ banner's Gates line carries it: `🚦 Gates: K2 plan-completeness ✓ | ✗ | inconclusive`. Details and the
167
+ per-check list: `modules/06-implementation-plan.md`. In the ultracode workflow the gate runs
168
+ automatically and a non-PASS returns `phase: 'plan-gate-failed'` without dispatching the coder.
169
+
147
170
  ## DAG Dependencies
148
171
 
149
172
  Steps are NOT purely linear. The DAG defines what can run in parallel:
@@ -321,10 +344,17 @@ Two flags for integrating with the full agentic-qe package:
321
344
 
322
345
  | Flag | Mode | What it does |
323
346
  |------|------|-------------|
324
- | (none) | Reference | Condensed copies of 9 core skills, no install needed |
325
- | `--full-qe` | Direct | Full agentic-qe protocols for existing 9 core skills |
347
+ | (none) | Reference | Condensed copies of 9 core skills, no install needed — the **no-install fallback** |
348
+ | `--full-qe` | Direct | **RECOMMENDED DEFAULT when agentic-qe is installed.** Full agentic-qe protocols for the 9 core skills, and BOTH halves of the learning loop run: Step-0 recall = `memory_query("patterns/feature-adr/*")` **+** `dz recall`; Step-8 teach = the aqe `qe-outcome` store **+** `dz teach` |
326
349
  | `--full-qe-extended` | Direct Extended | Full protocols + 6 additional skills (chaos, security, performance, mutation, TDD, production-swarm) |
327
350
 
351
+ **Which one to run:** if `aqe` is installed (`which aqe` or `node_modules/agentic-qe/`), run
352
+ `--full-qe` — it is the recommended default at every tier, because the aqe pattern memory only
353
+ contributes when the Direct half of the loop is active. Reference mode is the honest fallback for a
354
+ machine without agentic-qe; it is not a lesser-quality choice there, it is the only correct one.
355
+ This is a RECOMMENDATION about which flag to pass — the mode LOGIC is unchanged (flag + installed →
356
+ direct; flag without install → WARN + reference).
357
+
328
358
  ### Installation
329
359
 
330
360
  ```bash
@@ -338,8 +368,8 @@ cd your-project && aqe init --auto
338
368
  ### Activation
339
369
 
340
370
  ```
341
- /feature-adr [описание фичи] # Reference Mode (default)
342
- /feature-adr --full-qe [описание фичи] # Direct Mode: full protocols, same skills
371
+ /feature-adr --full-qe [описание фичи] # RECOMMENDED when agentic-qe is installed
372
+ /feature-adr [описание фичи] # Reference Mode: the no-install fallback
343
373
  /feature-adr --full-qe-extended [описание фичи] # Direct Extended: full protocols + extra skills
344
374
  ```
345
375
 
@@ -454,7 +484,8 @@ each step records the live panel via `dz statusline --fa-record`. This half NEVE
454
484
 
455
485
  The **agentic-qe MCP layer below is an ADDITIONAL enrichment, active only in Direct modes**
456
486
  (`--full-qe` / `--full-qe-extended`, needs `fleet_init`) — it adds semantic in-session recall on top of
457
- the always-on dz loop.
487
+ the always-on dz loop. This is why `--full-qe` is the RECOMMENDED default wherever agentic-qe is
488
+ installed: only then do BOTH recall halves run at Step 0 and BOTH teach halves at Step 8.
458
489
 
459
490
  When `{AGENTIC_QE_MODE}` = `direct` | `direct-extended`, the pipeline ALSO runs a recall → store cycle
460
491
  over agentic-qe's MCP pattern memory (`namespace: "learning"`, `fleet_init` first):
@@ -537,8 +568,9 @@ Everything from `--full-qe` plus 6 additional skills and up to 3 extra agents in
537
568
 
538
569
  | Scenario | Mode |
539
570
  |----------|------|
540
- | S/M tier features | Reference (always) |
541
- | L tier, standard QE | Reference or `--full-qe` |
571
+ | agentic-qe installed (any tier) | `--full-qe` the recommended default |
572
+ | agentic-qe NOT installed | Reference (the fallback; the dz half of the loop still runs) |
573
+ | S/M tier, agentic-qe installed | `--full-qe` |
542
574
  | L tier, high QE maturity | `--full-qe` |
543
575
  | XL tier features | `--full-qe` recommended |
544
576
  | XL + security-critical (banking, ФЗ-152) | `--full-qe-extended` recommended |
@@ -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
@@ -134,13 +137,17 @@ This gap-check loop ensures the implementation plan is complete before coding be
134
137
 
135
138
  ## Output
136
139
 
137
- ### S-tier (inline)
138
- Inline checklist in conversation:
140
+ ### S-tier (minimal file — NOT inline-only)
141
+ Write `features/<slug>/06_implementation_plan.md` with the checklist plus the machine-read block, and
142
+ show the same checklist inline at the checkpoint:
139
143
  ```
140
144
  Implementation plan:
141
145
  - [ ] {task 1}
142
146
  - [ ] {task 2}
143
147
  - [ ] {task 3}
148
+
149
+ EXPECTED_CODE_TARGETS:
150
+ - {repo-relative path Step 7 will create or modify}
144
151
  ```
145
152
 
146
153
  ### M/L/XL
@@ -157,6 +164,41 @@ Create `features/<slug>/06_implementation_plan.md` with:
157
164
 
158
165
  Set `{IMPL_PLAN}` variable.
159
166
 
167
+ ## K2 plan-completeness gate (MANDATORY — Step 7 does not start until it exits 0)
168
+
169
+ After the plan is written, run the gate. It is a script, not a judgement call:
170
+
171
+ ```bash
172
+ node .claude/skills/feature-adr/scripts/check-plan-completeness.mjs features/<slug>
173
+ ```
174
+
175
+ | Exit | Verdict | What you do |
176
+ |------|---------|-------------|
177
+ | `0` | PASS | proceed to Step 7 |
178
+ | `1` | FAIL | **return to Step 6** — fix the plan for every `FAIL C*` line, rerun the gate |
179
+ | `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. |
180
+
181
+ Never proceed on a non-zero exit, and never treat empty output as a pass — the last line
182
+ (`K2 plan-completeness: PASS|FAIL|NOT-ESTABLISHED`) is the verdict, and its absence is not one.
183
+ What it checks: C1 every ADR has a plan task citing it · C2 every ADR Confirmation test path is named
184
+ in the plan · C3 the `EXPECTED_CODE_TARGETS:` block parses line by line · C4 the feature's declared
185
+ acid corpus is named · C5 (WARN) the `Inputs read:` line. An S-tier run with no `03_adr/` skips C1/C2
186
+ with a note (it cannot be failed for ADRs it never had) — unless the plan itself cites `ADR-<n>`,
187
+ which is NOT-ESTABLISHED. C1 is a grep: it catches "forgot entirely", not "mentioned but not tasked".
188
+
189
+ Pass the run's tier so the check cannot be dodged: `--tier=S|M|L|XL`. An M/L/XL feature with no
190
+ `03_adr/` FAILS C1/C2 (an M+ feature owes ADRs); only `--tier=S` — or no tier at all, and then the
191
+ skip note says so — takes the skip. A declared-but-malformed acid table (lowercase `| a1 |`, a header
192
+ with no parsable rows) FAILS as `C4-malformed`; it is not a skip.
193
+
194
+ **If the gate fails in the ultracode workflow:** the run returns `phase: 'plan-gate-failed'` and the
195
+ plan stage is CHECKPOINTED, so a bare re-invoke resumes the same failing plan. Repair it from outside:
196
+ edit `features/<slug>/06_implementation_plan.md` to fix each `FAIL` line and re-invoke (the checkpoint
197
+ is keyed on run INPUTS, not on the file, so your edit survives), or re-invoke with `args.resume:
198
+ 'never'` (or delete `features/<slug>/.fa-state/`) to force a fresh plan.
199
+
200
+ The checkpoint banner's Gates line carries the verdict: `K2 plan-completeness ✓ | ✗ | inconclusive`.
201
+
160
202
  ## Checkpoint Format
161
203
 
162
204
  ```
@@ -167,6 +209,7 @@ Tier: {COMPLEXITY_TIER}
167
209
 
168
210
  {N} tasks in {M} parallel groups
169
211
  Estimated {K} files to create/modify
212
+ 🚦 Gates: K2 plan-completeness ✓ (exit 0) ← ✗/inconclusive ⇒ do NOT start Step 7
170
213
 
171
214
  • "ок" — start coding
172
215
  • "разбей [TASK-N]" — split task further
@@ -196,3 +239,4 @@ re-flagging it. In the ultracode workflow this врезка runs automatically a
196
239
  - [ ] Parallel groups correctly identified
197
240
  - [ ] Each task touches identifiable files
198
241
  - [ ] Total file count matches complexity tier expectation
242
+ - [ ] `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);