@dzhechkov/skills-feature-adr 1.3.63 → 1.3.64

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
@@ -3,38 +3,6 @@
3
3
  "version": 1,
4
4
  "pack": "skills-feature-adr",
5
5
  "files": [
6
- {
7
- "path": ".agentic-qe/brain.rvf",
8
- "sha256": "70f89fc8d429547e83bfae570908ab29d03310d0ea6f665041e9d234a613c9b3"
9
- },
10
- {
11
- "path": ".agentic-qe/brain.rvf.lock",
12
- "sha256": "dabbafbdcc8ebd25620e26be72e0416225740d0706e6d955d19d31beb5179a49"
13
- },
14
- {
15
- "path": ".agentic-qe/memory.db",
16
- "sha256": "a9a1c9c9a16a4928935c1b9683ac06dab3d9a74dd4084e11c48f81dd95f9d9b6"
17
- },
18
- {
19
- "path": ".agentic-qe/memory.db-shm",
20
- "sha256": "e0d6e3d8739fca2b4e1d7c8f2c2287ca1e997f25278e57ca51b6fc8928404d06"
21
- },
22
- {
23
- "path": ".agentic-qe/memory.db-wal",
24
- "sha256": "f4ffd1e7c99d271374117717ff4d5206c620180e3c36de6a6d445ff513b8bd05"
25
- },
26
- {
27
- "path": ".agentic-qe/patterns.rvf",
28
- "sha256": "69cfe471991b1d375cfbdcdb2df88226f86195659f7aa761581ca4d5f46ff24a"
29
- },
30
- {
31
- "path": ".agentic-qe/patterns.rvf.idmap.json",
32
- "sha256": "1f8838c958b471fe5d819d681d29b1965a75470a9e4d6a2171d6c7e620ba223e"
33
- },
34
- {
35
- "path": ".agentic-qe/patterns.rvf.lock",
36
- "sha256": "f7c13d34313c97974dc8ba868859d68201a2d6a2dcba6670d2e52ec66d010081"
37
- },
38
6
  {
39
7
  "path": "CHANGELOG.md",
40
8
  "sha256": "e4f50d4f757f877b54c8d25dfc1321776545be4cb814d5f17ee1128ee2320982"
@@ -45,7 +13,7 @@
45
13
  },
46
14
  {
47
15
  "path": "README.md",
48
- "sha256": "e3aec229dfc9657d90214618356600eb7c25481e4f3d21f32ca8cf33f7b543e7"
16
+ "sha256": "b3095a1ad16fe17c103fb167e2844dabfe61f2a67dde3f797521c7916f41ddb3"
49
17
  },
50
18
  {
51
19
  "path": "bin/cli.js",
@@ -57,7 +25,7 @@
57
25
  },
58
26
  {
59
27
  "path": "package.json",
60
- "sha256": "e8683f389e05702b2a8f382428eb62ffd8cae6802460905690f47acd013caa72"
28
+ "sha256": "98aaa022a135e9cf92c534e191c96bdcd0254bf1ea8d7aba42745a82071ad4c2"
61
29
  },
62
30
  {
63
31
  "path": "scripts/sync-templates.js",
@@ -141,7 +109,7 @@
141
109
  },
142
110
  {
143
111
  "path": "templates/.claude/skills/explore/SKILL.md",
144
- "sha256": "c755a7bcdfe97fb7cb0bcd7bf3b1fd8a2d238ef082967fbf87740fc808302542"
112
+ "sha256": "2473e7961845db0d64566cd42627b374d1830664b9e302058a1b1aa466bc7361"
145
113
  },
146
114
  {
147
115
  "path": "templates/.claude/skills/explore/references/questioning-techniques.md",
@@ -153,7 +121,7 @@
153
121
  },
154
122
  {
155
123
  "path": "templates/.claude/skills/feature-adr/SKILL.md",
156
- "sha256": "14edee77f2d108815e2a2c1d3cf40226ef277659b9be1232230fc3c8bf1a31f9"
124
+ "sha256": "c422d1e2b50cf7b8bd56cd5d886d96af30d8bf33a468b925396f395ebf32de53"
157
125
  },
158
126
  {
159
127
  "path": "templates/.claude/skills/feature-adr/examples/sample-feature-output.md",
@@ -293,7 +261,7 @@
293
261
  },
294
262
  {
295
263
  "path": "templates/.claude/skills/frontend-design/SKILL.md",
296
- "sha256": "742519aca53dab0f10e903e19bd1641b62dd975cdd0df9aab3b2ac3173ce0ec2"
264
+ "sha256": "c9e93c1b95b99a9e120b93d7683afa804713f98ce44bf29ba6d24e9ea1b44aee"
297
265
  },
298
266
  {
299
267
  "path": "templates/.claude/skills/knowledge-extractor/SKILL.md",
@@ -337,7 +305,7 @@
337
305
  },
338
306
  {
339
307
  "path": "templates/.claude/skills/problem-solver-enhanced/SKILL.md",
340
- "sha256": "530623c986dee931d47314c22195ea9de50a75f2a0daecfc469fb015c3dd3415"
308
+ "sha256": "031378aa2e10c98928e35de07d454135e73550cd0c49f0cd886d53e4208d6b9f"
341
309
  },
342
310
  {
343
311
  "path": "templates/.claude/skills/system-grill/JOURNAL-TEMPLATE.md",
@@ -349,7 +317,7 @@
349
317
  },
350
318
  {
351
319
  "path": "templates/.claude/workflows/feature-adr.js",
352
- "sha256": "466adce16bb53b7b6d3d40f7ab21580b978d53f00b76628c830e4ed16127b586"
320
+ "sha256": "e02d770867542d0d1505baca64c3008ff1bbc0048b291bd30be7ccc41bdc06a1"
353
321
  },
354
322
  {
355
323
  "path": "templates/lib/memory-protocol.md",
@@ -377,5 +345,5 @@
377
345
  }
378
346
  ]
379
347
  },
380
- "signature": "+FPXqVvujh2lZm4KF3Vva8BVv4cfmtYLdLq6gGPwOB0nnjqyJFlFEcRVfavGaIbQGAbLQ7Lnjcxs+iT8O2g2BQ=="
348
+ "signature": "ac1qFz1SL9en2rAGmTGAYgsXlnlNG7tX3LCKhQFdt0nngZoLWW3LIT9pXBKrSAwKLAsqLXc1EVcmx2fmupeyAg=="
381
349
  }
package/README.md CHANGED
@@ -106,6 +106,32 @@ ARCHITECTURE → IMPLEMENTATION → CODE → QE → FLEET QE
106
106
  # Full protocols + 6 extra skills, up to 7 fleet QE agents
