@dzhechkov/skills-feature-adr 1.3.36 → 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/README.md
CHANGED
|
@@ -139,7 +139,41 @@ Workflow({ scriptPath: '.claude/workflows/feature-adr.js',
|
|
|
139
139
|
```
|
|
140
140
|
|
|
141
141
|
Omit the Codex knobs entirely for today's all-Claude behavior. The run result reports
|
|
142
|
-
`plannerUsed` / `coderUsed` / `qeReviewerUsed` / `codexModel` so you can see who did what.
|
|
142
|
+
`plannerUsed` / `coderUsed` / `qeReviewerUsed` / `codexModel` / `modelsUsed` so you can see who did what.
|
|
143
|
+
|
|
144
|
+
### Per-stage model routing — `args.models`
|
|
145
|
+
|
|
146
|
+
The three Codex knobs above are shortcuts. `args.models` is the **general dial**: an optional map that
|
|
147
|
+
routes each of the 11 pipeline stages to an optimal model. Keys: `{router, requirements, research, adr,
|
|
148
|
+
ideation, ddd, architecture, plan, code, qe, fleet}`. Each value is a **spec** — Claude
|
|
149
|
+
`fable|opus|sonnet|haiku`, or Codex `codex` / `codex:<id>` / `codex:<id>:<reasoning>`
|
|
150
|
+
(`reasoning ∈ low|medium|high|xhigh`; ids incl. `gpt-5.5`, `gpt-5.6`).
|
|
151
|
+
|
|
152
|
+
**Recommended DEFAULT TABLE** (applied only when you opt in — one `args.models` key or any Codex knob
|
|
153
|
+
turns it on; otherwise every stage is session-inherited, byte-identical to today):
|
|
154
|
+
|
|
155
|
+
| router | requirements | adr | ideation | architecture | plan | code | qe | fleet |
|
|
156
|
+
|---|---|---|---|---|---|---|---|---|
|
|
157
|
+
| `fable` | `sonnet` | `opus` | `sonnet` | `opus` | `sonnet` | coder (default `opus`) | cross-model | `sonnet` |
|
|
158
|
+
|
|
159
|
+
(`research` folds into `requirements`, `ddd` into `architecture` — recorded in `modelsUsed`, not a
|
|
160
|
+
separate call.)
|
|
161
|
+
|
|
162
|
+
**Cross-model QE default (load-bearing):** when `args.models.qe` is unset, QE auto-routes to the **other
|
|
163
|
+
family than the coder** — a model that writes code must not also self-QE; independent cross-model review
|
|
164
|
+
catches what self-review misses. coder=Codex ⇒ QE=Claude (`opus`); coder=Claude ⇒ QE=Codex
|
|
165
|
+
(`codex:<top>:high`), or a Claude reviewer if Codex is unavailable (never blocks).
|
|
166
|
+
|
|
167
|
+
**Precedence:** `args.models[stage]` wins; the legacy `planner`/`coder`/`qeReviewer`/`codexModel` knobs
|
|
168
|
+
fill only unspecified stages. `codexModel` seeds the id for a bare `'codex'` spec. gpt-5.6-ready: a new
|
|
169
|
+
Codex id is a data-only allowlist edit.
|
|
170
|
+
|
|
171
|
+
```js
|
|
172
|
+
// Claude writes the code; Codex independently QEs it (cross-model by construction):
|
|
173
|
+
Workflow({ scriptPath: '.claude/workflows/feature-adr.js',
|
|
174
|
+
args: { slug: 'add-oauth', description: '…', tier: 'L',
|
|
175
|
+
models: { code: 'opus', qe: 'codex:gpt-5.6:high', architecture: 'opus', router: 'fable' } } })
|
|
176
|
+
```
|
|
143
177
|
|
|
144
178
|
---
|
|
145
179
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dzhechkov/skills-feature-adr",
|
|
3
|
-
"version": "1.3.
|
|
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"
|
|
@@ -375,6 +375,69 @@ Pre-flight, if Codex is `ready` (`codex-companion setup --json`), the orchestrat
|
|
|
375
375
|
before launching; plain `/feature-adr` offers the same at the planning checkpoint. Omit the Codex knobs
|
|
376
376
|
for today's all-Claude behavior. See `.claude/rules/feature-adr-ultracode.md`.
|
|
377
377
|
|
|
378
|
+
**Per-stage model routing — `args.models` (one dial, 11 stages).** `args.models` is an optional map that
|
|
379
|
+
routes each pipeline stage to an optimal model. Keys: `{router, requirements, research, adr, ideation,
|
|
380
|
+
ddd, architecture, plan, code, qe, fleet}`. Each value is a **spec**:
|
|
381
|
+
|
|
382
|
+
- **Claude** — `'fable' | 'opus' | 'sonnet' | 'haiku'` → adds `model` to that stage's `agent()` call
|
|
383
|
+
(any role `agentType` like `qe-code-reviewer` is PRESERVED).
|
|
384
|
+
- **Codex** — `'codex'` / `'codex:<id>'` / `'codex:<id>:<reasoning>'` (e.g. `'codex:gpt-5.6:xhigh'`;
|
|
385
|
+
`reasoning ∈ low|medium|high|xhigh`; ids incl. `gpt-5.5`, `gpt-5.6`) → routes the stage to the
|
|
386
|
+
`codex:codex-rescue` runtime with that `codexModel` + reasoning hint.
|
|
387
|
+
|
|
388
|
+
**Recommended DEFAULT TABLE** (applied only when you opt into routing — any one `args.models` key or any
|
|
389
|
+
Codex knob flips it on; otherwise every stage stays session-inherited, byte-identical to today):
|
|
390
|
+
|
|
391
|
+
| Stage | Default | | Stage | Default |
|
|
392
|
+
|---|---|---|---|---|
|
|
393
|
+
| router | `fable` | | architecture | `opus` |
|
|
394
|
+
| requirements | `sonnet` | | plan | `sonnet` |
|
|
395
|
+
| research | `sonnet` (folds into requirements) | | code | *the coder knob* (default Claude `opus`) |
|
|
396
|
+
| adr | `opus` | | qe | *CROSS-MODEL of the coder* (see below) |
|
|
397
|
+
| ideation | `sonnet` | | fleet | `sonnet` |
|
|
398
|
+
| ddd | `opus` (folds into architecture) | | | |
|
|
399
|
+
|
|
400
|
+
**Cross-model QE default (load-bearing).** When `args.models.qe` is UNSET, QE is auto-routed to the
|
|
401
|
+
**other model family than the coder** — a model that WRITES code must not also SELF-QE; independent
|
|
402
|
+
cross-model review catches what self-review misses. coder=Codex ⇒ QE=Claude (`opus`); coder=Claude ⇒
|
|
403
|
+
QE=Codex (`codex:<top>:high`), or a Claude reviewer if Codex is unavailable (**never blocks**).
|
|
404
|
+
|
|
405
|
+
**Precedence.** `args.models[stage]` is the general mechanism and WINS on conflict; the legacy
|
|
406
|
+
`planner`/`coder`/`qeReviewer`/`codexModel` knobs are shortcuts that fill a stage only when `args.models`
|
|
407
|
+
does not. `codexModel` sets the default id for a bare `'codex'` spec. Codex-fallback (Claude-first, then
|
|
408
|
+
Codex on limit-exhaustion) stays a knob-only behavior; a direct `models.code='codex'` means codex-first.
|
|
409
|
+
|
|
410
|
+
**gpt-5.6-ready** — a new Codex id is a DATA-ONLY addition to the `KNOWN_CODEX` allowlist (no control flow).
|
|
411
|
+
**Reporting** — the run result includes `modelsUsed` (the resolved per-stage model) so you see who did what.
|
|
412
|
+
|
|
413
|
+
*Example:* `Workflow({ scriptPath: '.claude/workflows/feature-adr.js', args: { slug, description, tier:
|
|
414
|
+
'L', models: { code: 'opus', qe: 'codex:gpt-5.6:high', architecture: 'opus', router: 'fable' } } })` —
|
|
415
|
+
Claude writes the code, Codex independently QEs it.
|
|
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
|
+
|
|
378
441
|
### Pattern memory loop (self-learning — runs in ALL modes)
|
|
379
442
|
|
|
380
443
|
**Self-learning is MANDATORY on EVERY `/feature-adr` run — including plain `/feature-adr` without any
|
|
@@ -408,6 +471,15 @@ non-blocking — an error or empty result never stalls the pipeline. This layer
|
|
|
408
471
|
**distinct from the Keysarium reward layer** installed by `--with-learning`: it uses agentic-qe MCP
|
|
409
472
|
memory + the dz store, is UNCONDITIONAL for the dz half (recall/teach/fa-record run in every mode) and Direct-mode-only for the aqe-MCP half, and never touches `.keysarium/memory/`.
|
|
410
473
|
|
|
474
|
+
**Canonical brain store — `args.brain` (never fragment the loop).** The dz durable loop only compounds if
|
|
475
|
+
Step-0 recall and Step-8 teach hit the SAME store. The workflow pins both to a canonical **brain** —
|
|
476
|
+
`args.brain`, default = the workspace root — via `cd <brain> && dz recall/teach … --project <brain>`, so a
|
|
477
|
+
Step-8 teach issued from a coder that `cd`'d into a target repo still lands in the brain, not that repo's
|
|
478
|
+
`.dz`. Omitting `args.brain` is behaviorally inert for a workspace-CWD run (`brain === repo`). **Share** a
|
|
479
|
+
brain: `dz recall --all --json > patterns.json` → `dz teach --from-json patterns.json --project <brain>`
|
|
480
|
+
(exact-text dedup, idempotent). **Recover** a fragmented store: `cd <stray-repo> && dz recall --all --json >
|
|
481
|
+
/tmp/stray.json` → `dz teach --from-json /tmp/stray.json --project <brain>` to merge it back into the brain.
|
|
482
|
+
|
|
411
483
|
**Live learning panel (`dz statusline`).** The pipeline **drives** the panel: at each pattern-memory-loop
|
|
412
484
|
step above it records its live state via `dz statusline --fa-record …`, so `dz statusline` can surface
|
|
413
485
|
per-run learning (which feature, which step, how many patterns recalled vs. newly stored). Each of the
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const meta = {
|
|
2
2
|
name: 'feature-adr',
|
|
3
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).',
|
|
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}}) instead of an ad-hoc orchestration, so every feature ships with an ADR + inline agentic-qe QE + self-learning.',
|
|
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' },
|
|
7
7
|
{ title: 'Design', detail: 'Steps 1-5 - requirements, ADR, QCSD, architecture (tier-gated)' },
|
|
@@ -25,6 +25,206 @@ const REPO = (A.repo || '.').replace(/\/+$/, '')
|
|
|
25
25
|
const FDIR = REPO + '/features/' + SLUG
|
|
26
26
|
// The dz CLI: bare `dz` (on PATH for installed users) unless the caller overrides with a bin path.
|
|
27
27
|
const DZ = A.dzBin || 'dz'
|
|
28
|
+
// CANONICAL BRAIN store: the self-learning loop (Step-0 recall → Step-8 teach) MUST read+write ONE
|
|
29
|
+
// shared pattern store so lessons never fragment into a target repo's .dz when the Step-7 coder cd's
|
|
30
|
+
// away. BRAIN defaults to the workspace root (REPO) — so an OMITTED args.brain is behaviorally inert
|
|
31
|
+
// for a workspace-CWD run (BRAIN===REPO, same store as today's bare recall/teach). Override args.brain
|
|
32
|
+
// with a stable absolute path to keep ONE brain across several target checkouts.
|
|
33
|
+
const BRAIN = (A.brain || REPO).replace(/\/+$/, '')
|
|
34
|
+
// Helpers PIN every learn-loop command to the canonical brain: `cd <BRAIN> &&` survives a cd'd agent
|
|
35
|
+
// (belt); `--project <BRAIN>` is explicit (suspenders). Either alone fixes it; together they also
|
|
36
|
+
// survive the relative-vs-absolute --project resolution asymmetry between recall and teach.
|
|
37
|
+
const DZ_RECALL = (terms) => 'cd ' + BRAIN + ' && ' + DZ + ' recall "' + terms + '" --project ' + BRAIN
|
|
38
|
+
const DZ_TEACH = (lesson, reward, domain) =>
|
|
39
|
+
'cd ' + BRAIN + ' && ' + DZ + ' teach "' + lesson + '" --reward ' + reward + ' --domain ' + domain + ' --project ' + BRAIN
|
|
40
|
+
|
|
41
|
+
// ── Codex-routing knobs (hoisted so the routing block below can fold them) ──
|
|
42
|
+
// CODER/QE_REVIEWER ∈ 'claude'|'codex'|'codex-fallback'. On 'codex-fallback' the Claude agent runs
|
|
43
|
+
// FIRST; if it returns null (e.g. the Claude Code session limit is exhausted mid-code/mid-QE), the SAME
|
|
44
|
+
// task is retried on the codex:codex-rescue runtime. args.codexModel is DEFAULT 'auto' (Codex self-selects
|
|
45
|
+
// the top model available to the account — ids are account/version-specific and move ahead of any static
|
|
46
|
+
// default, so 'auto' is the portable choice). To hard-pin a specific id the orchestrator writes it into
|
|
47
|
+
// ~/.codex/config.toml at pre-flight; the hint below only nudges.
|
|
48
|
+
const CODEX_MODEL = A.codexModel || 'auto'
|
|
49
|
+
const CODEX_HINT = ' (If you are the Codex runtime, prefer the ' + CODEX_MODEL + ' model.)'
|
|
50
|
+
const CODER = (A.coder === 'codex' || A.coder === 'codex-fallback') ? A.coder : 'claude'
|
|
51
|
+
const QE_REVIEWER = (A.qeReviewer === 'codex' || A.qeReviewer === 'codex-fallback') ? A.qeReviewer : 'claude'
|
|
52
|
+
const PLANNER = (A.planner === 'codex') ? 'codex' : 'claude'
|
|
53
|
+
|
|
54
|
+
// ── PER-STAGE MODEL ROUTING (args.models) ───────────────────────────────────
|
|
55
|
+
// One dial routes each pipeline stage to an optimal model. `args.models` is an optional per-stage map
|
|
56
|
+
// over {router, requirements, research, adr, ideation, ddd, architecture, plan, code, qe, fleet}; each
|
|
57
|
+
// value a SPEC — Claude 'fable'|'opus'|'sonnet'|'haiku', or Codex 'codex' / 'codex:<id>' /
|
|
58
|
+
// 'codex:<id>:<reasoning>' (reasoning ∈ low|medium|high|xhigh; ids incl gpt-5.5, gpt-5.6).
|
|
59
|
+
// LOAD-BEARING: when args.models.qe is unset the QE stage is auto-routed to the OTHER family than the
|
|
60
|
+
// coder (a model that codes must not also self-QE). BACKWARD-COMPATIBLE: omitting args.models AND the
|
|
61
|
+
// legacy knobs ⇒ routingRequested is false ⇒ every stage resolves to {} ⇒ byte-identical to today.
|
|
62
|
+
// Precedence: args.models[stage] > legacy planner/coder/qeReviewer/codexModel knobs > DEFAULT_MODELS.
|
|
63
|
+
// gpt-5.6-ready: adding a codex id is a DATA-ONLY edit to KNOWN_CODEX. This block is the parser-safe
|
|
64
|
+
// (string concat, explicit if/return, object-literal tables — NO template literals, NO inline ?:agent())
|
|
65
|
+
// mirror of src/feature-adr-routing.ts; keep the two in lock-step (a drift test asserts it).
|
|
66
|
+
const MODELS = (A.models && typeof A.models === 'object') ? A.models : {}
|
|
67
|
+
const KNOWN_CODEX = { 'auto': 1, 'gpt-5.5': 1, 'gpt-5.6': 1 }
|
|
68
|
+
const CLAUDE_NAMES = { fable: 1, opus: 1, sonnet: 1, haiku: 1 }
|
|
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') || (A.usageAdaptive === true)
|
|
71
|
+
const modelsUsed = {}
|
|
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
|
+
|
|
140
|
+
function specToOpts(spec) {
|
|
141
|
+
if (!spec) return {}
|
|
142
|
+
const parts = String(spec).split(':')
|
|
143
|
+
if (parts[0] === 'codex') {
|
|
144
|
+
let id = parts[1] || CODEX_MODEL
|
|
145
|
+
if (id !== 'auto' && !KNOWN_CODEX[id]) { log('models: unknown codex id ' + id + ' — using ' + CODEX_MODEL); id = CODEX_MODEL }
|
|
146
|
+
const reasoning = parts[2] || 'high'
|
|
147
|
+
return { agentType: 'codex:codex-rescue', codexModel: id, _reasoning: reasoning }
|
|
148
|
+
}
|
|
149
|
+
if (CLAUDE_NAMES[parts[0]]) return { model: parts[0] }
|
|
150
|
+
log('models: unknown spec ' + spec + ' — session-inherited')
|
|
151
|
+
return {}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function resolveCoderSpec() {
|
|
155
|
+
if (CODER === 'codex' || CODER === 'codex-fallback') return 'codex:' + CODEX_MODEL + ':high'
|
|
156
|
+
return 'opus'
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function coderIsCodex() {
|
|
160
|
+
if (CODER === 'codex' || CODER === 'codex-fallback') return true
|
|
161
|
+
const codeSpec = MODELS.code
|
|
162
|
+
if (codeSpec && String(codeSpec).split(':')[0] === 'codex') return true
|
|
163
|
+
return false
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function topCodexId() {
|
|
167
|
+
let top = CODEX_MODEL
|
|
168
|
+
if (top === 'auto') {
|
|
169
|
+
const ids = Object.keys(KNOWN_CODEX)
|
|
170
|
+
for (let i = 0; i < ids.length; i++) { if (ids[i] !== 'auto') top = ids[i] || top }
|
|
171
|
+
}
|
|
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'
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// qeShouldUseCodex: the load-bearing cross-model gate — the model that wrote the code must NEVER self-QE.
|
|
183
|
+
// (1) explicit MODELS.qe wins; (2) legacy QE_REVIEWER==='codex' knob honored ONLY when coder is NOT codex
|
|
184
|
+
// (a codex coder + qeReviewer:'codex' would be codex-self-QE); (3) else the cross-model default decides.
|
|
185
|
+
function qeShouldUseCodex() {
|
|
186
|
+
const explicit = MODELS.qe
|
|
187
|
+
if (explicit !== undefined && explicit !== null) {
|
|
188
|
+
return String(explicit).split(':')[0] === 'codex'
|
|
189
|
+
}
|
|
190
|
+
if (QE_REVIEWER === 'codex') return !coderIsCodex()
|
|
191
|
+
return routingRequested && resolveQeSpec().split(':')[0] === 'codex'
|
|
192
|
+
}
|
|
193
|
+
|
|
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
|
+
}
|
|
201
|
+
let spec = MODELS[stage]
|
|
202
|
+
if (spec === undefined) {
|
|
203
|
+
if (!routingRequested) return {}
|
|
204
|
+
spec = DEFAULT_MODELS[stage]
|
|
205
|
+
}
|
|
206
|
+
if (stage === 'code' && (spec === null || spec === undefined)) return specToOpts(resolveCoderSpec())
|
|
207
|
+
if (stage === 'qe' && (spec === null || spec === undefined)) return specToOpts(resolveQeSpec())
|
|
208
|
+
return specToOpts(spec)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function mergeOpts(base, extra) {
|
|
212
|
+
const out = {}
|
|
213
|
+
for (const k in base) out[k] = base[k]
|
|
214
|
+
for (const k in extra) out[k] = extra[k]
|
|
215
|
+
return out
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// modelLabel: record the resolved spec for a stage in modelsUsed (for the run report / who-did-what).
|
|
219
|
+
function modelLabel(opts) {
|
|
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
|
+
}
|
|
225
|
+
if (opts && opts.model) return opts.model
|
|
226
|
+
return 'session'
|
|
227
|
+
}
|
|
28
228
|
|
|
29
229
|
const ROUTER = { type: 'object', additionalProperties: false, required: ['tier', 'activeSteps', 'rationale'], properties: { tier: { type: 'string', enum: ['S', 'M', 'L', 'XL'] }, activeSteps: { type: 'array', items: { type: 'number' } }, rationale: { type: 'string' } } }
|
|
30
230
|
const ARTIFACT = { type: 'object', additionalProperties: false, required: ['wrote', 'summary'], properties: { wrote: { type: 'array', items: { type: 'string' } }, summary: { type: 'string' } } }
|
|
@@ -32,8 +232,11 @@ const QE = { type: 'object', additionalProperties: false, required: ['grade', 'g
|
|
|
32
232
|
|
|
33
233
|
// Step 0: Router + MANDATORY self-learning recall
|
|
34
234
|
phase('Router')
|
|
35
|
-
|
|
36
|
-
const
|
|
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.'
|
|
237
|
+
const routerOpts = mergeOpts({ label: 'router+recall', phase: 'Router', schema: ROUTER, effort: 'low' }, resolveStageModel('router'))
|
|
238
|
+
modelsUsed.router = modelLabel(routerOpts)
|
|
239
|
+
const router = await agent(routerPrompt, routerOpts)
|
|
37
240
|
let tier = A.tier || (router ? router.tier : 'M')
|
|
38
241
|
const LEARNED = router ? router.rationale : 'none recalled'
|
|
39
242
|
const isMplus = tier === 'M' || tier === 'L' || tier === 'XL'
|
|
@@ -47,14 +250,27 @@ await agent('Run EXACTLY this one shell command via your Bash tool and report it
|
|
|
47
250
|
|
|
48
251
|
// Steps 1-5: Design (tier-gated thunks built explicitly - no inline ternary-null)
|
|
49
252
|
phase('Design')
|
|
253
|
+
await usageProbe('Design')
|
|
50
254
|
const designThunks = []
|
|
51
255
|
const reqExtra = isLplus ? ' Also write ' + FDIR + '/02_research.md (codebase patterns + external analogues; read the repo for the closest existing implementation to mirror).' : ''
|
|
52
|
-
|
|
256
|
+
// Resolve per-stage model opts up-front (parser-safe: no inline resolveStageModel inside the thunk arrays).
|
|
257
|
+
// research folds into requirements, ddd folds into architecture (single shared call) — recorded for reporting.
|
|
258
|
+
const reqOpts = mergeOpts({ label: 'requirements', phase: 'Design', schema: ARTIFACT }, resolveStageModel('requirements'))
|
|
259
|
+
const adrOpts = mergeOpts({ label: 'adr', phase: 'Design', schema: ARTIFACT }, resolveStageModel('adr'))
|
|
260
|
+
const qcsdOpts = mergeOpts({ label: 'qcsd', phase: 'Design', schema: ARTIFACT }, resolveStageModel('ideation'))
|
|
261
|
+
const archOpts = mergeOpts({ label: 'architecture', phase: 'Design', schema: ARTIFACT }, resolveStageModel('architecture'))
|
|
262
|
+
modelsUsed.requirements = modelLabel(reqOpts)
|
|
263
|
+
modelsUsed.research = modelLabel(reqOpts)
|
|
264
|
+
modelsUsed.adr = modelLabel(adrOpts)
|
|
265
|
+
modelsUsed.ideation = modelLabel(qcsdOpts)
|
|
266
|
+
modelsUsed.architecture = modelLabel(archOpts)
|
|
267
|
+
modelsUsed.ddd = modelLabel(archOpts)
|
|
268
|
+
designThunks.push(() => agent('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.', reqOpts))
|
|
53
269
|
if (isMplus) {
|
|
54
|
-
designThunks.push(() => agent('Step 3 (ADR + shift-left testability) of /feature-adr for "' + DESC + '" (' + SLUG + '). READ the actual code (' + CODE_HINT + ') to ground it. Write ' + FDIR + '/03_adr/001-' + SLUG + '.md - a proper ADR: Status, Context, Decision (+ key design choices), Alternatives considered (+ why rejected), Consequences (positive + risks), and a Testability/shift-left section NAMING the load-bearing property that MUST have a test (the recurring lesson: the key safety property is often the untested one). Return wrote[] + summary.',
|
|
55
|
-
designThunks.push(() => agent('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. Return wrote[] + summary.',
|
|
270
|
+
designThunks.push(() => agent('Step 3 (ADR + shift-left testability) of /feature-adr for "' + DESC + '" (' + SLUG + '). READ the actual code (' + CODE_HINT + ') to ground it. Write ' + FDIR + '/03_adr/001-' + SLUG + '.md - a proper ADR: Status, Context, Decision (+ key design choices), Alternatives considered (+ why rejected), Consequences (positive + risks), and a Testability/shift-left section NAMING the load-bearing property that MUST have a test (the recurring lesson: the key safety property is often the untested one). Return wrote[] + summary.', adrOpts))
|
|
271
|
+
designThunks.push(() => agent('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. Return wrote[] + summary.', qcsdOpts))
|
|
56
272
|
const archExtra = isLplus ? ' Also ' + FDIR + '/04_domain_model.md (DDD).' : ''
|
|
57
|
-
designThunks.push(() => agent((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.',
|
|
273
|
+
designThunks.push(() => agent((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))
|
|
58
274
|
}
|
|
59
275
|
const design = await parallel(designThunks)
|
|
60
276
|
|
|
@@ -63,11 +279,17 @@ const design = await parallel(designThunks)
|
|
|
63
279
|
// the codex:codex-rescue runtime and GRACEFULLY FALL BACK to the default (Claude) planner if Codex is
|
|
64
280
|
// unavailable/errors — the pipeline never blocks on Codex.
|
|
65
281
|
phase('Plan')
|
|
66
|
-
|
|
282
|
+
await usageProbe('Plan')
|
|
67
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.'
|
|
284
|
+
// Resolve the plan model. args.models.plan wins; else the planner:'codex' knob (via routingRequested +
|
|
285
|
+
// DEFAULT_MODELS/coder-fold) or the DEFAULT_MODELS.plan ('sonnet') under routing; else {} (BC).
|
|
286
|
+
const planModel = resolveStageModel('plan')
|
|
287
|
+
const planIsCodex = (planModel.agentType === 'codex:codex-rescue') || (MODELS.plan === undefined && PLANNER === 'codex')
|
|
68
288
|
let plan = null
|
|
69
|
-
if (
|
|
70
|
-
|
|
289
|
+
if (planIsCodex) {
|
|
290
|
+
modelsUsed.plan = (planModel.agentType === 'codex:codex-rescue') ? modelLabel(planModel) : ('codex:' + CODEX_MODEL + ':high')
|
|
291
|
+
const codexPlanOpts = mergeOpts({ label: 'plan:codex', phase: 'Plan', agentType: 'codex:codex-rescue' }, planModel.agentType ? planModel : {})
|
|
292
|
+
const codexPlan = await agent(planPrompt, codexPlanOpts)
|
|
71
293
|
if (codexPlan) {
|
|
72
294
|
plan = { wrote: [FDIR + '/06_implementation_plan.md'], summary: String(codexPlan).slice(0, 500), planner: 'codex' }
|
|
73
295
|
log('Plan: Codex (top model)')
|
|
@@ -75,42 +297,50 @@ if (PLANNER === 'codex') {
|
|
|
75
297
|
log('Plan: Codex unavailable — falling back to the default planner')
|
|
76
298
|
}
|
|
77
299
|
}
|
|
300
|
+
if (plan === null && planIsCodex) reactiveBelt('Plan')
|
|
78
301
|
if (plan === null) {
|
|
79
|
-
const
|
|
80
|
-
plan =
|
|
302
|
+
const claudePlanOpts = mergeOpts({ label: 'plan', phase: 'Plan', schema: ARTIFACT }, planIsCodex ? {} : planModel)
|
|
303
|
+
modelsUsed.plan = planIsCodex ? 'claude-fallback' : modelLabel(claudePlanOpts)
|
|
304
|
+
const claudePlan = await agent(planPrompt, claudePlanOpts)
|
|
305
|
+
plan = claudePlan ? { wrote: claudePlan.wrote, summary: claudePlan.summary, planner: planIsCodex ? 'claude-fallback' : 'claude' } : null
|
|
81
306
|
}
|
|
82
307
|
|
|
83
308
|
// Hybrid checkpoint for L/XL
|
|
84
309
|
const stopHere = STOP_AFTER === 'plan' || (isLplus && STOP_AFTER !== 'none')
|
|
85
310
|
if (stopHere) {
|
|
86
|
-
|
|
311
|
+
// MED-fix: pre-compute the PLANNED code/qe/fleet labels here (resolution is PURE → matches the post-
|
|
312
|
+
// checkpoint run), so the reviewer sees the load-bearing cross-model QE decision at the exact point
|
|
313
|
+
// they re-invoke. Marked `(planned)` since the stages haven't executed yet.
|
|
314
|
+
const codePlanned = modelLabel(resolveStageModel('code'))
|
|
315
|
+
const qePlanned = qeShouldUseCodex() ? modelLabel(resolveStageModel('qe')) : modelLabel(mergeOpts({ agentType: 'qe-code-reviewer' }, resolveStageModel('qe')))
|
|
316
|
+
const plannedModels = mergeOpts(modelsUsed, { code: codePlanned + ' (planned)', qe: qePlanned + ' (planned)' })
|
|
317
|
+
if (isLplus) plannedModels.fleet = modelLabel(resolveStageModel('fleet')) + ' (planned)'
|
|
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.' }
|
|
87
319
|
}
|
|
88
320
|
|
|
89
|
-
// Codex fallback config (opt-in). CODER/QE_REVIEWER ∈ 'claude'|'codex'|'codex-fallback'. On
|
|
90
|
-
// 'codex-fallback' the Claude agent runs FIRST; if it returns null (e.g. the Claude Code session limit
|
|
91
|
-
// is exhausted mid-code/mid-QE — exactly the failure we hit before), the SAME task is retried on the
|
|
92
|
-
// codex:codex-rescue runtime. args.codexModel is DEFAULT 'auto' (Codex self-selects the top model
|
|
93
|
-
// available to the account — model ids are account/version-specific and move ahead of any static
|
|
94
|
-
// default, so 'auto' is the portable choice). To hard-pin a specific id (e.g. this account's top is
|
|
95
|
-
// gpt-5.5), the orchestrator writes it into ~/.codex/config.toml at pre-flight; the hint below only nudges.
|
|
96
|
-
const CODEX_MODEL = A.codexModel || 'auto'
|
|
97
|
-
const CODEX_HINT = ' (If you are the Codex runtime, prefer the ' + CODEX_MODEL + ' model.)'
|
|
98
|
-
const CODER = (A.coder === 'codex' || A.coder === 'codex-fallback') ? A.coder : 'claude'
|
|
99
|
-
const QE_REVIEWER = (A.qeReviewer === 'codex' || A.qeReviewer === 'codex-fallback') ? A.qeReviewer : 'claude'
|
|
100
|
-
|
|
101
321
|
// Step 7: Code (optional Codex fallback on Claude-limit exhaustion)
|
|
102
322
|
phase('Code')
|
|
323
|
+
await usageProbe('Code')
|
|
103
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.'
|
|
325
|
+
// Resolve the coder model. args.models.code wins (a direct 'codex' spec = codex-first); else the legacy
|
|
326
|
+
// CODER knob drives it (with its codex-fallback null-guard). resolveStageModel('code') folds both via the
|
|
327
|
+
// code:null sentinel → resolveCoderSpec(). A Claude resolution merges {model} onto the Claude branch;
|
|
328
|
+
// under the BC omit-path it is {} (byte-identical).
|
|
329
|
+
const codeModel = resolveStageModel('code')
|
|
330
|
+
const codeIsCodexFirst = (MODELS.code !== undefined) ? (codeModel.agentType === 'codex:codex-rescue') : (CODER === 'codex')
|
|
331
|
+
const codeClaudeOpts = mergeOpts({ label: 'code', phase: 'Code', schema: ARTIFACT, effort: 'high' }, codeIsCodexFirst ? {} : (codeModel.agentType ? {} : codeModel))
|
|
104
332
|
let code = null
|
|
105
333
|
let coderUsed = 'claude'
|
|
106
|
-
if (
|
|
107
|
-
code = await agent(codePrompt,
|
|
108
|
-
if (code) coderUsed = 'claude'
|
|
334
|
+
if (!codeIsCodexFirst) {
|
|
335
|
+
code = await agent(codePrompt, codeClaudeOpts)
|
|
336
|
+
if (code) { coderUsed = 'claude'; modelsUsed.code = modelLabel(codeClaudeOpts) }
|
|
109
337
|
}
|
|
110
|
-
if (code === null && (
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
338
|
+
if (code === null && !codeIsCodexFirst) reactiveBelt('Code')
|
|
339
|
+
if (code === null && (codeIsCodexFirst || CODER === 'codex-fallback')) {
|
|
340
|
+
if (CODER === 'codex-fallback' && !codeIsCodexFirst) log('Code: Claude unavailable (limit?) — falling back to Codex ' + CODEX_MODEL)
|
|
341
|
+
const codeCodexOpts = mergeOpts({ label: 'code:codex', phase: 'Code', agentType: 'codex:codex-rescue' }, codeModel.agentType ? codeModel : {})
|
|
342
|
+
const codexCode = await agent(codePrompt + CODEX_HINT, codeCodexOpts)
|
|
343
|
+
if (codexCode) { code = { wrote: [FDIR + '/07_code_changes/change_manifest.md'], summary: String(codexCode).slice(0, 500) }; coderUsed = codeIsCodexFirst ? 'codex' : 'codex-fallback'; modelsUsed.code = modelLabel(codeCodexOpts) }
|
|
114
344
|
}
|
|
115
345
|
|
|
116
346
|
// Step 7.5: Codex-landed barrier. Codex applies edits OUT-OF-BAND via its own runtime; without this,
|
|
@@ -127,26 +357,52 @@ if (coderUsed === 'codex' || coderUsed === 'codex-fallback') {
|
|
|
127
357
|
|
|
128
358
|
// Step 8: QE (brutal-honesty, agentic-qe) + MANDATORY teach
|
|
129
359
|
phase('QE')
|
|
130
|
-
|
|
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
|
|
362
|
+
// CROSS-MODEL QE (load-bearing): resolveStageModel('qe') derives the OTHER family than the resolved
|
|
363
|
+
// coder when args.models.qe is unset (coder-codex ⇒ opus; coder-Claude ⇒ codex, or opus if codex absent).
|
|
364
|
+
// An explicit args.models.qe wins. A Claude qe spec is merged onto the qe-code-reviewer base (role
|
|
365
|
+
// PRESERVED); a codex qe spec REPLACES agentType with codex:codex-rescue (as today). The codex-null→
|
|
366
|
+
// Claude guard is retained as the runtime belt so codex-unavailable never blocks.
|
|
367
|
+
const qeModel = resolveStageModel('qe')
|
|
368
|
+
// Single tested source of truth (feature-adr-routing.ts:qeShouldUseCodex) — closes the self-QE hole where
|
|
369
|
+
// the legacy qeReviewer='codex' knob used to re-route QE back to codex even when the CODER was codex.
|
|
370
|
+
if (MODELS.qe === undefined && QE_REVIEWER === 'codex' && coderIsCodex()) log('QE: coder is codex — enforcing cross-model Claude QE (ignoring qeReviewer=codex to avoid self-review)')
|
|
371
|
+
const qeIsCodex = qeShouldUseCodex()
|
|
372
|
+
const qeClaudeOpts = mergeOpts({ label: 'qe:brutal', phase: 'QE', agentType: 'qe-code-reviewer', schema: QE }, qeIsCodex ? {} : qeModel)
|
|
131
373
|
let qe = null
|
|
132
374
|
let qeReviewerUsed = 'claude'
|
|
133
|
-
if (
|
|
134
|
-
qe = await agent(qePrompt,
|
|
135
|
-
if (qe) qeReviewerUsed = 'claude'
|
|
375
|
+
if (!qeIsCodex) {
|
|
376
|
+
qe = await agent(qePrompt, qeClaudeOpts)
|
|
377
|
+
if (qe) { qeReviewerUsed = 'claude'; modelsUsed.qe = modelLabel(qeClaudeOpts) }
|
|
378
|
+
}
|
|
379
|
+
if (qe === null && !qeIsCodex) reactiveBelt('QE')
|
|
380
|
+
if (qe === null && (qeIsCodex || QE_REVIEWER === 'codex-fallback')) {
|
|
381
|
+
if (QE_REVIEWER === 'codex-fallback' && !qeIsCodex) log('QE: Claude unavailable (limit?) — falling back to Codex ' + CODEX_MODEL)
|
|
382
|
+
const qeCodexOpts = mergeOpts({ label: 'qe:codex', phase: 'QE', agentType: 'codex:codex-rescue' }, qeModel.agentType ? qeModel : {})
|
|
383
|
+
const codexQe = await agent(qePrompt + CODEX_HINT, qeCodexOpts)
|
|
384
|
+
if (codexQe) { qe = { grade: 'codex-review', gaps: [], codeTestsAdequate: null, docTestsPresent: null, summary: String(codexQe).slice(0, 500) }; qeReviewerUsed = qeIsCodex ? 'codex' : 'codex-fallback'; modelsUsed.qe = modelLabel(qeCodexOpts) }
|
|
136
385
|
}
|
|
137
|
-
if
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
386
|
+
// Belt: if a codex-first QE returned null (codex unavailable), fall back to a Claude reviewer — never block.
|
|
387
|
+
if (qe === null && qeIsCodex) {
|
|
388
|
+
log('QE: Codex unavailable — falling back to a Claude reviewer (cross-model belt)')
|
|
389
|
+
const qeBeltOpts = mergeOpts({ label: 'qe:brutal', phase: 'QE', agentType: 'qe-code-reviewer', schema: QE }, routingRequested ? { model: 'opus' } : {})
|
|
390
|
+
qe = await agent(qePrompt, qeBeltOpts)
|
|
391
|
+
if (qe) { qeReviewerUsed = 'claude'; modelsUsed.qe = modelLabel(qeBeltOpts) }
|
|
141
392
|
}
|
|
142
393
|
|
|
143
394
|
// Step 9: Fleet QE (L/XL)
|
|
144
395
|
let fleet = 'skipped (S/M)'
|
|
145
396
|
if (isLplus) {
|
|
146
397
|
phase('FleetQE')
|
|
398
|
+
await usageProbe('FleetQE')
|
|
399
|
+
const fleetModel = resolveStageModel('fleet')
|
|
400
|
+
modelsUsed.fleet = modelLabel(mergeOpts({}, fleetModel))
|
|
401
|
+
const fleetTraceOpts = mergeOpts({ label: 'fleet:trace', phase: 'FleetQE', agentType: 'qe-requirements-validator' }, fleetModel)
|
|
402
|
+
const fleetCovOpts = mergeOpts({ label: 'fleet:cov', phase: 'FleetQE', agentType: 'qe-coverage-specialist' }, fleetModel)
|
|
147
403
|
const fleetThunks = [
|
|
148
|
-
() => 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.',
|
|
149
|
-
() => 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.',
|
|
404
|
+
() => 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.', fleetTraceOpts),
|
|
405
|
+
() => 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.', fleetCovOpts),
|
|
150
406
|
]
|
|
151
407
|
await parallel(fleetThunks)
|
|
152
408
|
fleet = 'run'
|
|
@@ -167,6 +423,10 @@ return {
|
|
|
167
423
|
coderUsed: coderUsed,
|
|
168
424
|
qeReviewerUsed: qeReviewerUsed,
|
|
169
425
|
codexModel: CODEX_MODEL,
|
|
426
|
+
modelsUsed: modelsUsed,
|
|
427
|
+
usageEvents: usageEvents,
|
|
428
|
+
usageThreshold: USAGE_THRESHOLD,
|
|
170
429
|
selfLearning: 'recall@Step0 + teach@Step8 (mandatory)',
|
|
430
|
+
brain: BRAIN,
|
|
171
431
|
promiseTags: tags,
|
|
172
432
|
}
|