@dzhechkov/skills-feature-adr 1.3.38 → 1.3.40

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dzhechkov/skills-feature-adr",
3
- "version": "1.3.38",
3
+ "version": "1.3.40",
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"
@@ -414,6 +414,30 @@ Codex on limit-exhaustion) stays a knob-only behavior; a direct `models.code='co
414
414
  'L', models: { code: 'opus', qe: 'codex:gpt-5.6:high', architecture: 'opus', router: 'fable' } } })` —
415
415
  Claude writes the code, Codex independently QEs it.
416
416
 
417
+ **Usage-adaptive routing (pre-emptive Codex switch under limit pressure).** When routing is opted into,
418
+ the workflow probes Claude SESSION (active 5h-block) and WEEKLY (rolling 7d) usage at EACH phase boundary
419
+ via a minimal `dz usage --json` agent. When either metric is `>= usageThreshold` (default `70`) BEFORE a
420
+ phase launches — OR the probe output is missing (agent-null, which often MEANS the limit was hit) — ALL
421
+ remaining stages switch to `codex:<top>` (design/code/plan at `xhigh`, router/qe/fleet at `high`). When a
422
+ later probe reads BOTH metrics below the threshold (positive numbers, not nulls), the normal Claude+Codex
423
+ mix is RESTORED. Null percentages (unconfigured limits) change NOTHING in either direction. Switched
424
+ stages are tagged ` (usage-switched)` in `modelsUsed`, and every flip is recorded in the result's
425
+ `usageEvents` array (the audit trail — never trust promiseTags for this). Args:
426
+
427
+ | `args.*` | Default | Effect |
428
+ |---|---|---|
429
+ | `usageAdaptive` | `true` when routing is requested (any `args.models` key or Codex knob); `false` otherwise | `true` forces it on even without other routing; `false` disables all probes (byte-identical to today) |
430
+ | `usageThreshold` | `70` | the `>=` percent (either metric) that triggers the pre-emptive switch |
431
+ | `usageReasoning` | the `OVERRIDE_REASONING` map | per-stage reasoning under the override (merge over the default: design/code/plan → `xhigh`, router/qe/fleet → `high`) |
432
+
433
+ Configure the limits the probe measures against in `.dz/config.json` — `memory.usage.sessionTokenLimit`
434
+ and `memory.usage.weeklyTokenLimit` (OPTIONAL; absent ⇒ `pct` is `null` and no switch fires). The
435
+ percentages are **ESTIMATES** from local transcript aggregation (there is no official usage API);
436
+ **calibrate** by scaling a limit by `X/100` when a real limit-hit lands at an estimated `X%`.
437
+ **Honest caveat (the wrapper lesson):** at TRUE exhaustion even the Codex dispatch dies because
438
+ `codex:codex-rescue` is a Claude wrapper subagent — so the switch MUST happen BEFORE, which is why the
439
+ 70% pre-emptive probe (not just reactive null-detection) is the real defense.
440
+
417
441
  ### Pattern memory loop (self-learning — runs in ALL modes)
418
442
 
419
443
  **Self-learning is MANDATORY on EVERY `/feature-adr` run — including plain `/feature-adr` without any
@@ -67,9 +67,76 @@ const MODELS = (A.models && typeof A.models === 'object') ? A.models : {}
67
67
  const KNOWN_CODEX = { 'auto': 1, 'gpt-5.5': 1, 'gpt-5.6': 1 }
68
68
  const CLAUDE_NAMES = { fable: 1, opus: 1, sonnet: 1, haiku: 1 }
69
69
  const DEFAULT_MODELS = { router: 'fable', requirements: 'sonnet', research: 'sonnet', adr: 'opus', ideation: 'sonnet', ddd: 'opus', architecture: 'opus', plan: 'sonnet', code: null, qe: null, fleet: 'sonnet' }
70
- const routingRequested = (Object.keys(MODELS).length > 0) || (PLANNER === 'codex') || (CODER === 'codex' || CODER === 'codex-fallback') || (QE_REVIEWER === 'codex' || QE_REVIEWER === 'codex-fallback')
70
+ const routingRequested = (Object.keys(MODELS).length > 0) || (PLANNER === 'codex') || (CODER === 'codex' || CODER === 'codex-fallback') || (QE_REVIEWER === 'codex' || QE_REVIEWER === 'codex-fallback') || (A.usageAdaptive === true)
71
71
  const modelsUsed = {}