107
107
  ```
108
108
 
109
+ ### Durable checkpoints + resume (v1.3.64)
110
+
111
+ Every expensive stage (router / design / plan / code / qe / fleet) checkpoints its result into
112
+ `features/<slug>/.fa-state/checkpoints.jsonl`. Re-invoking the workflow with the SAME slug resumes
113
+ completed stages instead of re-running them — covering both a **killed run** (previously a total
114
+ restart: the exact failure mode that motivated usage-adaptive routing) and the **standard L/XL
115
+ two-phase flow** (the `stopAfter: 'plan'` → `stopAfter: 'none'` re-invoke now resumes
116
+ router+design+plan for free).
117
+
118
+ ```js
119
+ // run 1 (dies mid-code, or stops at the L/XL plan checkpoint)…
120
+ Workflow({ scriptPath: '.claude/workflows/feature-adr.js', args: { slug: 'add-x', description: '…' } })
121
+ // run 2 — same slug: completed stages resume, only the unfinished work runs
122
+ Workflow({ scriptPath: '.claude/workflows/feature-adr.js', args: { slug: 'add-x', description: '…', stopAfter: 'none' } })
123
+ // → log: "checkpoint: router RESUMED … design RESUMED … plan RESUMED"; result carries resumedStages
124
+ ```
125
+
126
+ Knobs: `args.resume: 'auto'` (default — resume only on input-hash match AND every tier-required
127
+ artifact present) · `'never'` (ignore recorded state) · `'force'` (trust the hash, skip artifact
128
+ probes); `args.checkpoints: false` disables the layer entirely. **Honest scope:** resume proves the
129
+ run *inputs* are unchanged and artifacts exist — it does NOT fingerprint the working tree; after
130
+ manual edits mid-feature pass `resume: 'never'` (or delete `.fa-state/`) and re-QE. A stale-input
131
+ checkpoint never resumes in any mode. RU: упавший L/XL-ран продолжает с места смерти, а не
132
+ пере-тратит выполненные стадии; штатный двухфазный L/XL-флоу тоже дешевеет — re-invoke после
133
+ план-чекпоинта возобновляет router+design+plan из чекпоинтов.
134
+
109
135
  ### Codex model routing (optional)
110
136
 
111
137
  If [Codex](https://developers.openai.com/codex) is installed + logged in, the pipeline can route work to
package/bin/cli.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dzhechkov/skills-feature-adr",
3
- "version": "1.3.63",
3
+ "version": "1.3.64",
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"
@@ -15,10 +15,6 @@
15
15
  ".dz-manifest.json",
16
16
  "sbom.json"
17
17
  ],
18
- "scripts": {
19
- "test": "node --test \"test/**/*.test.js\"",
20
- "prepack": "node -e \"const fs=require('fs');const bad=['.claude','.skills-feature-adr.json'].filter(p=>fs.existsSync(p));if(bad.length){console.error('prepack guard: stray init artifacts in package dir: '+bad.join(', ')+' — remove before packing');process.exit(1)}\""
21
- },
22
18
  "keywords": [
23
19
  "claude",
24
20
  "claude-code",
@@ -63,5 +59,8 @@
63
59
  },
64
60
  "publishConfig": {
65
61
  "access": "public"
62
+ },
63
+ "scripts": {
64
+ "test": "node --test \"test/**/*.test.js\""
66
65
  }
67
- }
66
+ }
package/sbom.json CHANGED
@@ -9,86 +9,6 @@
9
9
  }
10
10
  },
11
11
  "components": [
12
- {
13
- "type": "file",
14
- "name": ".agentic-qe/brain.rvf",
15
- "hashes": [
16
- {
17
- "alg": "SHA-256",
18
- "content": "70f89fc8d429547e83bfae570908ab29d03310d0ea6f665041e9d234a613c9b3"
19
- }
20
- ]
21
- },
22
- {
23
- "type": "file",
24
- "name": ".agentic-qe/brain.rvf.lock",
25
- "hashes": [
26
- {
27
- "alg": "SHA-256",
28
- "content": "dabbafbdcc8ebd25620e26be72e0416225740d0706e6d955d19d31beb5179a49"
29
- }
30
- ]
31
- },
32
- {
33
- "type": "file",
34
- "name": ".agentic-qe/memory.db",
35
- "hashes": [
36
- {
37
- "alg": "SHA-256",
38
- "content": "a9a1c9c9a16a4928935c1b9683ac06dab3d9a74dd4084e11c48f81dd95f9d9b6"
39
- }
40
- ]
41
- },
42
- {
43
- "type": "file",
44
- "name": ".agentic-qe/memory.db-shm",
45
- "hashes": [
46
- {
47
- "alg": "SHA-256",
48
- "content": "e0d6e3d8739fca2b4e1d7c8f2c2287ca1e997f25278e57ca51b6fc8928404d06"
49
- }
50
- ]
51
- },
52
- {
53
- "type": "file",
54
- "name": ".agentic-qe/memory.db-wal",
55
- "hashes": [
56
- {
57
- "alg": "SHA-256",
58
- "content": "f4ffd1e7c99d271374117717ff4d5206c620180e3c36de6a6d445ff513b8bd05"
59
- }
60
- ]
61
- },
62
- {
63
- "type": "file",
64
- "name": ".agentic-qe/patterns.rvf",
65
- "hashes": [
66
- {
67
- "alg": "SHA-256",
68
- "content": "69cfe471991b1d375cfbdcdb2df88226f86195659f7aa761581ca4d5f46ff24a"
69
- }
70
- ]
71
- },
72
- {
73
- "type": "file",
74
- "name": ".agentic-qe/patterns.rvf.idmap.json",
75
- "hashes": [
76
- {
77
- "alg": "SHA-256",
78
- "content": "1f8838c958b471fe5d819d681d29b1965a75470a9e4d6a2171d6c7e620ba223e"
79
- }
80
- ]
81
- },
82
- {
83
- "type": "file",
84
- "name": ".agentic-qe/patterns.rvf.lock",
85
- "hashes": [
86
- {
87
- "alg": "SHA-256",
88
- "content": "f7c13d34313c97974dc8ba868859d68201a2d6a2dcba6670d2e52ec66d010081"
89
- }
90
- ]
91
- },
92
12
  {
93
13
  "type": "file",
94
14
  "name": "CHANGELOG.md",
@@ -115,7 +35,7 @@
115
35
  "hashes": [
116
36
  {
117
37
  "alg": "SHA-256",
118
- "content": "e3aec229dfc9657d90214618356600eb7c25481e4f3d21f32ca8cf33f7b543e7"
38
+ "content": "b3095a1ad16fe17c103fb167e2844dabfe61f2a67dde3f797521c7916f41ddb3"
119
39
  }
120
40
  ]
121
41
  },
@@ -145,7 +65,7 @@
145
65
  "hashes": [
146
66
  {
147
67
  "alg": "SHA-256",
148
- "content": "e8683f389e05702b2a8f382428eb62ffd8cae6802460905690f47acd013caa72"
68
+ "content": "98aaa022a135e9cf92c534e191c96bdcd0254bf1ea8d7aba42745a82071ad4c2"
149
69
  }
150
70
  ]
151
71
  },
@@ -355,7 +275,7 @@
355
275
  "hashes": [
356
276
  {
357
277
  "alg": "SHA-256",
358
- "content": "c755a7bcdfe97fb7cb0bcd7bf3b1fd8a2d238ef082967fbf87740fc808302542"
278
+ "content": "2473e7961845db0d64566cd42627b374d1830664b9e302058a1b1aa466bc7361"
359
279
  }
360
280
  ]
361
281
  },
@@ -385,7 +305,7 @@
385
305
  "hashes": [
386
306
  {
387
307
  "alg": "SHA-256",
388
- "content": "14edee77f2d108815e2a2c1d3cf40226ef277659b9be1232230fc3c8bf1a31f9"
308
+ "content": "c422d1e2b50cf7b8bd56cd5d886d96af30d8bf33a468b925396f395ebf32de53"
389
309
  }
390
310
  ]
391
311
  },
@@ -735,7 +655,7 @@
735
655
  "hashes": [
736
656
  {
737
657
  "alg": "SHA-256",
738
- "content": "742519aca53dab0f10e903e19bd1641b62dd975cdd0df9aab3b2ac3173ce0ec2"
658
+ "content": "c9e93c1b95b99a9e120b93d7683afa804713f98ce44bf29ba6d24e9ea1b44aee"
739
659
  }
740
660
  ]
741
661
  },
@@ -845,7 +765,7 @@
845
765
  "hashes": [
846
766
  {
847
767
  "alg": "SHA-256",
848
- "content": "530623c986dee931d47314c22195ea9de50a75f2a0daecfc469fb015c3dd3415"
768
+ "content": "031378aa2e10c98928e35de07d454135e73550cd0c49f0cd886d53e4208d6b9f"
849
769
  }
850
770
  ]
851
771
  },
@@ -875,7 +795,7 @@
875
795
  "hashes": [
876
796
  {
877
797
  "alg": "SHA-256",
878
- "content": "466adce16bb53b7b6d3d40f7ab21580b978d53f00b76628c830e4ed16127b586"
798
+ "content": "e02d770867542d0d1505baca64c3008ff1bbc0048b291bd30be7ccc41bdc06a1"
879
799
  }
880
800
  ]
881
801
  },
@@ -10,7 +10,7 @@ description: >
10
10
  provide solutions until task is fully explored.
11
11
  trust_tier: 1
12
12
  trust_tier_label: "Structured"
13
- trust_tier_path: "Run /bto-test to promote to Tier 2"
13
+ trust_tier_path: "Run a BTO evaluation (see the skills-bto package) to promote to Tier 2"
14
14
  ---
15
15
 
16
16
  # Explore: Adaptive Task Clarification
@@ -11,7 +11,7 @@ description: >
11
11
  "implement feature", "/feature-adr".
12
12
  trust_tier: 0
13
13
  trust_tier_label: "Advisory"
14
- trust_tier_path: "Run /bto-test to promote to Tier 1"
14
+ trust_tier_path: "Run a BTO evaluation (see the skills-bto package) to promote to Tier 1"
15
15
  agentic_qe_version: "7.5.1"
16
16
  agentic_qe_source: "https://github.com/proffesor-for-testing/agentic-qe"
17
17
  agentic_qe_skills_core: 9
@@ -4,7 +4,7 @@ description: Create distinctive, production-grade frontend interfaces with high
4
4
  license: Complete terms in LICENSE.txt
5
5
  trust_tier: 0
6
6
  trust_tier_label: "Advisory"
7
- trust_tier_path: "Add references/ directory and run /bto-test to promote to Tier 1"
7
+ trust_tier_path: "Add references/ directory and run a BTO evaluation (see the skills-bto package) to promote to Tier 1"
8
8
  ---
9
9
 
10
10
  This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.
@@ -3,7 +3,7 @@ name: problem-solver-enhanced
3
3
  description: Elite problem-solving system combining first principles thinking, game theory, root cause analysis, systematic frameworks AND TRIZ inventive principles for solving complex challenges. Use when facing complex challenges with unclear solutions, strategic decisions with multiple stakeholders, problems requiring breakthrough (not incremental) solutions, trade-offs that seem impossible to resolve, competitive scenarios requiring game-theoretic analysis, system design problems, or any situation requiring both analytical rigor and inventive thinking beyond surface-level solutions.
4
4
  trust_tier: 1
5
5
  trust_tier_label: "Structured"
6
- trust_tier_path: "Run /bto-test to promote to Tier 2"
6
+ trust_tier_path: "Run a BTO evaluation (see the skills-bto package) to promote to Tier 2"
7
7
  ---
8
8
 
9
9
  # Elite Problem Solver (Enhanced with TRIZ)
@@ -1,6 +1,6 @@
1
1
  export const meta = {
2
2
  name: 'feature-adr',
3
- description: 'Canonical /feature-adr --full-qe-extended pipeline as a reusable workflow: router+RECALL then design(ADR, applies learned patterns) then plan then code then agentic-qe QE+TEACH, producing features/<slug>/00-09 artifacts. MANDATORY in-process self-learning loop (Step-0 recall, apply, Step-8 teach). OPTIONAL Codex routing: args.planner=codex (Step-6), args.coder/qeReviewer=codex-fallback (Step-7/8 fall back to Codex when Claude limits exhaust; args.codexModel default auto, Codex self-selects top). Hybrid checkpoints (S/M autonomous; L/XL stop-after-plan).',
3
+ description: 'Canonical /feature-adr --full-qe-extended pipeline as a reusable workflow: router+RECALL then design(ADR, applies learned patterns) then plan then code then agentic-qe QE+TEACH, producing features/<slug>/00-09 artifacts. MANDATORY in-process self-learning loop (Step-0 recall, apply, Step-8 teach). OPTIONAL Codex routing: args.planner=codex (Step-6), args.coder/qeReviewer=codex-fallback (Step-7/8 fall back to Codex when Claude limits exhaust; args.codexModel default auto, Codex self-selects top). Hybrid checkpoints (S/M autonomous; L/XL stop-after-plan). DURABLE per-stage checkpoints in features/<slug>/.fa-state/ (args.resume auto|never|force, args.checkpoints:false to disable): a dead run or an L/XL re-invoke resumes completed stages instead of re-spending them.',
4
4
  whenToUse: 'ultracode + a feature implementation. Invoke via Workflow({scriptPath:".claude/workflows/feature-adr.js", args:{slug, description, code, tier, stopAfter, planner, coder, qeReviewer, codexModel, brain}}) instead of an ad-hoc orchestration, so every feature ships with an ADR + inline agentic-qe QE + self-learning. args.brain pins the self-learning loop (recall/teach) to ONE canonical brain store (default = the workspace root) so lessons never fragment into a target repo when the coder cd`s away.',
