@dzhechkov/skills-feature-adr 1.3.66 → 1.4.0
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 +13 -13
- package/CHANGELOG.md +35 -0
- package/README.md +65 -0
- package/package.json +1 -1
- package/sbom.json +12 -12
- package/templates/.claude/skills/feature-adr/SKILL.md +9 -0
- package/templates/.claude/skills/feature-adr/modules/01-requirements.md +26 -0
- package/templates/.claude/skills/feature-adr/modules/02-research.md +26 -0
- package/templates/.claude/skills/feature-adr/modules/03-adr.md +26 -0
- package/templates/.claude/skills/feature-adr/modules/03.5-ideation-swarm.md +25 -0
- package/templates/.claude/skills/feature-adr/modules/04-ddd.md +25 -0
- package/templates/.claude/skills/feature-adr/modules/05-architecture.md +26 -0
- package/templates/.claude/skills/feature-adr/modules/06-implementation-plan.md +26 -0
- package/templates/.claude/workflows/feature-adr.js +220 -13
package/.dz-manifest.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"files": [
|
|
6
6
|
{
|
|
7
7
|
"path": "CHANGELOG.md",
|
|
8
|
-
"sha256": "
|
|
8
|
+
"sha256": "bbb43101a686369a8662f47fe7b7aa75073b614a188c2752cd81d9570620bb3d"
|
|
9
9
|
},
|
|
10
10
|
{
|
|
11
11
|
"path": "LICENSE",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
"path": "README.md",
|
|
16
|
-
"sha256": "
|
|
16
|
+
"sha256": "469873d967c4438afd0b25ec8b013347841e07328f508f6786f90cc6aa2f09b9"
|
|
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": "
|
|
28
|
+
"sha256": "c1e4d257167fd2462e0c8e09f248fc1aef332aa34fb09dddd5940bdcc30ea34b"
|
|
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": "
|
|
124
|
+
"sha256": "bdedfed69d985eb6969f158a72d92f63c6b1b993008c3661f7ffc2c6a7ccfbae"
|
|
125
125
|
},
|
|
126
126
|
{
|
|
127
127
|
"path": "templates/.claude/skills/feature-adr/examples/sample-feature-output.md",
|
|
@@ -133,31 +133,31 @@
|
|
|
133
133
|
},
|
|
134
134
|
{
|
|
135
135
|
"path": "templates/.claude/skills/feature-adr/modules/01-requirements.md",
|
|
136
|
-
"sha256": "
|
|
136
|
+
"sha256": "e6ea3a224b555d5b7410cf2ddf02a3fb8d009fa01109463353e2b993c318270c"
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
"path": "templates/.claude/skills/feature-adr/modules/02-research.md",
|
|
140
|
-
"sha256": "
|
|
140
|
+
"sha256": "e2506e42251c74485188473acc4586f841afbec3ae7c5eaa8a3d8f73f6e14cc5"
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
"path": "templates/.claude/skills/feature-adr/modules/03-adr.md",
|
|
144
|
-
"sha256": "
|
|
144
|
+
"sha256": "ea03eee136c89267888b8034baa1a1cb0057314a2e745df71654a9ebee5d46b6"
|
|
145
145
|
},
|
|
146
146
|
{
|
|
147
147
|
"path": "templates/.claude/skills/feature-adr/modules/03.5-ideation-swarm.md",
|
|
148
|
-
"sha256": "
|
|
148
|
+
"sha256": "d48a8d6eebf9760cc8f1a4f19df0e6afc31fcc1284b5eacfb604ad8ced6a0b53"
|
|
149
149
|
},
|
|
150
150
|
{
|
|
151
151
|
"path": "templates/.claude/skills/feature-adr/modules/04-ddd.md",
|
|
152
|
-
"sha256": "
|
|
152
|
+
"sha256": "e9504cf1183bc0105167dc06108860882941fcf1b5be79d1978d8212c410ffbb"
|
|
153
153
|
},
|
|
154
154
|
{
|
|
155
155
|
"path": "templates/.claude/skills/feature-adr/modules/05-architecture.md",
|
|
156
|
-
"sha256": "
|
|
156
|
+
"sha256": "2ef7cf316903f0cbb1fabe1f0e1770c22b2cb3a11a4118cac0529c25074b5e8a"
|
|
157
157
|
},
|
|
158
158
|
{
|
|
159
159
|
"path": "templates/.claude/skills/feature-adr/modules/06-implementation-plan.md",
|
|
160
|
-
"sha256": "
|
|
160
|
+
"sha256": "c8fdd1877d8d9515ab29f52c3db49ddd9612b5797bc071e6208def860c02e6f2"
|
|
161
161
|
},
|
|
162
162
|
{
|
|
163
163
|
"path": "templates/.claude/skills/feature-adr/modules/07-code.md",
|
|
@@ -321,7 +321,7 @@
|
|
|
321
321
|
},
|
|
322
322
|
{
|
|
323
323
|
"path": "templates/.claude/workflows/feature-adr.js",
|
|
324
|
-
"sha256": "
|
|
324
|
+
"sha256": "1be239d2f02612bc33027767d9930eae8a2d6d5afd427d1485198bbdd821a3c7"
|
|
325
325
|
},
|
|
326
326
|
{
|
|
327
327
|
"path": "templates/lib/memory-protocol.md",
|
|
@@ -349,5 +349,5 @@
|
|
|
349
349
|
}
|
|
350
350
|
]
|
|
351
351
|
},
|
|
352
|
-
"signature": "
|
|
352
|
+
"signature": "gixeRY/3+gjSSS3a+DfVwn40SVlD9Gr2sCh3H8afGWd1+Dzo8R8CmD61qG0cLeDdHn2NgzRWwvICHe+0HFlYCA=="
|
|
353
353
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.4.0] - 2026-08-20
|
|
4
|
+
|
|
5
|
+
### Changed — the composite design checkpoint
|
|
6
|
+
|
|
7
|
+
- The Step 1–5 design fan is checkpointed **per sibling** instead of as one composite entry, so one
|
|
8
|
+
dead agent no longer discards three finished siblings. Each sibling's resume key carries its own
|
|
9
|
+
steering inputs, including its prompt text: fixing one step's instructions invalidates that step
|
|
10
|
+
and nothing else.
|
|
11
|
+
- An incomplete fan is **refused** at the Step-5/6 boundary (`phase: 'design-incomplete'`) instead of
|
|
12
|
+
being handed to the planner. Previously the completeness verdict was computed and only logged, and
|
|
13
|
+
Step 6 planned off a null design. The refusal captures training pairs and appends a cost-ledger row
|
|
14
|
+
on the way out, so a stopped run is neither silent nor invisible to cost analysis.
|
|
15
|
+
- Three named reasons with distinct repairs — `substage-missing`, `artifact-missing`,
|
|
16
|
+
`probe-not-established`. Under `resume:'force'` the artifact-missing repair says `resume:'never'`,
|
|
17
|
+
because force skips artifact probes and a plain re-invoke would loop forever.
|
|
18
|
+
- The artifact check no longer lists a directory: it runs `[ -f <exact path> ]` per required
|
|
19
|
+
artifact. A listing is a list of filenames, and a file whose NAME ends in a newline was measured
|
|
20
|
+
satisfying the requirement for the real file.
|
|
21
|
+
- The probe transcript is validated strictly rather than scanned. It is relayed by an agent, not read
|
|
22
|
+
from a pipe, and an agent that narrates the expected output emits the token byte-identically —
|
|
23
|
+
which produced a false pass on a missing artifact before this release. Inconclusive is never a pass.
|
|
24
|
+
|
|
25
|
+
### Requires
|
|
26
|
+
|
|
27
|
+
- `@dzhechkov/harness-core >= 0.5.4`. The checkpoint schema is now `fa-ckpt-3`: every existing
|
|
28
|
+
`.fa-state/checkpoints.jsonl` reads as no checkpoint, so each in-flight feature re-runs
|
|
29
|
+
router+design+plan once.
|
|
30
|
+
|
|
31
|
+
### Verification
|
|
32
|
+
|
|
33
|
+
Eight cross-family review rounds (Codex `gpt-5.6-sol` at xhigh, each pinned to a sha256 of the
|
|
34
|
+
reviewed files): D → D → D → B → C → C → D → **A**, the last with no new defects. Full ladder in
|
|
35
|
+
`features/feature-adr-hardening/composite-design-checkpoint.md`.
|
|
36
|
+
|
|
37
|
+
|
|
3
38
|
### Follow-up (QE LOW gaps closed before publish)
|
|
4
39
|
- **Upstream deletions**: `update` now removes files the template dropped (manifest-tracked orphans only; user-created untracked files are never touched) — previously `diff.missing` was computed but ignored. Shown in the summary + `--dry-run` (`- DEL`). Tests: Case G/H.
|
|
5
40
|
- Removed a dead `unchanged` branch in the directory update path (unreachable — `diff.modified` guarantees bytes differ).
|
package/README.md
CHANGED
|
@@ -106,6 +106,36 @@ ARCHITECTURE → IMPLEMENTATION → CODE → QE → FLEET QE
|
|
|
106
106
|
# Full protocols + 6 extra skills, up to 7 fleet QE agents
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
+
### The design fan resumes per sibling, and an incomplete design is REFUSED (v1.4.0)
|
|
110
|
+
|
|
111
|
+
Step 1–5 run as one parallel fan — requirements, ADR, QCSD, architecture. Until v1.4.0 the whole fan
|
|
112
|
+
was ONE checkpoint entry, so a single dead agent discarded three finished siblings and the next
|
|
113
|
+
invocation paid for all four again. Each sibling is now checkpointed on its own
|
|
114
|
+
(`design:requirements` / `design:adr` / `design:qcsd` / `design:architecture`), keyed on what steers
|
|
115
|
+
**it** — its own prompt text included, so correcting one step's instructions re-runs that step and
|
|
116
|
+
leaves the others alone.
|
|
117
|
+
|
|
118
|
+
The other half is what the pipeline is allowed to CONSUME. An incomplete fan no longer flows into
|
|
119
|
+
Step 6: the run stops at the Step-5/6 boundary and returns `phase: 'design-incomplete'` rather than
|
|
120
|
+
producing a plan with no ADR behind it. Three distinct reasons, each with its own repair:
|
|
121
|
+
|
|
122
|
+
| Reason | What happened | Repair printed |
|
|
123
|
+
|---|---|---|
|
|
124
|
+
| `substage-missing` | a sibling agent died (often a Claude limit) | re-invoke — the finished siblings resume free; or route that stage to Codex |
|
|
125
|
+
| `artifact-missing` | every sibling reported success, but a required file is not on disk | under `resume:'force'` it says use `resume:'never'` — force skips artifact probes, so re-invoking would loop |
|
|
126
|
+
| `probe-not-established` | the check could not be trusted at all | inconclusive is never a pass — refuse, then re-run |
|
|
127
|
+
|
|
128
|
+
The artifact check never lists a directory. It asks `[ -f <exact path> ]` per required artifact,
|
|
129
|
+
because a listing is a list of filenames and the data can impersonate the frame: measured, a file
|
|
130
|
+
whose NAME ends in a newline satisfied the requirement for the real file. And since the check is
|
|
131
|
+
relayed by an agent rather than read from a pipe, the whole transcript is validated — an agent that
|
|
132
|
+
merely *narrates* the expected output emits the token byte-identically, and that produced a false
|
|
133
|
+
pass before this release.
|
|
134
|
+
|
|
135
|
+
Requires `@dzhechkov/harness-core >= 0.5.4`. **One-time cost:** the checkpoint schema moved to
|
|
136
|
+
`fa-ckpt-3`, so every existing `.fa-state/checkpoints.jsonl` reads as no checkpoint and each
|
|
137
|
+
in-flight feature re-runs router+design+plan once.
|
|
138
|
+
|
|
109
139
|
### Durable checkpoints + resume (v1.3.64)
|
|
110
140
|
|
|
111
141
|
Every expensive stage (router / design / plan / code / qe / fleet) checkpoints its result into
|
|
@@ -878,3 +908,38 @@ them inside the package's own monorepo is almost never what you want anyway.
|
|
|
878
908
|
- **npm:** [https://www.npmjs.com/package/@dzhechkov/skills-feature-adr](https://www.npmjs.com/package/@dzhechkov/skills-feature-adr)
|
|
879
909
|
- **Keysarium:** [https://www.npmjs.com/package/@dzhechkov/keysarium](https://www.npmjs.com/package/@dzhechkov/keysarium)
|
|
880
910
|
- **BTO:** [https://www.npmjs.com/package/@dzhechkov/skills-bto](https://www.npmjs.com/package/@dzhechkov/skills-bto)
|
|
911
|
+
|
|
912
|
+
## Write discipline — why the writing steps now build a skeleton first
|
|
913
|
+
|
|
914
|
+
MEASURED in the field on 2026-08-19 and 2026-08-20, two independent runs of the canonical pipeline
|
|
915
|
+
against a ~130-file repository: the steps that must produce a document — Step 5 (Architecture) and
|
|
916
|
+
Step 6 (Plan) — **never reached a write**. 18 attempts, zero file writes in every one. They died in
|
|
917
|
+
the READING phase: a shell result returns, the agent thinks about its next move, stays silent past
|
|
918
|
+
the runtime's 180-second inactivity watchdog, and is killed. One run cost ~4M tokens and 1h54m.
|
|
919
|
+
|
|
920
|
+
The failure is **deterministic, not unlucky**: thinking time grows with accumulated history, so on a
|
|
921
|
+
large enough repository unbounded exploration guarantees the kill. And the cause was in the
|
|
922
|
+
instructions, not the runtime — every writing step said, in effect, *"read the code, write the
|
|
923
|
+
document"*, with no reading budget and no order of operations. An agent obeying literally reads
|
|
924
|
+
until it dies.
|
|
925
|
+
|
|
926
|
+
The owner's control experiment is the whole evidence base, and it is n=1: same slice, same inputs,
|
|
927
|
+
same model, ONE added paragraph about write discipline → a 10-section skeleton on disk 8 minutes in,
|
|
928
|
+
first attempt, after six consecutive deaths.
|
|
929
|
+
|
|
930
|
+
So every document-producing step now carries:
|
|
931
|
+
|
|
932
|
+
1. the FIRST file write happens within the first ~12 tool calls — a skeleton of section headings
|
|
933
|
+
with one line of intent under each;
|
|
934
|
+
2. then fill it **one section per edit**, no edit longer than ~120 lines;
|
|
935
|
+
3. never go more than 2 minutes without a tool call;
|
|
936
|
+
4. when unsure whether to read more or to write — **write**.
|
|
937
|
+
|
|
938
|
+
**What this does not do**, stated plainly: it does not stop the deaths. The watchdog is unchanged.
|
|
939
|
+
It changes what survives one — previously nothing, now a skeleton on disk that the next attempt can
|
|
940
|
+
continue. The `~12` and `~120` are chosen parameters, not measured optima. Two possible regressions
|
|
941
|
+
were named by an independent reviewer and are **not measured**: an early skeleton may anchor a
|
|
942
|
+
structure chosen before understanding, and section-per-edit raises tool-call overhead.
|
|
943
|
+
|
|
944
|
+
Steps whose deliverable is a returned verdict rather than a document (the complexity router) are
|
|
945
|
+
deliberately excluded — "skeleton first" is nonsense there.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dzhechkov/skills-feature-adr",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
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
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"hashes": [
|
|
16
16
|
{
|
|
17
17
|
"alg": "SHA-256",
|
|
18
|
-
"content": "
|
|
18
|
+
"content": "bbb43101a686369a8662f47fe7b7aa75073b614a188c2752cd81d9570620bb3d"
|
|
19
19
|
}
|
|
20
20
|
]
|
|
21
21
|
},
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"hashes": [
|
|
36
36
|
{
|
|
37
37
|
"alg": "SHA-256",
|
|
38
|
-
"content": "
|
|
38
|
+
"content": "469873d967c4438afd0b25ec8b013347841e07328f508f6786f90cc6aa2f09b9"
|
|
39
39
|
}
|
|
40
40
|
]
|
|
41
41
|
},
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"hashes": [
|
|
66
66
|
{
|
|
67
67
|
"alg": "SHA-256",
|
|
68
|
-
"content": "
|
|
68
|
+
"content": "c1e4d257167fd2462e0c8e09f248fc1aef332aa34fb09dddd5940bdcc30ea34b"
|
|
69
69
|
}
|
|
70
70
|
]
|
|
71
71
|
},
|
|
@@ -305,7 +305,7 @@
|
|
|
305
305
|
"hashes": [
|
|
306
306
|
{
|
|
307
307
|
"alg": "SHA-256",
|
|
308
|
-
"content": "
|
|
308
|
+
"content": "bdedfed69d985eb6969f158a72d92f63c6b1b993008c3661f7ffc2c6a7ccfbae"
|
|
309
309
|
}
|
|
310
310
|
]
|
|
311
311
|
},
|
|
@@ -335,7 +335,7 @@
|
|
|
335
335
|
"hashes": [
|
|
336
336
|
{
|
|
337
337
|
"alg": "SHA-256",
|
|
338
|
-
"content": "
|
|
338
|
+
"content": "e6ea3a224b555d5b7410cf2ddf02a3fb8d009fa01109463353e2b993c318270c"
|
|
339
339
|
}
|
|
340
340
|
]
|
|
341
341
|
},
|
|
@@ -345,7 +345,7 @@
|
|
|
345
345
|
"hashes": [
|
|
346
346
|
{
|
|
347
347
|
"alg": "SHA-256",
|
|
348
|
-
"content": "
|
|
348
|
+
"content": "e2506e42251c74485188473acc4586f841afbec3ae7c5eaa8a3d8f73f6e14cc5"
|
|
349
349
|
}
|
|
350
350
|
]
|
|
351
351
|
},
|
|
@@ -355,7 +355,7 @@
|
|
|
355
355
|
"hashes": [
|
|
356
356
|
{
|
|
357
357
|
"alg": "SHA-256",
|
|
358
|
-
"content": "
|
|
358
|
+
"content": "ea03eee136c89267888b8034baa1a1cb0057314a2e745df71654a9ebee5d46b6"
|
|
359
359
|
}
|
|
360
360
|
]
|
|
361
361
|
},
|
|
@@ -365,7 +365,7 @@
|
|
|
365
365
|
"hashes": [
|
|
366
366
|
{
|
|
367
367
|
"alg": "SHA-256",
|
|
368
|
-
"content": "
|
|
368
|
+
"content": "d48a8d6eebf9760cc8f1a4f19df0e6afc31fcc1284b5eacfb604ad8ced6a0b53"
|
|
369
369
|
}
|
|
370
370
|
]
|
|
371
371
|
},
|
|
@@ -375,7 +375,7 @@
|
|
|
375
375
|
"hashes": [
|
|
376
376
|
{
|
|
377
377
|
"alg": "SHA-256",
|
|
378
|
-
"content": "
|
|
378
|
+
"content": "e9504cf1183bc0105167dc06108860882941fcf1b5be79d1978d8212c410ffbb"
|
|
379
379
|
}
|
|
380
380
|
]
|
|
381
381
|
},
|
|
@@ -385,7 +385,7 @@
|
|
|
385
385
|
"hashes": [
|
|
386
386
|
{
|
|
387
387
|
"alg": "SHA-256",
|
|
388
|
-
"content": "
|
|
388
|
+
"content": "2ef7cf316903f0cbb1fabe1f0e1770c22b2cb3a11a4118cac0529c25074b5e8a"
|
|
389
389
|
}
|
|
390
390
|
]
|
|
391
391
|
},
|
|
@@ -395,7 +395,7 @@
|
|
|
395
395
|
"hashes": [
|
|
396
396
|
{
|
|
397
397
|
"alg": "SHA-256",
|
|
398
|
-
"content": "
|
|
398
|
+
"content": "c8fdd1877d8d9515ab29f52c3db49ddd9612b5797bc071e6208def860c02e6f2"
|
|
399
399
|
}
|
|
400
400
|
]
|
|
401
401
|
},
|
|
@@ -805,7 +805,7 @@
|
|
|
805
805
|
"hashes": [
|
|
806
806
|
{
|
|
807
807
|
"alg": "SHA-256",
|
|
808
|
-
"content": "
|
|
808
|
+
"content": "1be239d2f02612bc33027767d9930eae8a2d6d5afd427d1485198bbdd821a3c7"
|
|
809
809
|
}
|
|
810
810
|
]
|
|
811
811
|
},
|
|
@@ -226,6 +226,14 @@ Execute protocol
|
|
|
226
226
|
→ Checkpoint N
|
|
227
227
|
```
|
|
228
228
|
|
|
229
|
+
**Write discipline — applies to EVERY step that produces a document.** Open the step's artifact within
|
|
230
|
+
your first ~12 tool calls as a SKELETON (its section headings, one line of intent each), then fill it one
|
|
231
|
+
section per edit, no edit longer than ~120 lines. Never go more than 2 minutes without a tool call, and
|
|
232
|
+
when you are unsure whether to read more or to write, WRITE. Reason: an executor silent for 180 seconds is
|
|
233
|
+
killed by the runtime, and thinking time grows with accumulated history — so unbounded exploration before
|
|
234
|
+
the first write is a deterministic death on a large repo, not bad luck. Each module restates this under
|
|
235
|
+
its own `## Write discipline (the 180-second rule)` heading.
|
|
236
|
+
|
|
229
237
|
### 4. Final Verification
|
|
230
238
|
After Step 8 (or Step 9 for L/XL) completes, verify:
|
|
231
239
|
- All mandatory artifacts exist per tier
|
|
@@ -616,6 +624,7 @@ npx @dzhechkov/skills-feature-adr init --with-learning --knowledge-extractor
|
|
|
616
624
|
| Ignore NO-GO verdict | Proceed despite Step 3.5 NO-GO | BLOCK — rework required |
|
|
617
625
|
| Skip gap loop | No gap detection in Step 8 | Missing requirements coverage check |
|
|
618
626
|
| Skip fleet QE for L/XL | No Step 9 for large features | BLOCK — fleet assessment mandatory |
|
|
627
|
+
| Unbounded reading before the first write | The step's artifact still does not exist after ~12 tool calls | BLOCK — write the skeleton now; a silent executor is killed at 180 s |
|
|
619
628
|
|
|
620
629
|
## Checkpoint Format
|
|
621
630
|
|
|
@@ -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
|
|
@@ -135,6 +135,32 @@ Before finalizing the plan, validate completeness:
|
|
|
135
135
|
|
|
136
136
|
This gap-check loop ensures the implementation plan is complete before coding begins.
|
|
137
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
|
+
|
|
138
164
|
## Output
|
|
139
165
|
|
|
140
166
|
### S-tier (minimal file — NOT inline-only)
|
|
@@ -44,6 +44,20 @@ function absolutizeRepo(raw, cwd) {
|
|
|
44
44
|
}
|
|
45
45
|
const ABSOLUTE_PATH_NOTE = ' All artifact paths in this prompt are ABSOLUTE. Read and write them exactly as given; do not cd first and do not re-relativize them.'
|
|
46
46
|
|
|
47
|
+
// WRITE DISCIPLINE — the anti-watchdog clause for every step whose DELIVERABLE IS A DOCUMENT.
|
|
48
|
+
// MEASURED (field report P14, two independent runs, agent journals on disk): Steps 5 and 6 never
|
|
49
|
+
// reached a single write — 18/18 attempts ended with FILE_WRITES=0, all of them killed inside the
|
|
50
|
+
// READING phase after >180 s of silence between tool calls; ~4M tokens and 1h54m per run bought
|
|
51
|
+
// nothing. Thinking time grows with accumulated history, so on a large repo this is DETERMINISTIC,
|
|
52
|
+
// not unlucky. The prompts said 'READ the code. Write <file>' with no reading budget and no order of
|
|
53
|
+
// operations, and the executor obeyed literally. Control experiment (owner, same slice/inputs/model,
|
|
54
|
+
// ONE added paragraph on write discipline): a 10-section skeleton landed 8 minutes in, on the FIRST
|
|
55
|
+
// attempt, after six consecutive deaths. One variable changed, opposite outcome. NOT CLAIMED: that
|
|
56
|
+
// this removes the watchdog class — it is a layer-4 prompt instruction, so it is probabilistic by
|
|
57
|
+
// construction; the deterministic half is the layer-1 test that this clause is PRESENT in every
|
|
58
|
+
// writing-step prompt (harness-core/test/feature-adr-write-discipline.test.ts).
|
|
59
|
+
const WRITE_DISCIPLINE = ' WRITE DISCIPLINE (mandatory, not advice). 1. Your FIRST file write must happen within your first 12 tool calls: create the SKELETON of the artifact — every section heading plus one line of intent under each. 2. Then fill it in ONE SECTION PER EDIT; no single edit longer than ~120 lines. 3. Never go more than 2 minutes without a tool call. 4. If you are unsure whether to read more or to write, WRITE. Reason: an executor that shows no activity for 180 seconds is KILLED by the runtime, so unbounded exploration before the first write makes the step die having written nothing at all.'
|
|
60
|
+
|
|
47
61
|
const UNSAFE_PATH_CHARS = /[\u0000-\u001f\u007f]/
|
|
48
62
|
const DOT_DOT_SEGMENT = /(^|\/)\.\.(\/|$)/
|
|
49
63
|
function hasUnsafePathChars(p) { return UNSAFE_PATH_CHARS.test(String(p)) }
|
|
@@ -128,7 +142,7 @@ const CKPT_FILE = FDIR + '/.fa-state/checkpoints.jsonl'
|
|
|
128
142
|
// M10 Stage-A, feature loop-designer). This region is now a GENERATED BLOB (regen-diff-gated by
|
|
129
143
|
// loop-blobs-regen.test.ts): edit the canonical TS FIRST, run node scripts/gen-loop-blobs.mjs,
|
|
130
144
|
// then re-splice. The value-pinned wiring tests in feature-adr-checkpoints.test.ts stay the net.
|
|
131
|
-
// ── BEGIN BLOB checkpoints@1.1.0 sha256:
|
|
145
|
+
// ── BEGIN BLOB checkpoints@1.1.0 sha256:d4dc6c956be3dd279b225bc5061ec4255babc44ffb5ca58646c9b6ea04a0c8c2 src=packages/@dzhechkov/harness-core/src/feature-adr-checkpoints.ts ──
|
|
132
146
|
const CHECKPOINT_STAGES = ['router', 'design', 'plan', 'code', 'qe', 'fleet'];
|
|
133
147
|
const STAGE_ARTIFACTS = {
|
|
134
148
|
router: null,
|
|
@@ -139,7 +153,7 @@ const STAGE_ARTIFACTS = {
|
|
|
139
153
|
fleet: '09_fleet_qe_assessment.md',
|
|
140
154
|
};
|
|
141
155
|
const CHECKPOINT_MAX_RESULT_CHARS = 12000;
|
|
142
|
-
const CKPT_SCHEMA_VERSION = 'fa-ckpt-
|
|
156
|
+
const CKPT_SCHEMA_VERSION = 'fa-ckpt-3';
|
|
143
157
|
function fnv1a(str) {
|
|
144
158
|
let h = 0x811c9dc5;
|
|
145
159
|
for (let i = 0; i < str.length; i++) {
|
|
@@ -157,6 +171,33 @@ function checkpointInputHash(stage, parts) {
|
|
|
157
171
|
function resumeMode(raw) {
|
|
158
172
|
return raw === 'never' ? 'never' : raw === 'force' ? 'force' : 'auto';
|
|
159
173
|
}
|
|
174
|
+
const DESIGN_SUBSTAGES = ['requirements', 'adr', 'qcsd', 'architecture'];
|
|
175
|
+
function designStageKey(sub) {
|
|
176
|
+
return 'design:' + sub;
|
|
177
|
+
}
|
|
178
|
+
function decideDesignFanResume(opts) {
|
|
179
|
+
const missingSubstages = [];
|
|
180
|
+
opts.required.forEach((sub, i) => {
|
|
181
|
+
const r = opts.results[i];
|
|
182
|
+
if (r === null || r === undefined)
|
|
183
|
+
missingSubstages.push(sub);
|
|
184
|
+
});
|
|
185
|
+
const missingArtifacts = [];
|
|
186
|
+
let probeMissing = false;
|
|
187
|
+
if (opts.artifacts.length > 0) {
|
|
188
|
+
if (opts.postRunListing === null)
|
|
189
|
+
probeMissing = true;
|
|
190
|
+
else
|
|
191
|
+
for (const rel of opts.artifacts)
|
|
192
|
+
if (!opts.postRunListing.has(rel))
|
|
193
|
+
missingArtifacts.push(rel);
|
|
194
|
+
}
|
|
195
|
+
const reason = missingSubstages.length > 0 ? 'substage-missing'
|
|
196
|
+
: probeMissing ? 'probe-not-established'
|
|
197
|
+
: missingArtifacts.length > 0 ? 'artifact-missing'
|
|
198
|
+
: 'ok';
|
|
199
|
+
return { complete: reason === 'ok', missingSubstages, missingArtifacts, reason };
|
|
200
|
+
}
|
|
160
201
|
function decideCheckpointResume(opts) {
|
|
161
202
|
if (opts.mode === 'never')
|
|
162
203
|
return { resume: false, reason: 'mode-never' };
|
|
@@ -236,10 +277,41 @@ function checkpointAppendCmd(fdirAbs, line) {
|
|
|
236
277
|
const file = shellQuote(fdirAbs + '/.fa-state/checkpoints.jsonl');
|
|
237
278
|
return 'mkdir -p ' + dir + " && printf '%s\\n' " + shellQuote(line) + ' >> ' + file;
|
|
238
279
|
}
|
|
280
|
+
function parseArtifactProbe(opts) {
|
|
281
|
+
if (opts.stdout === null || opts.stdout === undefined)
|
|
282
|
+
return null;
|
|
283
|
+
const known = new Map();
|
|
284
|
+
for (const rel of opts.required)
|
|
285
|
+
known.set('HAVE:' + rel, rel);
|
|
286
|
+
const found = new Set();
|
|
287
|
+
let sentinels = 0;
|
|
288
|
+
for (const raw of String(opts.stdout).split('\n')) {
|
|
289
|
+
const line = raw.trim();
|
|
290
|
+
if (line === '')
|
|
291
|
+
continue;
|
|
292
|
+
if (line === opts.sentinel) {
|
|
293
|
+
sentinels++;
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
if (sentinels > 0)
|
|
297
|
+
return null;
|
|
298
|
+
const rel = known.get(line);
|
|
299
|
+
if (rel === undefined)
|
|
300
|
+
return null;
|
|
301
|
+
found.add(rel);
|
|
302
|
+
}
|
|
303
|
+
if (sentinels !== 1)
|
|
304
|
+
return null;
|
|
305
|
+
return found;
|
|
306
|
+
}
|
|
239
307
|
// ── END BLOB checkpoints@1.1.0 ──
|
|
240
308
|
// glue (workflow-owned, hand-reviewed): legacy mirror names → the blob exports. The hash
|
|
241
309
|
// composition is UNCHANGED (same CKPT_SCHEMA_VERSION salt + JSON-tuple), so recorded
|
|
242
310
|
// checkpoints from pre-wrap runs still resume.
|
|
311
|
+
// Run-specific, not a fixed literal: a fixed one is a string an ARTIFACT FILE could be named, and a
|
|
312
|
+
// listing is exactly a list of filenames. Derived from the slug + dir so it stays stable across a
|
|
313
|
+
// resume (the workflow sandbox has no randomness, and a random one would break nothing here anyway).
|
|
314
|
+
const FAN_LS_SENTINEL = '<<<DZ-FAN-LS-END:' + fnv1a64(SLUG + '\0' + FDIR) + '>>>'
|
|
243
315
|
const CKPT_LS_SENTINEL = CHECKPOINT_LS_SENTINEL
|
|
244
316
|
const CKPT_MAX = CHECKPOINT_MAX_RESULT_CHARS
|
|
245
317
|
const STAGE_ARTIFACTS_CKPT = STAGE_ARTIFACTS
|
|
@@ -1450,20 +1522,20 @@ modelsUsed.ddd = modelLabel(archOpts)
|
|
|
1450
1522
|
// record the exact input each design agent received. designPairMeta[i] aligns with designThunks[i],
|
|
1451
1523
|
// which aligns with design[i] in the parallel result — one pair per design sub-stage.
|
|
1452
1524
|
const designPairMeta = []
|
|
1453
|
-
const reqPromptText = 'Step 1 (Requirements)' + (isLplus ? ' + Step 2 (Research)' : '') + ' of /feature-adr for "' + DESC + '" (tier ' + tier + ', slug ' + SLUG + '). Code: ' + CODE_HINT + '. APPLY these Step-0 recalled LEARNED PATTERNS (fold the applicable ones into requirements/constraints - the loop paying off): ' + LEARNED + '. Write ' + FDIR + '/01_requirements.md (functional + non-functional requirements, acceptance criteria, constraints, and an "Applied learned patterns" note).' + reqExtra + ' Return wrote[] + a 1-line summary.' + PS_GUIDANCE('design')
|
|
1454
|
-
designThunks.push(() => designStage(reqPromptText, reqOpts, FDIR + '/01_requirements.md', 'requirements'))
|
|
1525
|
+
const reqPromptText = 'Step 1 (Requirements)' + (isLplus ? ' + Step 2 (Research)' : '') + ' of /feature-adr for "' + DESC + '" (tier ' + tier + ', slug ' + SLUG + '). Code: ' + CODE_HINT + '. APPLY these Step-0 recalled LEARNED PATTERNS (fold the applicable ones into requirements/constraints - the loop paying off): ' + LEARNED + '. Write ' + FDIR + '/01_requirements.md (functional + non-functional requirements, acceptance criteria, constraints, and an "Applied learned patterns" note).' + reqExtra + ' Return wrote[] + a 1-line summary.' + PS_GUIDANCE('design') + WRITE_DISCIPLINE
|
|
1526
|
+
designThunks.push(() => subCheckpoint('requirements', reqPromptText, reqOpts, ['01_requirements.md'].concat(isLplus ? ['02_research.md'] : []), () => designStage(reqPromptText, reqOpts, FDIR + '/01_requirements.md', 'requirements')))
|
|
1455
1527
|
// the requirements prompt is where the Step-0 recalled lessons are INJECTED — recorded per pair
|
|
1456
1528
|
designPairMeta.push({ role: 'design:requirements', prompt: reqPromptText, model: modelLabel(reqOpts), lessons: (LEARNED && LEARNED !== 'none recalled') ? [String(LEARNED)] : [] })
|
|
1457
1529
|
if (isMplus) {
|
|
1458
|
-
const adrPromptText = 'Step 3 (ADR + shift-left testability) of /feature-adr for "' + DESC + '" (' + SLUG + '). READ the actual code (' + CODE_HINT + ') to ground it. ' + ADR_TEMPLATE_GUIDE + ' Write ' + FDIR + '/03_adr/001-' + SLUG + '.md as a MADR-structured ADR that PASSES the Step-8 ADR fitness checklist (do NOT emit the legacy shape). Emit ALL of these sections, in order: a decision-shaped # Title (present-tense imperative verb — the auto-filename tracks the feature slug, so the IMPERATIVE signal lives in the title); ## Status (proposed/accepted/rejected/deprecated/superseded + a reversibility/revisit clause); ## Context (neutral, problem-first, BEFORE the Decision); ## Decision Drivers (ranked/weighted D1, D2, …); ## Considered Options (frame the CHOSEN approach as one option ALONGSIDE the rejected ones, each with symmetric Pros:/Cons:); ## Decision (concrete/testable — exact names, versions, paths, commands); ## Rationale (map each point to a driver Dn + why the losers lost); ## Consequences (Positive + Negative/Accepted Downsides + Follow-up ADRs + After-action Review with owner + date); a REQUIRED ## Confirmation stanza with Method:, Monitoring:, Success metric:, Owner:, Load-bearing property:, and Required automated check: `<test file>` NAMING the load-bearing property that MUST have a Step-8 test (the recurring lesson: the key safety property is often the untested one); and a ## Links traceability block (requirements, driving use case, related ADRs). Add a one-line provenance note (model-generated, edited for clarity) and, for a long ADR, a top-of-file table of contents. Do NOT use an "Alternatives considered" or "Testability/shift-left" heading in place of Considered Options / Confirmation. When creating ADDITIONAL ADRs, name them 03_adr/NNN-{decision-slug}.md with a lowercase-kebab, present-tense imperative, dateless, ticketless slug. Return wrote[] + summary.'
|
|
1459
|
-
designThunks.push(() => designStage(adrPromptText, adrOpts, FDIR + '/03_adr/001-' + SLUG + '.md', 'adr'))
|
|
1530
|
+
const adrPromptText = 'Step 3 (ADR + shift-left testability) of /feature-adr for "' + DESC + '" (' + SLUG + '). READ the actual code (' + CODE_HINT + ') to ground it. ' + ADR_TEMPLATE_GUIDE + ' Write ' + FDIR + '/03_adr/001-' + SLUG + '.md as a MADR-structured ADR that PASSES the Step-8 ADR fitness checklist (do NOT emit the legacy shape). Emit ALL of these sections, in order: a decision-shaped # Title (present-tense imperative verb — the auto-filename tracks the feature slug, so the IMPERATIVE signal lives in the title); ## Status (proposed/accepted/rejected/deprecated/superseded + a reversibility/revisit clause); ## Context (neutral, problem-first, BEFORE the Decision); ## Decision Drivers (ranked/weighted D1, D2, …); ## Considered Options (frame the CHOSEN approach as one option ALONGSIDE the rejected ones, each with symmetric Pros:/Cons:); ## Decision (concrete/testable — exact names, versions, paths, commands); ## Rationale (map each point to a driver Dn + why the losers lost); ## Consequences (Positive + Negative/Accepted Downsides + Follow-up ADRs + After-action Review with owner + date); a REQUIRED ## Confirmation stanza with Method:, Monitoring:, Success metric:, Owner:, Load-bearing property:, and Required automated check: `<test file>` NAMING the load-bearing property that MUST have a Step-8 test (the recurring lesson: the key safety property is often the untested one); and a ## Links traceability block (requirements, driving use case, related ADRs). Add a one-line provenance note (model-generated, edited for clarity) and, for a long ADR, a top-of-file table of contents. Do NOT use an "Alternatives considered" or "Testability/shift-left" heading in place of Considered Options / Confirmation. When creating ADDITIONAL ADRs, name them 03_adr/NNN-{decision-slug}.md with a lowercase-kebab, present-tense imperative, dateless, ticketless slug. Return wrote[] + summary.' + WRITE_DISCIPLINE
|
|
1531
|
+
designThunks.push(() => subCheckpoint('adr', adrPromptText, adrOpts, ['03_adr/001-' + SLUG + '.md'], () => designStage(adrPromptText, adrOpts, FDIR + '/03_adr/001-' + SLUG + '.md', 'adr')))
|
|
1460
1532
|
designPairMeta.push({ role: 'design:adr', prompt: adrPromptText, model: modelLabel(adrOpts), lessons: [] })
|
|
1461
|
-
const qcsdPromptText = 'Step 3.5 (QCSD ideation swarm - HTSM quality criteria + SFDIPOT risk) of /feature-adr for "' + DESC + '" (' + SLUG + '). Assess quality criteria + product-factors risk. Write ' + FDIR + '/03.5_ideation_report.md with a GO/CONDITIONAL/NO-GO verdict + top quality risks for QE. On a CONDITIONAL verdict, write each condition as an amendment row in a `## Amendments` section. ' + AMENDMENT_RULE + ' Return wrote[] + summary.'
|
|
1462
|
-
designThunks.push(() => designStage(qcsdPromptText, qcsdOpts, FDIR + '/03.5_ideation_report.md', 'qcsd'))
|
|
1533
|
+
const qcsdPromptText = 'Step 3.5 (QCSD ideation swarm - HTSM quality criteria + SFDIPOT risk) of /feature-adr for "' + DESC + '" (' + SLUG + '). Assess quality criteria + product-factors risk. Write ' + FDIR + '/03.5_ideation_report.md with a GO/CONDITIONAL/NO-GO verdict + top quality risks for QE. On a CONDITIONAL verdict, write each condition as an amendment row in a `## Amendments` section. ' + AMENDMENT_RULE + ' Return wrote[] + summary.' + WRITE_DISCIPLINE
|
|
1534
|
+
designThunks.push(() => subCheckpoint('qcsd', qcsdPromptText, qcsdOpts, ['03.5_ideation_report.md'], () => designStage(qcsdPromptText, qcsdOpts, FDIR + '/03.5_ideation_report.md', 'qcsd')))
|
|
1463
1535
|
designPairMeta.push({ role: 'design:qcsd', prompt: qcsdPromptText, model: modelLabel(qcsdOpts), lessons: [] })
|
|
1464
1536
|
const archExtra = isLplus ? ' Also ' + FDIR + '/04_domain_model.md (DDD).' : ''
|
|
1465
|
-
const archPromptText = (isLplus ? 'Step 4 (DDD) + ' : '') + 'Step 5 (Architecture) of /feature-adr for "' + DESC + '" (' + SLUG + '). READ the code. Write ' + FDIR + '/05_architecture.md (components, data flow, integration points, the emit/merge/wiring shape).' + archExtra + ' Return wrote[] + summary.'
|
|
1466
|
-
designThunks.push(() => designStage(archPromptText, archOpts, FDIR + '/05_architecture.md', 'architecture'))
|
|
1537
|
+
const archPromptText = (isLplus ? 'Step 4 (DDD) + ' : '') + 'Step 5 (Architecture) of /feature-adr for "' + DESC + '" (' + SLUG + '). READ the code. Write ' + FDIR + '/05_architecture.md (components, data flow, integration points, the emit/merge/wiring shape).' + archExtra + ' Return wrote[] + summary.' + WRITE_DISCIPLINE
|
|
1538
|
+
designThunks.push(() => subCheckpoint('architecture', archPromptText, archOpts, ['05_architecture.md'].concat(isLplus ? ['04_domain_model.md'] : []), () => designStage(archPromptText, archOpts, FDIR + '/05_architecture.md', 'architecture')))
|
|
1467
1539
|
designPairMeta.push({ role: 'design:architecture', prompt: archPromptText, model: modelLabel(archOpts), lessons: [] })
|
|
1468
1540
|
}
|
|
1469
1541
|
// design checkpoint: ONE composite entry for the whole parallel design fan (requirements/ADR/QCSD/
|
|
@@ -1476,10 +1548,145 @@ const designHash = ckptHash('design', [tier, DESC, CODE_HINT, fnv1a64(String(LEA
|
|
|
1476
1548
|
const designArtifacts = ['01_requirements.md']
|
|
1477
1549
|
if (isMplus) designArtifacts.push('03_adr/001-' + SLUG + '.md', '03.5_ideation_report.md', '05_architecture.md')
|
|
1478
1550
|
if (isLplus) designArtifacts.push('02_research.md', '04_domain_model.md')
|
|
1479
|
-
|
|
1551
|
+
/**
|
|
1552
|
+
* PER-SIBLING checkpoint (SP-2, no-amplification). Before 2026-08-20 the whole fan shared ONE
|
|
1553
|
+
* all-or-nothing entry, so a single dead agent discarded three finished siblings — and because every
|
|
1554
|
+
* downstream hash is content-addressed on the design RESULT, re-running them produced different
|
|
1555
|
+
* summaries and a completed plan then read `stale-input` too. One death re-spent the entire run,
|
|
1556
|
+
* every time, which is why the field report saw 18 attempts rather than 6.
|
|
1557
|
+
*
|
|
1558
|
+
* `withCheckpoint` needs no change for this: it already does its own lookup, hash, artifact probe
|
|
1559
|
+
* and append per call. Nesting is the whole fix.
|
|
1560
|
+
*/
|
|
1561
|
+
function subCheckpoint(sub, prompt, opts, artifacts, thunk) {
|
|
1562
|
+
const key = 'design:' + sub
|
|
1563
|
+
// SP-3 (independence). The hash carries THIS sibling's own steering inputs — including its PROMPT
|
|
1564
|
+
// TEXT (owner decision 2026-08-20). Two consequences, both intended:
|
|
1565
|
+
// • a fix to one step's instructions invalidates exactly that step and nothing else, so a feature
|
|
1566
|
+
// still IN FLIGHT picks the corrected prompt up on its next invocation. A feature that already
|
|
1567
|
+
// finished is never re-invoked, so its written artifacts are untouched — which is the line the
|
|
1568
|
+
// owner drew: correct what has not run, leave alone what has.
|
|
1569
|
+
// • changing the ADR model no longer invalidates requirements. The old fan hash lumped every
|
|
1570
|
+
// model spec together, so one dial moved four stages.
|
|
1571
|
+
// The hash carries what actually steers THIS sibling, and nothing else. Review round 1 measured
|
|
1572
|
+
// both directions of wrongness in the first version:
|
|
1573
|
+
// OVER-invalidating — FIXED, by DELETION. CODEX_MODEL was folded in unconditionally, so changing
|
|
1574
|
+
// args.codexModel invalidated a sibling explicitly pinned to `sonnet`. Round 1 narrowed it to
|
|
1575
|
+
// Codex siblings; round 2 showed that was still wrong — a sibling pinned to
|
|
1576
|
+
// 'codex:gpt-5.6-sol:high' resolves its own id, yet still moved when the global dial moved. It
|
|
1577
|
+
// is gone entirely, because `modelLabel(opts)` — already in this hash — renders
|
|
1578
|
+
// 'codex:<resolvedId>:<reasoning>', and specToOpts resolves a bare 'codex' spec THROUGH
|
|
1579
|
+
// CODEX_MODEL. So the effective model is captured in every case, pinned or inherited, and a
|
|
1580
|
+
// second copy of it could only ever add false invalidation.
|
|
1581
|
+
// UNDER-invalidating — NAMED, NOT FIXED, because it cannot be fixed here. Editing the CONTENT of
|
|
1582
|
+
// a project-skills guidance file without changing which files exist leaves this hash unchanged,
|
|
1583
|
+
// so a stale sibling resumes. The reason is structural: the Step-0 probe deliberately returns
|
|
1584
|
+
// only {hasManifest, report} and DROPS the guidance text ("drop the big design/code/qe
|
|
1585
|
+
// strings"), so the workflow never holds the content to hash. My first fix claimed the prompt
|
|
1586
|
+
// covered it via PS_GUIDANCE — MEASURED FALSE: of the four sibling prompts only `requirements`
|
|
1587
|
+
// embeds PS_GUIDANCE; adr, qcsd and architecture do not. POLY.report therefore stays in the
|
|
1588
|
+
// hash as the best available proxy — it names the source files, so adding or removing one does
|
|
1589
|
+
// invalidate. Closing this properly means carrying a content digest out of the probe, which is
|
|
1590
|
+
// a change to the probe, not to this line.
|
|
1591
|
+
const h = ckptHash(key, [tier, DESC, CODE_HINT, fnv1a64(String(prompt)), modelLabel(opts), POLY.hasManifest, fnv1a64(String(POLY.report || '')), usageOverride, sub === 'requirements' ? fnv1a64(String(LEARNED)) : null])
|
|
1592
|
+
return withCheckpoint(key, 'Design', h, thunk, { artifacts: artifacts })
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
const designSubsRequired = ['requirements'].concat(isMplus ? ['adr', 'qcsd', 'architecture'] : [])
|
|
1596
|
+
const designFan = await parallel(designThunks)
|
|
1597
|
+
// SP-1 (completeness) — what may be CONSUMED is a different question from what may be WRITTEN. The
|
|
1598
|
+
// old code answered the second by crippling the first. Codex QE #2 (a one-file probe accepting a
|
|
1599
|
+
// design with no ADR) is preserved here, on the read side where it belongs.
|
|
1600
|
+
// LIVE results, never the start-of-run snapshot. Cross-family review round 1 (grade D) caught the
|
|
1601
|
+
// snapshot version: a stale non-null entry from a PREVIOUS run survives in it even when this run's
|
|
1602
|
+
// retry returned null, so an incomplete design would be declared complete — reopening the very hole
|
|
1603
|
+
// the old all-or-nothing gate existed to close. A sibling that returns non-null wrote its artifact;
|
|
1604
|
+
// a sibling that died returns null. Nothing needs re-probing to know that.
|
|
1605
|
+
// The artifact half needs a listing taken AFTER the fan, and nothing else will do. Round 2 caught the
|
|
1606
|
+
// version fed CKPT_LISTING (taken once at run start, so on a fresh slug it cannot contain what the fan
|
|
1607
|
+
// is about to write — every fresh M+ run read as incomplete). Round 3 caught the version with no probe
|
|
1608
|
+
// at all: an L-tier requirements sibling that writes 01_requirements.md, skips 02_research.md and
|
|
1609
|
+
// returns non-null was accepted, and Step 6 planned with no research behind it. A non-null result is
|
|
1610
|
+
// the agent's own word about its own work. One effort-low `find` is what turns it into evidence.
|
|
1611
|
+
// The probe must PROVE it ran, and it must not be forgeable by the very thing it inspects.
|
|
1612
|
+
// Three rounds of review taught the shape:
|
|
1613
|
+
// • round 4 — `|| true` around a suppressed listing made an unreadable FDIR indistinguishable from
|
|
1614
|
+
// an empty one, so the refusal named the wrong cause and printed the wrong repair;
|
|
1615
|
+
// • round 5 — `find | sed; echo SENT` emitted the sentinel even when find itself failed, because sed
|
|
1616
|
+
// masks the pipeline status;
|
|
1617
|
+
// • round 6 — and the deeper problem: a LISTING is a list of filenames, so the data can impersonate
|
|
1618
|
+
// the frame. A file named "01_requirements.md\n" prints as a line reading `01_requirements.md`
|
|
1619
|
+
// plus a blank one, and satisfied a requirement for the real file. No amount of sentinel hardening
|
|
1620
|
+
// fixes that, because the forgery is inside the payload.
|
|
1621
|
+
// So the probe no longer prints filenames AT ALL. It asks `[ -f <exact rel> ]` once per required
|
|
1622
|
+
// artifact and echoes a fixed token we already know the text of. A filename can no longer produce a
|
|
1623
|
+
// line, so it can no longer forge one — and `-f` on an exact path cannot be satisfied by a name that
|
|
1624
|
+
// merely renders like it. MEASURED red/green below in a real shell against a newline-bearing filename.
|
|
1625
|
+
let fanChecks = ''
|
|
1626
|
+
for (const rel of designArtifacts) fanChecks += '[ -f ' + shq(rel) + ' ] && echo ' + shq('HAVE:' + rel) + '; '
|
|
1627
|
+
const fanLsCmd = 'cd ' + shq(FDIR) + ' 2>/dev/null && { ' + fanChecks + 'echo ' + shq(FAN_LS_SENTINEL) + '; } || true'
|
|
1628
|
+
const fanLsOut = await agent('Run EXACTLY this via Bash and return its stdout VERBATIM (it may be empty) with NO code fences and NO commentary: ' + fanLsCmd, { label: 'design:artifact-probe', phase: 'Design', effort: 'low' })
|
|
1629
|
+
// The transcript is validated STRICTLY, not scanned. Round 7 measured the difference: an agent that
|
|
1630
|
+
// narrates ("Expected output when present: HAVE:01_requirements.md … Actual stdout: …") emits a line
|
|
1631
|
+
// byte-identical to the real token, and a parser that merely LOOKED for the token passed a design whose
|
|
1632
|
+
// artifact did not exist. parseArtifactProbe accepts only a subset of the known tokens followed by
|
|
1633
|
+
// exactly one sentinel and nothing else; anything unexpected returns null — inconclusive, never a pass.
|
|
1634
|
+
// It cannot stop an agent that deliberately emits the exact expected transcript; that residual is the
|
|
1635
|
+
// same trust the checkpoint reader and the Step-7.5 landing barrier already place in a relaying agent.
|
|
1636
|
+
const fanListing = parseArtifactProbe({ stdout: fanLsOut, sentinel: FAN_LS_SENTINEL, required: designArtifacts })
|
|
1637
|
+
if (fanListing === null) log('design artifact probe NOT ESTABLISHED — the transcript was not the command\'s own output (no completion sentinel, a second one, or an unexpected line). This is inconclusive, not clean.')
|
|
1638
|
+
const fanVerdict = decideDesignFanResume({ results: designFan, required: designSubsRequired, artifacts: designArtifacts, postRunListing: fanListing })
|
|
1639
|
+
const design = designFan
|
|
1480
1640
|
// training pairs: one per design sub-stage (designPairMeta[i] ↔ design[i]); a null sub-result emits
|
|
1481
1641
|
// no pair (a dead agent produced no output). Design has no per-stage QE grade — grade:null honestly.
|
|
1482
|
-
|
|
1642
|
+
// Pair capture has to answer TWO questions per-sibling that the whole-stage guard inside capturePairs
|
|
1643
|
+
// cannot: which siblings produced output THIS run, and whether the stage as a whole is a resume.
|
|
1644
|
+
// • The guard tests resumedStages for the exact name 'design'; per-sibling checkpointing records
|
|
1645
|
+
// 'design:requirements' instead, so every repair run read as wholly-live and re-appended the
|
|
1646
|
+
// resumed siblings' pairs (round 3).
|
|
1647
|
+
// • Filtering the resumed siblings out unconditionally then killed the BACKFILL path — the recovery
|
|
1648
|
+
// that exists for a run whose pairs were never written (round 4). Backfill is file-absence-guarded,
|
|
1649
|
+
// so it only ever recovers a wholly-missing file, and that is exactly the all-resumed case.
|
|
1650
|
+
// So: all four resumed ⇒ the stage IS a resume, hand capturePairs every record under a guard stage it
|
|
1651
|
+
// will read as resumed, and let the absence guard decide. Otherwise ⇒ emit only what ran this run.
|
|
1652
|
+
const designPairRecords = designPairMeta.map(function (m, i) { return { input: m.prompt, output: design ? design[i] : null, evaluation: { grade: null, gradedBy: null, lessonsInjected: m.lessons }, provenance: { model: m.model, family: tpFamily(m.model), role: m.role } } })
|
|
1653
|
+
const designAllResumed = designSubsRequired.length > 0 && designSubsRequired.every(function (sub) { return resumedStages.indexOf('design:' + sub) !== -1 })
|
|
1654
|
+
// NAMED, not fixed: a MIXED repair run whose earlier pair file was lost entirely does not recover the
|
|
1655
|
+
// resumed siblings' pairs — backfill would skip anyway once the file exists, and re-appending them in
|
|
1656
|
+
// capture mode is the duplication round 3 caught. Recovering that case needs per-record dedup inside
|
|
1657
|
+
// capturePairs, which is a change to the capture layer, not to this call.
|
|
1658
|
+
if (design && designAllResumed) await capturePairs('design', 'Design', designPairRecords, 'design:' + designSubsRequired[0])
|
|
1659
|
+
else if (design) await capturePairs('design', 'Design', designPairRecords.map(function (r, i) { return (resumedStages.indexOf('design:' + designSubsRequired[i]) !== -1) ? null : r }))
|
|
1660
|
+
|
|
1661
|
+
// SP-1 ENFORCED. Round 2 was right that the previous line only LOGGED: the comment above promised a
|
|
1662
|
+
// read-side gate and the body handed a fan containing nulls straight to Step 6, which then planned
|
|
1663
|
+
// off a missing ADR — the exact outcome Codex QE #2 named. Refusing here is cheap precisely because
|
|
1664
|
+
// this feature made the fan resumable: every sibling that DID finish is checkpointed, so the repair
|
|
1665
|
+
// re-invoke re-spends only the missing ones. (Pairs are captured first: what ran deserves its record.)
|
|
1666
|
+
if (!fanVerdict.complete) {
|
|
1667
|
+
const missing = fanVerdict.missingSubstages.join(', ')
|
|
1668
|
+
const missingArt = fanVerdict.missingArtifacts.join(', ')
|
|
1669
|
+
const what = fanVerdict.reason === 'substage-missing' ? 'sub-stage(s) [' + missing + '] returned nothing (a dead or limit-exhausted agent)'
|
|
1670
|
+
: fanVerdict.reason === 'probe-not-established' ? 'the post-run artifact probe could not be read, so completeness is NOT ESTABLISHED (never a pass)'
|
|
1671
|
+
: 'the design artifact(s) [' + missingArt + '] are absent although every sub-stage reported success — an agent said it wrote a file it did not write'
|
|
1672
|
+
// Honest about the repair: "resume for free" is only true when resume is actually available. With
|
|
1673
|
+
// checkpoints off or resume:'never' the whole fan re-runs, and saying otherwise would misprice the retry.
|
|
1674
|
+
// resume:'force' deliberately trusts the input hash and SKIPS the artifact probe, so a sibling that
|
|
1675
|
+
// reported success without writing its file resumes forever and this gate refuses forever. Telling
|
|
1676
|
+
// that operator to "just re-invoke" would be a loop, not a repair (round 5).
|
|
1677
|
+
const repair = (CHECKPOINTS_ON && RESUME_MODE === 'force' && fanVerdict.reason === 'artifact-missing')
|
|
1678
|
+
? "HOW TO REPAIR: this run used resume:'force', which skips the artifact probe when resuming — so the sibling that reported success without writing [" + missingArt + "] would resume again and hit this same refusal. Re-invoke with args.resume='never' (or delete features/" + SLUG + "/.fa-state/) to make it actually re-run."
|
|
1679
|
+
: (CHECKPOINTS_ON && RESUME_MODE !== 'never')
|
|
1680
|
+
? 'HOW TO REPAIR: re-invoke with the SAME slug — the sub-stages that finished are individually checkpointed and resume for free, so only the failing one re-runs.'
|
|
1681
|
+
: 'HOW TO REPAIR: re-invoke with the SAME slug. NOTE: this run had ' + (CHECKPOINTS_ON ? "resume:'never', so its finished siblings ARE recorded — drop that argument and the retry re-runs only the failing one" : 'checkpoints disabled, so this run recorded NOTHING — dropping args.checkpoints:false does not make THIS retry cheap (the whole fan re-runs and is re-spent once more); it only makes the run AFTER it cheap') + '.'
|
|
1682
|
+
log('design fan INCOMPLETE (' + fanVerdict.reason + ') — ' + what + '. REFUSING to plan off a partial design.')
|
|
1683
|
+
// A run that stops here still SPENT its router + design phases, so it belongs in the cost ledger —
|
|
1684
|
+
// the same reason the K2 plan-gate refusal logs one. An unlogged refusal makes design stops
|
|
1685
|
+
// invisible to cost analysis, which is how a stage that keeps dying stays cheap-looking.
|
|
1686
|
+
// (coderUsed/qe are the outer bindings, both still null here, so the row reports null honestly.)
|
|
1687
|
+
await appendRunCostRow('design-gate', 'Design')
|
|
1688
|
+
return { tier: tier, phase: 'design-incomplete', slug: SLUG, artifactsDir: FDIR, missingSubstages: fanVerdict.missingSubstages, missingArtifacts: fanVerdict.missingArtifacts, reason: fanVerdict.reason, modelsUsed: modelsUsed, gates: { design: fanVerdict.reason === 'probe-not-established' ? 'not-established' : 'incomplete', plan: 'not-run', planCompleteness: 'not-run', challengePanel: 'not-run', code: 'not-run', qe: 'not-run' }, resumedStages: resumedStages, checkpointing: CHECKPOINTS_ON ? RESUME_MODE : 'off', trainingPairs: CAPTURE_PAIRS ? TP_DIR : 'off', captureFailures: captureFailures, usageEvents: usageEvents, usageThreshold: USAGE_THRESHOLD, polymorphism: POLY.hasManifest ? POLY.report : null, note: 'REFUSED at the Step-5/6 boundary: ' + what + ', so the design is incomplete and Step 6 was NOT dispatched. Planning off a partial design produces a plan with no ADR behind it. ' + repair + ' If a sibling died on a Claude limit, add usage-adaptive routing or route that stage to Codex first (args.models). To rebuild the whole design from scratch instead, re-invoke with args.resume=\'never\'.' }
|
|
1689
|
+
}
|
|
1483
1690
|
|
|
1484
1691
|
// Step 6: Plan — optionally routed to Codex's top model (opt-in via args.planner='codex').
|
|
1485
1692
|
// The user opts in at pre-flight ('use the top Codex model for planning?'); we route the Plan step to
|
|
@@ -1487,7 +1694,7 @@ if (design) await capturePairs('design', 'Design', designPairMeta.map(function (
|
|
|
1487
1694
|
// unavailable/errors — the pipeline never blocks on Codex.
|
|
1488
1695
|
phase('Plan')
|
|
1489
1696
|
await usageProbe('Plan')
|
|
1490
|
-
const planPrompt = 'Step 6 (SPARC-GOAP implementation plan) of /feature-adr for "' + DESC + '" (' + SLUG + ', tier ' + tier + '). Given the requirements + ADR + architecture in ' + FDIR + ', decompose into milestones + concrete tasks with success metrics. Write ' + FDIR + '/06_implementation_plan.md. END the plan with a trailing `EXPECTED_CODE_TARGETS:` block listing, one per line as `- <repo-relative path>`, EVERY production/test/config/doc file Step 7 is expected to create or modify. This block is machine-read by the Step-7.5 landing barrier: only paths it ESTABLISHES can ever count as landed, so an absent or unpollable block makes the barrier verdict INCONCLUSIVE. List only real targets outside features/, .dz/, .agentic-qe/ and roam/. If any corrections from Step 3.5 (a CONDITIONAL verdict) or other sources are folded into this plan, carry them in a `## Amendments` section. ' + AMENDMENT_RULE + ' Return wrote[] + summary.' + ABSOLUTE_PATH_NOTE
|
|
1697
|
+
const planPrompt = 'Step 6 (SPARC-GOAP implementation plan) of /feature-adr for "' + DESC + '" (' + SLUG + ', tier ' + tier + '). Given the requirements + ADR + architecture in ' + FDIR + ', decompose into milestones + concrete tasks with success metrics. Write ' + FDIR + '/06_implementation_plan.md. END the plan with a trailing `EXPECTED_CODE_TARGETS:` block listing, one per line as `- <repo-relative path>`, EVERY production/test/config/doc file Step 7 is expected to create or modify. This block is machine-read by the Step-7.5 landing barrier: only paths it ESTABLISHES can ever count as landed, so an absent or unpollable block makes the barrier verdict INCONCLUSIVE. List only real targets outside features/, .dz/, .agentic-qe/ and roam/. If any corrections from Step 3.5 (a CONDITIONAL verdict) or other sources are folded into this plan, carry them in a `## Amendments` section. ' + AMENDMENT_RULE + ' Return wrote[] + summary.' + ABSOLUTE_PATH_NOTE + WRITE_DISCIPLINE
|
|
1491
1698
|
// Resolve the plan model. args.models.plan wins; else the planner:'codex' knob (via routingRequested +
|
|
1492
1699
|
// DEFAULT_MODELS/coder-fold) or the DEFAULT_MODELS.plan ('sonnet') under routing; else {} (BC).
|
|
1493
1700
|
const planModel = resolveStageModel('plan')
|