72
72
 
73
+ // ── USAGE-ADAPTIVE ROUTING (pre-emptive codex switch at >= usageThreshold, default 70%) ──
74
+ // At every phase boundary a minimal haiku probe runs 'dz usage --json'; when SESSION or WEEKLY
75
+ // usage crosses the threshold BEFORE a phase launches, ALL remaining stages switch to
76
+ // codex:<topCodexId> (design/code/plan at xhigh, router/qe/fleet at high). When a later probe reads
77
+ // BOTH metrics below threshold (positive numbers, not nulls) the normal mix is RESTORED. The
78
+ // load-bearing asymmetry: an agent-null probe (dispatch died — often MEANS limits) fail-safe-switches
79
+ // TO codex; a value-null (unconfigured limits) flips NOTHING. All additive behind USAGE_ADAPTIVE:
80
+ // usageAdaptive:false OR no routing requested ⇒ zero probes, byte-identical to today.
81
+ // The single mutable routing bit usageOverride lives HERE (the workflow is its own environment);
82
+ // the pure library mirror (feature-adr-routing.ts) threads it via RoutingEnv — never a global.
83
+ const USAGE_THRESHOLD = Number(A.usageThreshold) > 0 ? Number(A.usageThreshold) : 70
84
+ const USAGE_ADAPTIVE = (A.usageAdaptive !== false) && (routingRequested || A.usageAdaptive === true)
85
+ const OVERRIDE_REASONING = mergeOpts({ router: 'high', requirements: 'xhigh', research: 'xhigh', adr: 'xhigh', ideation: 'xhigh', ddd: 'xhigh', architecture: 'xhigh', plan: 'xhigh', code: 'xhigh', qe: 'high', fleet: 'high' }, (A.usageReasoning && typeof A.usageReasoning === 'object') ? A.usageReasoning : {})
86
+ const usageReasoning = OVERRIDE_REASONING
87
+ let usageOverride = false
88
+ const usageEvents = []
89
+
90
+ // decideUsageAction: the PURE hysteresis core (byte-equivalent to feature-adr-routing.ts). Given the
91
+ // previous override bit, a probe signal (or null when the probe agent DIED), and the threshold,
92
+ // decide the new override bit + the LOCKED 6-value action. Total function.
93
+ function decideUsageAction(prevOverride, signal, threshold) {
94
+ if (signal === null || signal === undefined) {
95
+ if (prevOverride) return { override: true, action: 'keep' }
96
+ return { override: true, action: 'fail-safe-switch' }
97
+ }
98
+ const s = signal.sessionPct
99
+ const w = signal.weeklyPct
100
+ const sKnown = typeof s === 'number' && isFinite(s) && s >= 0
101
+ const wKnown = typeof w === 'number' && isFinite(w) && w >= 0
102
+ if ((sKnown && s >= threshold) || (wKnown && w >= threshold)) {
103
+ return { override: true, action: prevOverride ? 'keep' : 'switch' }
104
+ }
105
+ if (sKnown && wKnown) {
106
+ return { override: false, action: prevOverride ? 'restore' : 'none' }
107
+ }
108
+ return { override: prevOverride, action: prevOverride ? 'keep' : 'none' }
109
+ }
110
+
111
+ const PROBE_SCHEMA = { type: 'object', additionalProperties: false, required: ['sessionPct', 'weeklyPct'], properties: { sessionPct: { type: ['number', 'null'] }, weeklyPct: { type: ['number', 'null'] } } }
112
+
113
+ // usageProbe: at each phase boundary, dispatch a minimal haiku/effort-low agent that runs EXACTLY
114
+ // one shell command ('dz usage --json') — the same guaranteed-single-command shape as fa-record:step0.
115
+ // Feed the reading to decideUsageAction, flip usageOverride, record a usageEvents entry. First
116
+ // statement is the BC guard: when USAGE_ADAPTIVE is off, ZERO probe agents are dispatched (AC-4).
117
+ async function usageProbe(phaseName) {
118
+ if (!USAGE_ADAPTIVE) return
119
+ const probePrompt = 'Run EXACTLY this one shell command via your Bash tool and return ONLY its parsed JSON fields sessionPct and weeklyPct (numbers or null), nothing else, do not summarize: ' + DZ + ' usage --json --project ' + REPO
120
+ const r = await agent(probePrompt, { label: 'usage:probe', phase: phaseName, model: 'haiku', effort: 'low', schema: PROBE_SCHEMA })
121
+ const d = decideUsageAction(usageOverride, r, USAGE_THRESHOLD)
122
+ if (d.action === 'switch') log('usage: session ' + (r ? r.sessionPct : null) + '% / week ' + (r ? r.weeklyPct : null) + '% >= ' + USAGE_THRESHOLD + '% — switching remaining stages to codex:' + topCodexId())
123
+ if (d.action === 'fail-safe-switch') log('usage: probe died (agent-null — often MEANS limits) — fail-safe switching remaining stages to codex:' + topCodexId())
124
+ if (d.action === 'restore') log('usage: back to ' + (r ? r.sessionPct : null) + '%/' + (r ? r.weeklyPct : null) + '% — restoring normal routing')
125
+ usageOverride = d.override
126
+ usageEvents.push({ phase: phaseName, sessionPct: r ? r.sessionPct : null, weeklyPct: r ? r.weeklyPct : null, action: d.action })
127
+ }
128
+
129
+ // reactiveBelt: generalizes today's codex-fallback. When a stage agent returns null while NOT already
130
+ // overridden (a possible limit event), flip the override so the REMAINING stages don't walk into the
131
+ // same wall. The legacy codex-fallback same-stage retry is untouched. Best-effort: at TRUE exhaustion
132
+ // even codex dispatch dies (codex:codex-rescue is a Claude wrapper) — the 70% pre-emptive probe is the real defense.
133
+ function reactiveBelt(phaseName) {
134
+ if (!USAGE_ADAPTIVE || usageOverride) return
135
+ usageOverride = true
136
+ usageEvents.push({ phase: phaseName, sessionPct: null, weeklyPct: null, action: 'reactive-switch' })
137
+ log('usage: stage agent returned null while not overridden — possible limit event, switching remaining stages to codex:' + topCodexId())
138
+ }
139
+
73
140
  function specToOpts(spec) {
74
141
  if (!spec) return {}
75
142
  const parts = String(spec).split(':')
@@ -96,16 +163,20 @@ function coderIsCodex() {
96
163
  return false
97
164
  }
98
165
 
99
- function resolveQeSpec() {
100
- if (coderIsCodex()) return 'opus'
101
- const CODEX_AVAILABLE = A.codexAvailable !== false
102
- if (!CODEX_AVAILABLE) return 'opus'
166
+ function topCodexId() {
103
167
  let top = CODEX_MODEL
104
168
  if (top === 'auto') {
105
169
  const ids = Object.keys(KNOWN_CODEX)
106
170
  for (let i = 0; i < ids.length; i++) { if (ids[i] !== 'auto') top = ids[i] || top }
107
171
  }
108
- return 'codex:' + top + ':high'
172
+ return top
173
+ }
174
+
175
+ function resolveQeSpec() {
176
+ if (coderIsCodex()) return 'opus'
177
+ const CODEX_AVAILABLE = A.codexAvailable !== false
178
+ if (!CODEX_AVAILABLE) return 'opus'
179
+ return 'codex:' + topCodexId() + ':high'
109
180
  }
110
181
 
111
182
  // qeShouldUseCodex: the load-bearing cross-model gate — the model that wrote the code must NEVER self-QE.
@@ -121,6 +192,12 @@ function qeShouldUseCodex() {
121
192
  }
122
193
 
123
194
  function resolveStageModel(stage) {
195
+ if (usageOverride) {
196
+ const r = (usageReasoning && usageReasoning[stage]) || OVERRIDE_REASONING[stage] || 'high'
197
+ const o = specToOpts('codex:' + topCodexId() + ':' + r)
198
+ o._usageSwitched = true
199
+ return o
200
+ }
124
201
  let spec = MODELS[stage]
125
202
  if (spec === undefined) {
126
203
  if (!routingRequested) return {}
@@ -140,7 +217,11 @@ function mergeOpts(base, extra) {
140
217
 
141
218
  // modelLabel: record the resolved spec for a stage in modelsUsed (for the run report / who-did-what).
142
219
  function modelLabel(opts) {
143
- if (opts && opts.agentType === 'codex:codex-rescue') return 'codex:' + opts.codexModel + ':' + opts._reasoning
220
+ if (opts && opts.agentType === 'codex:codex-rescue') {
221
+ const base = 'codex:' + opts.codexModel + ':' + opts._reasoning
222
+ if (opts._usageSwitched) return base + ' (usage-switched)'
223
+ return base
224
+ }
144
225
  if (opts && opts.model) return opts.model
145
226
  return 'session'
146
227
  }
@@ -151,7 +232,8 @@ const QE = { type: 'object', additionalProperties: false, required: ['grade', 'g
151
232
 
152
233
  // Step 0: Router + MANDATORY self-learning recall
153
234
  phase('Router')
154
- 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, 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); M=4-10 (0,1,3,3.5,5,6,7,8); L=11-30 (all+9); XL=30+ (full+9). Return {tier, activeSteps, rationale} with the recalled patterns folded into rationale.'
235
+ await usageProbe('Router')
236
+ 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); M=4-10 (0,1,3,3.5,5,6,7,8); L=11-30 (all+9); XL=30+ (full+9). Return {tier, activeSteps, rationale} with the recalled patterns folded into rationale.'
155
237
  const routerOpts = mergeOpts({ label: 'router+recall', phase: 'Router', schema: ROUTER, effort: 'low' }, resolveStageModel('router'))
156
238
  modelsUsed.router = modelLabel(routerOpts)
157
239
  const router = await agent(routerPrompt, routerOpts)
@@ -168,6 +250,7 @@ await agent('Run EXACTLY this one shell command via your Bash tool and report it
168
250
 
169
251
  // Steps 1-5: Design (tier-gated thunks built explicitly - no inline ternary-null)
170
252
  phase('Design')
253
+ await usageProbe('Design')
171
254
  const designThunks = []
172
255
  const reqExtra = isLplus ? ' Also write ' + FDIR + '/02_research.md (codebase patterns + external analogues; read the repo for the closest existing implementation to mirror).' : ''
173
256
  // Resolve per-stage model opts up-front (parser-safe: no inline resolveStageModel inside the thunk arrays).
@@ -196,6 +279,7 @@ const design = await parallel(designThunks)
196
279
  // the codex:codex-rescue runtime and GRACEFULLY FALL BACK to the default (Claude) planner if Codex is
197
280
  // unavailable/errors — the pipeline never blocks on Codex.
198
281
  phase('Plan')
282
+ await usageProbe('Plan')
199
283
  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. Return wrote[] + summary.'
200
284
  // Resolve the plan model. args.models.plan wins; else the planner:'codex' knob (via routingRequested +
201
285
  // DEFAULT_MODELS/coder-fold) or the DEFAULT_MODELS.plan ('sonnet') under routing; else {} (BC).
@@ -213,6 +297,7 @@ if (planIsCodex) {
213
297
  log('Plan: Codex unavailable — falling back to the default planner')
214
298
  }
215
299
  }
300
+ if (plan === null && planIsCodex) reactiveBelt('Plan')
216
301
  if (plan === null) {
217
302
  const claudePlanOpts = mergeOpts({ label: 'plan', phase: 'Plan', schema: ARTIFACT }, planIsCodex ? {} : planModel)
218
303
  modelsUsed.plan = planIsCodex ? 'claude-fallback' : modelLabel(claudePlanOpts)
@@ -230,11 +315,12 @@ if (stopHere) {
230
315
  const qePlanned = qeShouldUseCodex() ? modelLabel(resolveStageModel('qe')) : modelLabel(mergeOpts({ agentType: 'qe-code-reviewer' }, resolveStageModel('qe')))
231
316
  const plannedModels = mergeOpts(modelsUsed, { code: codePlanned + ' (planned)', qe: qePlanned + ' (planned)' })
232
317
  if (isLplus) plannedModels.fleet = modelLabel(resolveStageModel('fleet')) + ' (planned)'
233
- return { tier: tier, phase: 'checkpoint-after-plan', artifactsDir: FDIR, planner: (plan ? plan.planner : null), plan: (plan ? plan.summary : null), modelsUsed: plannedModels, note: 'L/XL checkpoint - review the ADR + plan (+ the planned code/qe/fleet models), then re-invoke with args.stopAfter="none" to implement + QE.' }
318
+ return { tier: tier, phase: 'checkpoint-after-plan', artifactsDir: FDIR, planner: (plan ? plan.planner : null), plan: (plan ? plan.summary : null), modelsUsed: plannedModels, usageEvents: usageEvents, usageThreshold: USAGE_THRESHOLD, note: 'L/XL checkpoint - review the ADR + plan (+ the planned code/qe/fleet models), then re-invoke with args.stopAfter="none" to implement + QE.' }
234
319
  }
235
320
 
236
321
  // Step 7: Code (optional Codex fallback on Claude-limit exhaustion)
237
322
  phase('Code')
323
+ await usageProbe('Code')
238
324
  const codePrompt = 'Step 7 (Code) of /feature-adr for "' + DESC + '" (' + SLUG + '). Implement the feature per the plan + ADR + architecture in ' + FDIR + '. Write the ACTUAL production code + its tests (mirror the closest existing implementation named in research/architecture). Follow repo conventions; build must pass. Write a change manifest ' + FDIR + '/07_code_changes/change_manifest.md listing every file touched. Return wrote[] (incl. real source files) + summary.'
239
325
  // Resolve the coder model. args.models.code wins (a direct 'codex' spec = codex-first); else the legacy
240
326
  // CODER knob drives it (with its codex-fallback null-guard). resolveStageModel('code') folds both via the
@@ -249,6 +335,7 @@ if (!codeIsCodexFirst) {
249
335
  code = await agent(codePrompt, codeClaudeOpts)
250
336
  if (code) { coderUsed = 'claude'; modelsUsed.code = modelLabel(codeClaudeOpts) }
251
337
  }
338
+ if (code === null && !codeIsCodexFirst) reactiveBelt('Code')
252
339
  if (code === null && (codeIsCodexFirst || CODER === 'codex-fallback')) {
253
340
  if (CODER === 'codex-fallback' && !codeIsCodexFirst) log('Code: Claude unavailable (limit?) — falling back to Codex ' + CODEX_MODEL)
254
341
  const codeCodexOpts = mergeOpts({ label: 'code:codex', phase: 'Code', agentType: 'codex:codex-rescue' }, codeModel.agentType ? codeModel : {})
@@ -270,7 +357,8 @@ if (coderUsed === 'codex' || coderUsed === 'codex-fallback') {
270
357
 
271
358
  // Step 8: QE (brutal-honesty, agentic-qe) + MANDATORY teach
272
359
  phase('QE')
273
- const qePrompt = 'Step 8 (QE - brutal-honesty review, agentic-qe) of /feature-adr for "' + DESC + '" (' + SLUG + '). Adversarially review the SHIPPED code (read it): correctness, edge cases, error handling, and the LOAD-BEARING property the ADR named (ASSERT it has a test - the recurring lesson). Grade A/B/C/D honestly. Assess code-test adequacy + doc-test presence. List CONFIRMED gaps with severity. Write ' + FDIR + '/08_qe_report.md. MANDATORY SELF-LEARNING STORE (close the loop, never skip): store every lesson in the CANONICAL BRAIN store at `' + BRAIN + '` so it is NOT lost to a target repo you may have cd`d into. Via Bash run EXACTLY `' + DZ_TEACH('<a durable reusable lesson from this feature - a rule/pattern/pitfall, NOT a checkpoint echo>', '<0.7-0.95>', '<area>') + '` for each genuine lesson (1-3 max, high-signal) — the `cd ' + BRAIN + ' &&` prefix + `--project ' + BRAIN + '` pin guarantee the lesson lands in the brain regardless of your CWD. Then run `' + DZ + ' statusline --fa-record --slug ' + SLUG + ' --step "Step 8 QE" --recalled 3 --stored <count taught> --mode ' + MODE + ' --project ' + REPO + '` (run it verbatim via Bash, do not skip). Do NOT teach trivia or invent gaps. Return {grade, gaps, codeTestsAdequate, docTestsPresent}.' + landedNote
360
+ await usageProbe('QE')
361
+ const qePrompt = 'Step 8 (QE - brutal-honesty review, agentic-qe) of /feature-adr for "' + DESC + '" (' + SLUG + '). Adversarially review the SHIPPED code (read it): correctness, edge cases, error handling, and the LOAD-BEARING property the ADR named (ASSERT it has a test - the recurring lesson). Grade A/B/C/D honestly. Assess code-test adequacy + doc-test presence. List CONFIRMED gaps with severity. Write ' + FDIR + '/08_qe_report.md. MANDATORY SELF-LEARNING STORE (close the loop, never skip): compare every candidate lesson against the Step-0 recalled LEARNED patterns above. Teach ONLY lessons NOT covered by Step-0 recall. On overlap, run `dz teach --reinforce "<recalled pattern id or exact text>" --project ' + BRAIN + '` instead of minting a near-duplicate; if --reinforce is unavailable, skip the duplicate teach and report `reinforced existing pattern <id>` in the QE report. Store every genuinely new lesson in the CANONICAL BRAIN store at `' + BRAIN + '` so it is NOT lost to a target repo you may have cd`d into. Via Bash run EXACTLY `' + DZ_TEACH('<a durable reusable lesson from this feature - a rule/pattern/pitfall, NOT a checkpoint echo>', '<0.7-0.95>', '<area>') + '` for each genuine NEW lesson (1-3 max, high-signal) — the `cd ' + BRAIN + ' &&` prefix + `--project ' + BRAIN + '` pin guarantee the lesson lands in the brain regardless of your CWD. Then run `' + DZ + ' statusline --fa-record --slug ' + SLUG + ' --step "Step 8 QE" --recalled 3 --stored <count taught> --reinforced <count reinforced> --mode ' + MODE + ' --project ' + REPO + '` (run it verbatim via Bash, do not skip). Do NOT teach trivia or invent gaps. Return {grade, gaps, codeTestsAdequate, docTestsPresent}.' + landedNote
274
362
  // CROSS-MODEL QE (load-bearing): resolveStageModel('qe') derives the OTHER family than the resolved
275
363
  // coder when args.models.qe is unset (coder-codex ⇒ opus; coder-Claude ⇒ codex, or opus if codex absent).
276
364
  // An explicit args.models.qe wins. A Claude qe spec is merged onto the qe-code-reviewer base (role
@@ -288,6 +376,7 @@ if (!qeIsCodex) {
288
376
  qe = await agent(qePrompt, qeClaudeOpts)
289
377
  if (qe) { qeReviewerUsed = 'claude'; modelsUsed.qe = modelLabel(qeClaudeOpts) }
290
378
  }
379
+ if (qe === null && !qeIsCodex) reactiveBelt('QE')
291
380
  if (qe === null && (qeIsCodex || QE_REVIEWER === 'codex-fallback')) {
292
381
  if (QE_REVIEWER === 'codex-fallback' && !qeIsCodex) log('QE: Claude unavailable (limit?) — falling back to Codex ' + CODEX_MODEL)
293
382
  const qeCodexOpts = mergeOpts({ label: 'qe:codex', phase: 'QE', agentType: 'codex:codex-rescue' }, qeModel.agentType ? qeModel : {})
@@ -306,6 +395,7 @@ if (qe === null && qeIsCodex) {
306
395
  let fleet = 'skipped (S/M)'
307
396
  if (isLplus) {
308
397
  phase('FleetQE')
398
+ await usageProbe('FleetQE')
309
399
  const fleetModel = resolveStageModel('fleet')
310
400
  modelsUsed.fleet = modelLabel(mergeOpts({}, fleetModel))
311
401
  const fleetTraceOpts = mergeOpts({ label: 'fleet:trace', phase: 'FleetQE', agentType: 'qe-requirements-validator' }, fleetModel)
@@ -334,6 +424,8 @@ return {
334
424
  qeReviewerUsed: qeReviewerUsed,
335
425
  codexModel: CODEX_MODEL,
336
426
  modelsUsed: modelsUsed,
427
+ usageEvents: usageEvents,
428
+ usageThreshold: USAGE_THRESHOLD,
337
429
  selfLearning: 'recall@Step0 + teach@Step8 (mandatory)',
338
430
  brain: BRAIN,
339
431
  promiseTags: tags,