5
5
  phases: [
6
6
  { title: 'Router', detail: 'Step 0 - classify + self-learning recall' },
@@ -100,6 +100,110 @@ const DZ_RECALL = (terms) => 'cd ' + BRAIN + ' && ' + DZ + ' recall "' + terms +
100
100
  const DZ_TEACH = (lesson, reward, domain) =>
101
101
  'cd ' + BRAIN + ' && ' + DZ + ' teach "' + lesson + '" --reward ' + reward + ' --domain ' + domain + ' --project ' + BRAIN
102
102
 
103
+ // ── Durable checkpoints + resume (backlog 49e4a95b) — inline mirror of ──
104
+ // ── harness-core/src/feature-adr-checkpoints.ts (the workflow is self-contained, no imports) ──
105
+ // After each expensive stage a cheap effort-low agent appends {stage, inputHash, result} to
106
+ // features/<slug>/.fa-state/checkpoints.jsonl (the sandbox has no fs — the agent IS the fs). On the
107
+ // next run with the same slug, a stage is SKIPPED only when its recorded inputHash matches AND its
108
+ // expected artifact is still on disk. This covers BOTH the crash case (a dead L/XL session used to
109
+ // re-spend every completed stage) and the STANDARD L/XL two-phase flow (stop-after-plan → re-invoke
110
+ // used to re-run router+design+plan wholesale). Granularity is per-STAGE — a death mid-code re-runs
111
+ // the code stage only, never Steps 0–6. args.checkpoints:false disables everything; args.resume: 'auto'(default)
112
+ // | 'never' (ignore recorded state) | 'force' (trust the hash, skip the artifact probe). A STALE
113
+ // inputHash NEVER resumes in any mode — 'force' relaxes only the artifact probe (load-bearing,
114
+ // tested in feature-adr-checkpoints.test.ts).
115
+ const CHECKPOINTS_ON = A.checkpoints !== false
116
+ const RESUME_MODE = A.resume === 'never' ? 'never' : (A.resume === 'force' ? 'force' : 'auto')
117
+ const CKPT_FILE = FDIR + '/.fa-state/checkpoints.jsonl'
118
+ const CKPT_LS_SENTINEL = '---FA-CKPT-LS---'
119
+ const CKPT_MAX = 12000
120
+ // SALTED into every hash: bump when stage semantics/prompts/composite shapes change → every old
121
+ // checkpoint hashes stale and re-runs (never resumes into new logic).
122
+ const CKPT_SCHEMA_VERSION = 'fa-ckpt-2'
123
+ // HONEST SCOPE (the narrowed promise): the hash proves the run INPUTS are unchanged; the probe
124
+ // proves the stage artifacts EXIST. Neither fingerprints the working tree — a crash-resume
125
+ // legitimately sees the dead run's uncommitted writes, so a tree hash would invalidate every real
126
+ // resume. After MANUAL edits: resume:'never' or delete .fa-state/, and re-QE independently.
127
+ const STAGE_ARTIFACTS_CKPT = { router: null, design: '01_requirements.md', plan: '06_implementation_plan.md', code: '07_code_changes/change_manifest.md', qe: '08_qe_report.md', fleet: '09_fleet_qe_assessment.md' }
128
+ function fnv1a(str) { let h = 0x811c9dc5; for (let i = 0; i < str.length; i++) { h ^= str.charCodeAt(i); h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0 } return h.toString(16).padStart(8, '0') }
129
+ // 64 bits from two FNV passes — a single 32-bit hash admits findable collisions (Codex QE #9).
130
+ function fnv1a64(str) { return fnv1a(str) + fnv1a('fa-ckpt-salt' + str) }
131
+ function ckptHash(stage, parts) { return fnv1a64(JSON.stringify([CKPT_SCHEMA_VERSION, stage].concat(parts.map(function (p) { return p === undefined ? null : p })))) }
132
+ function decideCheckpointResume(mode, entry, inputHash, artifactRel, listing) {
133
+ if (mode === 'never') return { resume: false, reason: 'mode-never' }
134
+ if (!entry || entry.result === null || entry.result === undefined) return { resume: false, reason: 'no-checkpoint' }
135
+ if (entry.inputHash !== inputHash) return { resume: false, reason: 'stale-input' }
136
+ if (mode === 'force') return { resume: true, reason: 'resumed-force' }
137
+ const required = artifactRel === null ? [] : (typeof artifactRel === 'string' ? [artifactRel] : artifactRel)
138
+ for (const rel of required) { if (!listing.has(rel)) return { resume: false, reason: 'artifact-missing' } }
139
+ return { resume: true, reason: 'resumed' }
140
+ }
141
+ let CKPT_ENTRIES = {}
142
+ let CKPT_LISTING = new Set()
143
+ const resumedStages = []
144
+ async function loadCheckpoints(phaseName) {
145
+ if (!CHECKPOINTS_ON) return
146
+ const readCmd = 'cat ' + shq(CKPT_FILE) + ' 2>/dev/null || true; echo ' + shq(CKPT_LS_SENTINEL) + '; cd ' + shq(FDIR) + ' 2>/dev/null && find . -maxdepth 2 -type f 2>/dev/null | sed "s|^\\./||" || true'
147
+ const readOut = await agent('Run EXACTLY this via Bash and return its stdout VERBATIM (it may be empty) with NO code fences and NO commentary: ' + readCmd, { label: 'ckpt:read', phase: phaseName, effort: 'low' })
148
+ const raw = String(readOut == null ? '' : readOut)
149
+ // LINE-ANCHORED sentinel: a sentinel string INSIDE a recorded result shares its line with JSON
150
+ // syntax (stringify never emits raw newlines) and can never split the stream (Codex QE #10).
151
+ const allLines = raw.split('\n')
152
+ const sentinelAt = allLines.findIndex(function (l) { return l.trim() === CKPT_LS_SENTINEL })
153
+ const body = sentinelAt === -1 ? allLines : allLines.slice(0, sentinelAt)
154
+ const ls = sentinelAt === -1 ? [] : allLines.slice(sentinelAt + 1)
155
+ let malformed = 0
156
+ for (const line of body) {
157
+ const t = line.trim()
158
+ if (t === '') continue
159
+ try {
160
+ const e = JSON.parse(t)
161
+ if (e && typeof e === 'object' && typeof e.stage === 'string' && typeof e.inputHash === 'string' && ('result' in e) && e.result !== null && e.result !== undefined) CKPT_ENTRIES[e.stage] = e
162
+ else { if (e && typeof e === 'object' && typeof e.stage === 'string') delete CKPT_ENTRIES[e.stage]; malformed++ }
163
+ } catch (err) { malformed++ }
164
+ }
165
+ for (const line of ls) { const t = line.trim(); if (t !== '') CKPT_LISTING.add(t) }
166
+ const found = Object.keys(CKPT_ENTRIES)
167
+ if (malformed > 0) log('checkpoints: ' + malformed + ' malformed/null line(s) skipped (named, never silent — an all-malformed file resumes nothing)')
168
+ if (found.length > 0) log('checkpoints: ' + found.length + ' recorded stage(s) [' + found.join(', ') + '] (resume=' + RESUME_MODE + '; resume verifies INPUTS + artifact presence, NOT current-tree equivalence — after manual edits use resume:"never")')
169
+ }
170
+ async function withCheckpoint(stage, phaseName, inputHash, runFn, ckptOpts) {
171
+ const o = ckptOpts || {}
172
+ // artifacts override (Codex QE #2): tier-dependent stages pass EVERY artifact the tier requires
173
+ const artifactRel = (o.artifacts !== undefined) ? o.artifacts : ((stage in STAGE_ARTIFACTS_CKPT) ? STAGE_ARTIFACTS_CKPT[stage] : null)
174
+ let entry = CKPT_ENTRIES[stage]
175
+ // composite-shape validation (Codex QE #8): an old/partial composite must read as no-checkpoint
176
+ if (entry && typeof o.validate === 'function' && !o.validate(entry.result)) {
177
+ log('checkpoint: ' + stage + ' recorded result has an unexpected shape — treating as no checkpoint')
178
+ entry = undefined
179
+ }
180
+ const d = CHECKPOINTS_ON ? decideCheckpointResume(RESUME_MODE, entry, inputHash, artifactRel, CKPT_LISTING) : { resume: false, reason: 'checkpoints-off' }
181
+ if (d.resume) {
182
+ resumedStages.push(stage)
183
+ log('checkpoint: ' + stage + ' RESUMED (' + d.reason + ') — stage skipped, recorded result restored (inputs+artifacts verified; current-tree equivalence is NOT — the named limitation)')
184
+ return entry.result
185
+ }
186
+ if (CHECKPOINTS_ON && CKPT_ENTRIES[stage] && (d.reason === 'stale-input' || d.reason === 'artifact-missing')) log('checkpoint: ' + stage + ' NOT resumed (' + d.reason + ') — running live')
187
+ const result = await runFn()
188
+ // never checkpoint a dead/partial stage: null/undefined, or a parallel() array holding any null
189
+ const partial = Array.isArray(result) && result.some(function (x) { return x === null || x === undefined })
190
+ const persistable = (typeof o.persist === 'function') ? (result !== null && result !== undefined && o.persist(result)) : true
191
+ if (CHECKPOINTS_ON && result !== null && result !== undefined && !partial && persistable) {
192
+ let line = null
193
+ try { line = JSON.stringify({ stage: stage, inputHash: inputHash, result: result }) } catch (err) { line = null }
194
+ if (line && line.length <= CKPT_MAX) {
195
+ await agent('Run EXACTLY this one shell command via your Bash tool and reply with only OK: mkdir -p ' + shq(FDIR + '/.fa-state') + " && printf '%s\\n' " + shq(line) + ' >> ' + shq(CKPT_FILE), { label: 'ckpt:write:' + stage, phase: phaseName, effort: 'low' })
196
+ } else if (line) {
197
+ log('checkpoint: ' + stage + ' result oversize (' + line.length + ' > ' + CKPT_MAX + ' chars) — not checkpointed; the stage will re-run on resume (honest cost, never truncated state)')
198
+ } else {
199
+ log('checkpoint: ' + stage + ' result not serializable — not checkpointed')
200
+ }
201
+ } else if (CHECKPOINTS_ON && result !== null && result !== undefined && !partial && !persistable) {
202
+ log('checkpoint: ' + stage + ' result NOT persisted (persist predicate refused — e.g. a not-landed codex code stage must never resume as landed)')
203
+ }
204
+ return result
205
+ }
206
+
103
207
  // ── Codex-routing knobs (hoisted so the routing block below can fold them) ──
104
208
  // CODER/QE_REVIEWER ∈ 'claude'|'codex'|'codex-fallback'. On 'codex-fallback' the Claude agent runs
105
209
  // FIRST; if it returns null (e.g. the Claude Code session limit is exhausted mid-code/mid-QE), the SAME
@@ -575,12 +679,18 @@ const AMENDMENT_GATE = 'AMENDMENT GATE (P2): read the `## Amendments` sections o
575
679
 
576
680
  // Step 0: Router + MANDATORY self-learning recall
577
681
  phase('Router')
682
+ await loadCheckpoints('Router')
578
683
  await usageProbe('Router')
579
684
  const routerPrompt = 'You are Step 0 (Complexity Router) of the /feature-adr pipeline. TWO jobs. (1) MANDATORY SELF-LEARNING RECALL (never skip — run BOTH Bash commands VERBATIM, do not summarize instead of running them): the learned patterns live in the CANONICAL BRAIN store at `' + BRAIN + '` — pin every recall to it. Via your Bash tool run EXACTLY `' + DZ_RECALL('<the key domain terms of this feature>') + '` (and `' + DZ_RECALL('<the key domain terms of this feature>') + ' --all` if narrow) to load relevant LEARNED PATTERNS from the brain. Preserve recalled pattern TEXT, reward, domain, and any visible id in the rationale as a concrete list so Step 8 can compare candidate lessons against it. Then run `dz statusline --fa-record --slug ' + SLUG + ' --step "Step 0 recall" --recalled <count> --mode ' + MODE + ' --project ' + REPO + '`. Summarize the top 3 applicable patterns in the rationale. (2) Classify S/M/L/XL + active steps. Feature: "' + DESC + '". Code: ' + CODE_HINT + '. S=1-3 files (0,1,6,7,8; if an ADR is explicitly forced, use Nygard as the lightweight fallback); M=4-10 (0,1,3,3.5,5,6,7,8; Nygard/ITD-light ADR); L=11-30 (all+9; MADR+Confirmation ADRs); XL=30+ (full+9; MADR+Confirmation ADRs). ADR template-weight rule: S/M -> Nygard/ITD-light; L/XL -> MADR + NHS Wales Confirmation, while every generated ADR still carries the invariant core. Return {tier, activeSteps, rationale} with the recalled patterns folded into rationale.'
580
685
  const routerModel = resolveStageModel('router')
581
686
  const routerOpts = mergeOpts({ label: stageLabel('router+recall', routerModel), phase: 'Router', schema: ROUTER, effort: 'low' }, routerModel)
582
687
  modelsUsed.router = modelLabel(routerOpts)
583
- const router = await agent(routerPrompt + codexEffortHint(routerOpts), routerOpts)
688
+ // router checkpoint: its result (tier + recalled-pattern rationale) seeds every downstream hash.
689
+ // The Step-0 RECALL is part of the stage — a resumed router restores the SAME recalled patterns the
690
+ // original run applied (fresh lessons taught since then enter on the next live run, not mid-resume).
691
+ const routerHash = ckptHash('router', [DESC, CODE_HINT, MODE, A.tier === undefined ? null : A.tier, BRAIN, MODELS.router === undefined ? null : MODELS.router, CODEX_MODEL, usageOverride])
692
+ const router = await withCheckpoint('router', 'Router', routerHash, async () => agent(routerPrompt + codexEffortHint(routerOpts), routerOpts))
693
+ if (resumedStages.indexOf('router') !== -1) modelsUsed.router = modelsUsed.router + ' (resumed)'
584
694
  let tier = A.tier || (router ? router.tier : 'M')
585
695
  const LEARNED = router ? router.rationale : 'none recalled'
586
696
  const isMplus = tier === 'M' || tier === 'L' || tier === 'XL'
@@ -620,7 +730,7 @@ if (autoCostStages.length > 0) {
620
730
  // GUARANTEED fa-panel write (the router, being low-effort + multi-job, tends to skip the fa-record
621
731
  // Bash call). A dedicated single-command agent reliably lights up the live /feature-adr panel at the
622
732
  // most visible moment. Uses the workspace bin (PATH-independent). Best-effort — never blocks.
623
- await agent('Run EXACTLY this one shell command via your Bash tool and report its stdout verbatim — do nothing else, do not summarize: ' + DZ + ' statusline --fa-record --slug ' + SLUG + ' --step "Step 0 recall" --recalled 3 --stored 0 --mode ' + MODE + ' --project ' + REPO, { label: 'fa-record:step0', phase: 'Router', effort: 'low' })
733
+ if (resumedStages.indexOf('router') === -1) await agent('Run EXACTLY this one shell command via your Bash tool and report its stdout verbatim — do nothing else, do not summarize: ' + DZ + ' statusline --fa-record --slug ' + SLUG + ' --step "Step 0 recall" --recalled 3 --stored 0 --mode ' + MODE + ' --project ' + REPO, { label: 'fa-record:step0', phase: 'Router', effort: 'low' })
624
734
 
625
735
  // R1 product-architecture-lens (ADR-001 Decision 3): forward-looking сверка of THIS feature vs the LIVE
626
736
  // product map + vision. NON-BLOCKING/soft by design — it LOGS {signal,confidence} so a real command
@@ -672,7 +782,17 @@ if (isMplus) {
672
782
  const archExtra = isLplus ? ' Also ' + FDIR + '/04_domain_model.md (DDD).' : ''
673
783
  designThunks.push(() => designStage((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.', archOpts, FDIR + '/05_architecture.md', 'architecture'))
674
784
  }
675
- const design = await parallel(designThunks)
785
+ // design checkpoint: ONE composite entry for the whole parallel design fan (requirements/ADR/QCSD/
786
+ // architecture). A partial fan (any null) is never checkpointed — resume must not restore half a
787
+ // design. The hash carries EVERY input that steers the fan (Codex QE #4): tier, models, the recalled
788
+ // patterns, the project-skills manifest state, the usage override, and CODEX_MODEL (a bare 'codex'
789
+ // model spec resolves through it). The artifact probe requires EVERY tier-active design artifact
790
+ // (Codex QE #2 — a one-file probe accepted a design missing its ADR/architecture).
791
+ const designHash = ckptHash('design', [tier, DESC, CODE_HINT, fnv1a64(String(LEARNED)), MODELS.requirements === undefined ? null : MODELS.requirements, MODELS.adr === undefined ? null : MODELS.adr, MODELS.ideation === undefined ? null : MODELS.ideation, MODELS.architecture === undefined ? null : MODELS.architecture, POLY.hasManifest, fnv1a64(String(POLY.report || '')), usageOverride, CODEX_MODEL])
792
+ const designArtifacts = ['01_requirements.md']
793
+ if (isMplus) designArtifacts.push('03_adr/001-' + SLUG + '.md', '03.5_ideation_report.md', '05_architecture.md')
794
+ if (isLplus) designArtifacts.push('02_research.md', '04_domain_model.md')
795
+ const design = await withCheckpoint('design', 'Design', designHash, async () => parallel(designThunks), { artifacts: designArtifacts })
676
796
 
677
797
  // Step 6: Plan — optionally routed to Codex's top model (opt-in via args.planner='codex').
678
798
  // The user opts in at pre-flight ('use the top Codex model for planning?'); we route the Plan step to
@@ -685,6 +805,11 @@ const planPrompt = 'Step 6 (SPARC-GOAP implementation plan) of /feature-adr for
685
805
  // DEFAULT_MODELS/coder-fold) or the DEFAULT_MODELS.plan ('sonnet') under routing; else {} (BC).
686
806
  const planModel = resolveStageModel('plan')
687
807
  const planIsCodex = (planModel.agentType === 'codex:codex-rescue') || (MODELS.plan === undefined && PLANNER === 'codex')
808
+ // plan checkpoint: keyed on the design fan's RESULT (a stale design invalidates the plan) + the
809
+ // planner spec. Covers the standard L/XL two-phase flow: the stop-after-plan re-invoke resumes
810
+ // router+design+plan instead of re-running them.
811
+ const planHash = ckptHash('plan', [tier, DESC, fnv1a64(JSON.stringify(design === undefined ? null : design)), PLANNER, MODELS.plan === undefined ? null : MODELS.plan, CODEX_MODEL, usageOverride])
812
+ let plan = await withCheckpoint('plan', 'Plan', planHash, async () => {
688
813
  let plan = null
689
814
  if (planIsCodex) {
690
815
  const planCodexLabelOpts = (planModel.agentType === 'codex:codex-rescue') ? planModel : specToOpts('codex:' + CODEX_MODEL + ':high')
@@ -709,6 +834,9 @@ if (plan === null) {
709
834
  const claudePlan = await agent(planPrompt, claudePlanOpts)
710
835
  plan = claudePlan ? { wrote: claudePlan.wrote, summary: claudePlan.summary, planner: planIsCodex ? 'claude-fallback' : 'claude' } : null
711
836
  }
837
+ return plan
838
+ })
839
+ if (resumedStages.indexOf('plan') !== -1) modelsUsed.plan = (plan && plan.planner ? String(plan.planner) : 'plan') + ' (resumed)'
712
840
 
713
841
  // ── R6 challenge panel: adversarial plan-gate at the checkpoint (ADVISE, never block) ──
714
842
  // The panel is NEVER the plan's own author (ADR §1 hard invariant): author=Claude → a cross-family Codex
@@ -794,7 +922,7 @@ if (stopHere) {
794
922
  // P4 (checkpoint-gate-line): a DERIVED gates map — each entry comes from machine state (artifact/verdict
795
923
  // presence), never from prose, so a skipped gate shows as 'not-run' instead of being silently forgotten.
796
924
  const planGates = { plan: (plan ? 'produced' : 'missing'), challengePanel: (challengeVerdict ? 'ran' : 'not-run'), code: 'not-run', qe: 'not-run' }
797
- return { tier: tier, phase: 'checkpoint-after-plan', artifactsDir: FDIR, planner: (plan ? plan.planner : null), plan: (plan ? plan.summary : null), modelsUsed: plannedModels, challengeVerdict: challengeVerdict, gates: planGates, usageEvents: usageEvents, usageThreshold: USAGE_THRESHOLD, polymorphism: POLY.hasManifest ? POLY.report : null, note: 'L/XL checkpoint - review the ADR + plan (+ the planned code/qe/fleet models) + the challenge panel verdict (advisory) + the gates line, then re-invoke with args.stopAfter="none" to implement + QE. Present the gates map as a `🚦 Gates:` line in the checkpoint banner.' }
925
+ return { tier: tier, phase: 'checkpoint-after-plan', artifactsDir: FDIR, planner: (plan ? plan.planner : null), plan: (plan ? plan.summary : null), modelsUsed: plannedModels, challengeVerdict: challengeVerdict, gates: planGates, resumedStages: resumedStages, checkpointing: CHECKPOINTS_ON ? RESUME_MODE : 'off', usageEvents: usageEvents, usageThreshold: USAGE_THRESHOLD, polymorphism: POLY.hasManifest ? POLY.report : null, note: 'L/XL checkpoint - review the ADR + plan (+ the planned code/qe/fleet models) + the challenge panel verdict (advisory) + the gates line, then re-invoke with args.stopAfter="none" to implement + QE (durable checkpoints make the re-invoke resume router+design+plan instead of re-running them). Present the gates map as a `🚦 Gates:` line in the checkpoint banner.' }
798
926
  }
799
927
 
800
928
  // Step 7: Code (optional Codex fallback on Claude-limit exhaustion)
@@ -809,6 +937,12 @@ const codeModel = resolveStageModel('code')
809
937
  const codeIsCodexFirst = (MODELS.code !== undefined) ? (codeModel.agentType === 'codex:codex-rescue') : (CODER === 'codex')
810
938
  const codeClaudeModel = codeIsCodexFirst ? {} : (codeModel.agentType ? {} : codeModel)
811
939
  const codeClaudeOpts = mergeOpts({ label: stageLabel('code', codeClaudeModel), phase: 'Code', schema: ARTIFACT, effort: 'high' }, codeClaudeModel)
940
+ // code checkpoint: COMPOSITE — the branchy claude/codex/fallback block sets three interdependent
941
+ // values (code result, coderUsed, codexCodeText); resume must restore all of them together or the
942
+ // barrier/QE/auto-cost logic downstream would see an inconsistent trio. codexCodeText is capped for
943
+ // the checkpoint (it only feeds the expected-targets parse, already consumed by the original run).
944
+ const codeHash = ckptHash('code', [tier, DESC, fnv1a64(JSON.stringify(plan === undefined ? null : plan)), CODER, MODELS.code === undefined ? null : MODELS.code, CODEX_MODEL, POLY.hasManifest, fnv1a64(String(POLY.report || '')), usageOverride])
945
+ const codeStage = await withCheckpoint('code', 'Code', codeHash, async () => {
812
946
  let code = null
813
947
  let coderUsed = 'claude'
814
948
  let codexCodeText = ''
@@ -825,11 +959,10 @@ if (code === null && (codeIsCodexFirst || CODER === 'codex-fallback')) {
825
959
  const codexCode = await safeCodexAgent(codePrompt + CODEX_HINT + codexEffortHint(codeCodexOpts) + codexExpectedTargetsHint, codeCodexOpts)
826
960
  if (codexCode) { codexCodeText = String(codexCode); code = { wrote: [FDIR + '/07_code_changes/change_manifest.md'], summary: codexCodeText.slice(0, 500) }; coderUsed = codeIsCodexFirst ? 'codex' : 'codex-fallback'; modelsUsed.code = modelLabel(codeCodexLabelOpts) }
827
961
  }
828
-
829
- // Step 7.5: Codex-landed barrier. Codex applies edits OUT-OF-BAND via its own runtime; without this,
830
- // Step-8 QE can read the tree before the async write flushes and false-grade "Step 7 never ran" on real
831
- // completed code. Poll a bounded 120s backing-off window, preferring declared expected code targets when
832
- // known. Claude-coded runs are synchronous → this barrier is skipped with zero target parsing/probe work.
962
+ if (code === null) return null
963
+ // Step 7.5 landing barrier runs INSIDE the checkpointed stage (Codex QE #3): the checkpoint may
964
+ // only exist AFTER the barrier confirmed the out-of-band Codex edits landed a crash between
965
+ // code-return and barrier must leave NO resumable code checkpoint.
833
966
  let landedNote = ''
834
967
  if (needsCodeLandedBarrier(coderUsed)) {
835
968
  const expectedCodeTargets = extractExpectedCodeTargets(A.expectedCodeTargets, codexCodeText)
@@ -838,6 +971,23 @@ if (needsCodeLandedBarrier(coderUsed)) {
838
971
  const probe = await agent('Confirm the Codex Step-7 edits have LANDED in the working tree BEFORE QE runs (Codex writes out-of-band). Expected-file mode must be satisfied by one of the declared expected paths; unrelated dirty files do not count in that mode. Run EXACTLY this via Bash and return its stdout verbatim, nothing else:\n' + barrierCmd, { label: 'code:confirm-landed', phase: 'Code' })
839
972
  landedNote = '\n\nCODEX-CODED (out-of-band): Step 7.5 landing barrier used mode=' + barrierPlan.mode + ', window=' + barrierPlan.pollWindowSeconds + 's. Review the signal below. If status=landed, read the listed files and do NOT report "Step 7 never ran". Only status=genuinely-not-landed with "' + barrierPlan.emptySignal + '" means the bounded barrier found no intended code after the full window.\nExpected code targets: ' + (barrierPlan.expectedPaths.length ? barrierPlan.expectedPaths.join(', ') : '(none declared; fallback accepts any non-pipeline code change)') + '\n' + String(probe || '(landed-probe failed)').slice(0, 1500)
840
973
  }
974
+ return { code: code, coderUsed: coderUsed, codexCodeText: String(codexCodeText).slice(0, 4000), modelUsed: modelsUsed.code, landedNote: landedNote }
975
+ }, { validate: function (r) { return !!(r && typeof r === 'object' && r.code && typeof r.code === 'object' && typeof r.coderUsed === 'string' && typeof r.landedNote === 'string') }, persist: function (r) { return !/genuinely-not-landed/.test(String(r.landedNote || '')) } })
976
+ let code = codeStage ? codeStage.code : null
977
+ let coderUsed = codeStage ? codeStage.coderUsed : 'claude'
978
+ let codexCodeText = codeStage ? codeStage.codexCodeText : ''
979
+ if (codeStage && codeStage.modelUsed) modelsUsed.code = codeStage.modelUsed + (resumedStages.indexOf('code') !== -1 ? ' (resumed)' : '')
980
+
981
+ // Step 7.5: Codex-landed barrier. Codex applies edits OUT-OF-BAND via its own runtime; without this,
982
+ // Step-8 QE can read the tree before the async write flushes and false-grade "Step 7 never ran" on real
983
+ // completed code. Poll a bounded 120s backing-off window, preferring declared expected code targets when
984
+ // known. Claude-coded runs are synchronous → this barrier is skipped with zero target parsing/probe work.
985
+ // landedNote comes FROM the checkpointed composite (the barrier ran inside the code stage — QE #3):
986
+ // on resume the ORIGINAL barrier signal is restored verbatim with a resumed marker prepended.
987
+ let landedNote = codeStage ? String(codeStage.landedNote || '') : ''
988
+ if (resumedStages.indexOf('code') !== -1 && landedNote !== '') {
989
+ landedNote = '\n\n[RESUMED from checkpoint — the landing barrier below ran in the ORIGINAL run; the change-manifest artifact was re-verified present by the resume probe]' + landedNote
990
+ }
841
991
 
842
992
  // Step 8: QE (brutal-honesty, agentic-qe) + MANDATORY teach
843
993
  phase('QE')
@@ -855,6 +1005,11 @@ if (MODELS.qe === undefined && QE_REVIEWER === 'codex' && coderIsCodex()) log('Q
855
1005
  const qeIsCodex = qeShouldUseCodex()
856
1006
  const qeClaudeModel = qeIsCodex ? {} : qeModel
857
1007
  const qeClaudeOpts = mergeOpts({ label: stageLabel('qe:brutal', qeClaudeModel), phase: 'QE', agentType: 'qe-code-reviewer', schema: QE }, qeClaudeModel)
1008
+ // qe checkpoint: COMPOSITE (verdict + reviewer identity) keyed on the CODE stage's result — a re-coded
1009
+ // feature always re-QEs. The teach/fa-record side effects belong to the stage: a resumed QE does not
1010
+ // re-teach (the original run already stored its lessons — replaying teach would double-store).
1011
+ const qeHash = ckptHash('qe', [fnv1a64(JSON.stringify(codeStage === undefined ? null : codeStage)), tier, DESC, QE_REVIEWER, MODELS.qe === undefined ? null : MODELS.qe, CODEX_MODEL, coderUsed, POLY.hasManifest, fnv1a64(String(POLY.report || '')), usageOverride])
1012
+ const qeStage = await withCheckpoint('qe', 'QE', qeHash, async () => {
858
1013
  let qe = null
859
1014
  let qeReviewerUsed = 'claude'
860
1015
  if (!qeIsCodex) {
@@ -890,6 +1045,12 @@ if (qe === null && qeIsCodex) {
890
1045
  qe = await agent(qePrompt, qeBeltOpts)
891
1046
  if (qe) { qeReviewerUsed = 'claude'; modelsUsed.qe = modelLabel(qeBeltOpts) }
892
1047
  }
1048
+ if (qe === null) return null
1049
+ return { qe: qe, qeReviewerUsed: qeReviewerUsed, modelUsed: modelsUsed.qe }
1050
+ }, { validate: function (r) { return !!(r && typeof r === 'object' && r.qe && typeof r.qe === 'object' && typeof r.qeReviewerUsed === 'string') } })
1051
+ let qe = qeStage ? qeStage.qe : null
1052
+ let qeReviewerUsed = qeStage ? qeStage.qeReviewerUsed : 'claude'
1053
+ if (qeStage && qeStage.modelUsed) modelsUsed.qe = qeStage.modelUsed + (resumedStages.indexOf('qe') !== -1 ? ' (resumed)' : '')
893
1054
 
894
1055
  // Step 8 claim-gate: fold the QE agent's reported claim-check counts into an additive result field.
895
1056
  const claimGate = step8ClaimGate(qe && qe.claimCheck ? qe.claimCheck : null)
@@ -900,7 +1061,15 @@ log(claimGate.note)
900
1061
  // the CODE stage's authoritative gate (ii) is the Step-8 QE grade (A/B = pass) — a produced-but-gate-FAILED
901
1062
  // run is recorded as a FAILURE, down-ranking that model for the next run (ADR §2). Byte-identical no-op when
902
1063
  // nothing was auto-cost. Gate-less stages get weak provisional credit; richer per-stage gates are Phase-2.
903
- if (Object.keys(AUTOCOST).length > 0) {
1064
+ // checkpoint guard, AT-MOST-ONCE by choice: a resumed QE skips recording. NAMED window (Codex QE #6):
1065
+ // if the original run died AFTER the qe checkpoint landed but BEFORE recording finished, that run's
1066
+ // routing sample is LOST — accepted, because the alternative (re-recording on every resume) would
1067
+ // double-count outcomes and silently skew the auto-cost ranking. A lost sample under-informs; a
1068
+ // double-counted one misinforms.
1069
+ if (Object.keys(AUTOCOST).length > 0 && resumedStages.indexOf('qe') !== -1) {
1070
+ log('auto-cost: qe stage was RESUMED — outcome recording skipped (already recorded by the original run)')
1071
+ }
1072
+ if (Object.keys(AUTOCOST).length > 0 && resumedStages.indexOf('qe') === -1) {
904
1073
  const recPhase = isLplus ? 'FleetQE' : 'QE'
905
1074
  // Record ONLY stages that ACTUALLY RAN and produced an artifact (QE finding: a blanket loop over configured
906
1075
  // auto-cost stages credited skipped/failed/fallback stages). `code`/`plan` are guarded by their result var.
@@ -957,8 +1126,12 @@ if (isLplus) {
957
1126
  () => agent('Step 9 fleet-QE (requirements traceability + risk) for ' + SLUG + ': map ADR decisions to code to tests; flag orphans + high risk. Write ' + FDIR + '/09_fleet_qe_assessment.md.' + codexEffortHint(fleetTraceOpts), fleetTraceOpts),
958
1127
  () => agent('Step 9 fleet-QE (coverage + regression) for ' + SLUG + ': risk-weighted coverage gaps + regression selection for the changed files. Append to ' + FDIR + '/09_fleet_qe_assessment.md.' + codexEffortHint(fleetCovOpts), fleetCovOpts),
959
1128
  ]
960
- await parallel(fleetThunks)
961
- fleet = 'run'
1129
+ const fleetHash = ckptHash('fleet', [fnv1a64(JSON.stringify(qeStage === undefined ? null : qeStage)), tier, MODELS.fleet === undefined ? null : MODELS.fleet, CODEX_MODEL, usageOverride])
1130
+ fleet = await withCheckpoint('fleet', 'FleetQE', fleetHash, async () => {
1131
+ const fleetRuns = await parallel(fleetThunks)
1132
+ return fleetRuns.every(function (x) { return x !== null && x !== undefined }) ? 'run' : null
1133
+ })
1134
+ if (fleet === null) fleet = 'failed (a fleet agent died — not checkpointed)'
962
1135
  }
963
1136
 
964
1137
  // ── Step 10 (OPT-IN): Delivery Gate — post-implementation full review of the LANDED feature ──
@@ -1100,6 +1273,8 @@ return {
1100
1273
  usageEvents: usageEvents,
1101
1274
  usageThreshold: USAGE_THRESHOLD,
1102
1275
  selfLearning: 'recall@Step0 + teach@Step8 (mandatory)',
1276
+ resumedStages: resumedStages,
1277
+ checkpointing: CHECKPOINTS_ON ? RESUME_MODE : 'off',
1103
1278
  brain: BRAIN,
1104
1279
  polymorphism: POLY.hasManifest ? POLY.report : null,
1105
1280
  claimGate: claimGate,