@dzhechkov/skills-feature-adr 1.3.51 → 1.3.53
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
|
@@ -410,6 +410,25 @@ Fixed roles map to fixed stages (`product-vision`→design+QE, `critic`→QE, `b
|
|
|
410
410
|
open `extra` list adds guidance to any stage. `dz project-skills` prints the who-injected report. Absent ⇒ a
|
|
411
411
|
normal generic run. (See `dz mr-rakes --gen-critic` to auto-generate the `critic` skill from your recurring rakes.)
|
|
412
412
|
|
|
413
|
+
### Adversarial plan-gate at the L/XL checkpoint (needs `dz` ≥ 0.3.117)
|
|
414
|
+
|
|
415
|
+
Cross-model QE (Step 8) catches problems *after* the code is written. At the **checkpoint-after-plan** the
|
|
416
|
+
pipeline now also runs an adversarial **challenge panel** on the plan itself — a FRESH reviewer that did **not**
|
|
417
|
+
write the plan tries to BREAK it across a fixed C1-C8 owner-question set (arch-anti-cement, prod-ready, test
|
|
418
|
+
sufficiency + honesty, overengineering, silent decisions, runtime consistency, scope, executability):
|
|
419
|
+
|
|
420
|
+
- **Panel ≠ plan author** (hard invariant): if Claude wrote the plan the adversary is Codex (honest `codex exec`,
|
|
421
|
+
never a fire-and-forget stub); if Codex wrote it the adversary is a fresh Claude. An unknown family never
|
|
422
|
+
silently claims cross-family.
|
|
423
|
+
- **Every P0/P1 is cross-validated** by a second independent agent, matched by index (never by title), and the
|
|
424
|
+
non-validated ones are dropped — theory never reaches you. If the validator can't cover them, they are
|
|
425
|
+
surfaced **UNVALIDATED**, never silently dropped.
|
|
426
|
+
- **Advisory** — the verdict returns as `challengeVerdict` alongside the ADR + plan; it **never auto-blocks**.
|
|
427
|
+
- Calibrated by `architecture/vision.md` + `testing.md` + `map.json` + `architecture/degradations.md` (a
|
|
428
|
+
deviation from a pattern registered in the degradations file is **not** flagged). Run it ad-hoc with
|
|
429
|
+
`dz challenge --plan <plan.md>` or the `challenge-panel` skill; scaffold the degradations registry via
|
|
430
|
+
`dz feature-adr-setup --from-spec <spec with {"degradations":true}> --apply`.
|
|
431
|
+
|
|
413
432
|
### ADR quality gate (Step 3 generates → Step 8 enforces)
|
|
414
433
|
|
|
415
434
|
Step 3 and Step 8 share an ADR best-practices contract distilled from the
|
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.53",
|
|
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"
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: challenge-panel
|
|
3
|
+
description: >-
|
|
4
|
+
Adversarial plan-gate. BEFORE code, a FRESH reviewer (never the plan author) tries to BREAK an
|
|
5
|
+
implementation plan across a fixed C1-C8 owner-question set, every serious finding is cross-validated,
|
|
6
|
+
and the verdict is surfaced as ADVICE — never an auto-block. Trigger on "прогони challenge", "челлендж
|
|
7
|
+
плана", "challenge the plan", "adversarial review of this plan", or from the feature-adr plan checkpoint.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Challenge Panel — adversarial plan-gate
|
|
11
|
+
|
|
12
|
+
feature-adr's cross-model QE (Step 8) catches problems AFTER the code is written. The most expensive
|
|
13
|
+
mistakes — overengineering, silent decisions, cemented degradations, test-theater, unrealistic scope —
|
|
14
|
+
cement at the PLAN stage, and the plan's author structurally cannot find their own gaps (author bias).
|
|
15
|
+
This skill runs a "break the plan, don't confirm it" panel at the plan stage, by a FRESH agent that did
|
|
16
|
+
NOT write the plan, with cross-validated findings. **Advise, not block.**
|
|
17
|
+
|
|
18
|
+
## Hard invariant (never relax)
|
|
19
|
+
|
|
20
|
+
**The panel is NEVER the plan's own author.** Dispatch the adversary on a DIFFERENT model family than the
|
|
21
|
+
one that wrote the plan:
|
|
22
|
+
- plan written by Claude → adversary = Codex (cross-family). Run it through the honest synchronous path
|
|
23
|
+
(`codex exec` / `safeCodexAgent`), never a fire-and-forget wrapper — a stub reads exactly like a clean
|
|
24
|
+
review. If Codex is unavailable, fall back LOUDLY to a fresh Claude agent and say it was NOT cross-family.
|
|
25
|
+
- plan written by Codex → adversary = a fresh Claude agent (already cross-family).
|
|
26
|
+
- ad-hoc / unknown author → a fresh agent that did not participate in writing the plan.
|
|
27
|
+
|
|
28
|
+
`dz challenge --author <model>` prints which cross-family adversary to dispatch.
|
|
29
|
+
|
|
30
|
+
## Protocol
|
|
31
|
+
|
|
32
|
+
1. **Assemble the WIDE context (deterministic).** Run:
|
|
33
|
+
```bash
|
|
34
|
+
dz challenge --plan <path/to/06_implementation_plan.md>
|
|
35
|
+
```
|
|
36
|
+
This prints the challenge brief: the plan + `architecture/vision.md` + `architecture/testing.md` +
|
|
37
|
+
`architecture/map.json` + `architecture/degradations.md` inlined (missing docs degrade gracefully) +
|
|
38
|
+
the fixed C1-C8 questions + the verdict JSON schema. A WIDE context is load-bearing — hand a reviewer a
|
|
39
|
+
narrow slice and you get findings about the slice.
|
|
40
|
+
- `--context-only` shows just what the panel will read (and the chosen adversary).
|
|
41
|
+
- `--json` emits the context + brief + adversary as JSON for a subagent.
|
|
42
|
+
|
|
43
|
+
2. **Fire the panel (fresh adversary ≠ author).** Dispatch a subagent on the cross-family model with the
|
|
44
|
+
brief. It answers C1-C8 in "break it" mode and returns the verdict JSON:
|
|
45
|
+
| # | Question |
|
|
46
|
+
|---|----------|
|
|
47
|
+
| C1 | Architecture anti-cement — cements a NEW bad pattern/boundary? (deviating from a REGISTERED accepted degradation is NOT a finding) |
|
|
48
|
+
| C2 | Production-ready — where does it fall over? name the concrete input/condition |
|
|
49
|
+
| C3 | Test sufficiency + honesty BOTH ways — an ADR-named "never X" with no falsifying test? tests that are theater? |
|
|
50
|
+
| C4 | Overengineering sweep — built for a requirement nobody stated? the simpler thing? |
|
|
51
|
+
| C5 | Silent decisions — a policy made without surfacing it as a decision the owner could refuse |
|
|
52
|
+
| C6 | Runtime consistency — contradicts an existing convention (error shape, config source, ESM, naming)? |
|
|
53
|
+
| C7 | Scope — > ~1.5× what the request needs? the concrete cut list |
|
|
54
|
+
| C8 | Executability — could a non-author complete every step without coming back to ask? |
|
|
55
|
+
|
|
56
|
+
Each finding: `c` (C-number), `severity` (P0/P1/P2), `title`, `why` (a concrete failing input/condition —
|
|
57
|
+
never a general worry), optional `where`.
|
|
58
|
+
|
|
59
|
+
3. **Cross-validate every P0/P1 (mandatory anti-noise).** Dispatch a SECOND, independent agent: for each
|
|
60
|
+
P0/P1, "real and reachable, or FP/theory?" — default to false when uncertain. **Drop** every P0/P1 that
|
|
61
|
+
is not confirmed. Theory never reaches the owner (a false gate kills trust). P2 pass through.
|
|
62
|
+
|
|
63
|
+
4. **Surface the verdict — ADVISE, never block.** Present the cross-validated P0/P1 prominently + P2 as
|
|
64
|
+
notes. Do NOT auto-abort. The owner decides whether to revise the plan, accept a finding into
|
|
65
|
+
`architecture/degradations.md`, or proceed.
|
|
66
|
+
|
|
67
|
+
## Calibration docs (optional, improve precision)
|
|
68
|
+
|
|
69
|
+
- `architecture/vision.md` — boundaries + principles (what the product deliberately does NOT do).
|
|
70
|
+
- `architecture/testing.md` — what "done" and an honest test mean here.
|
|
71
|
+
- `architecture/degradations.md` — the accepted-degradations registry: patterns you KNOW are imperfect but
|
|
72
|
+
keep on purpose. **C1 does not flag a deviation from anything registered here.** Scaffold a starter with
|
|
73
|
+
`dz feature-adr-setup --from-spec <spec with {"degradations":true}> --apply`.
|
|
74
|
+
|
|
75
|
+
Absent docs never error — the panel simply runs with less calibration.
|
|
76
|
+
|
|
77
|
+
## In the feature-adr pipeline
|
|
78
|
+
|
|
79
|
+
At the L/XL **checkpoint-after-plan**, feature-adr runs this panel automatically (a fresh adversary ≠ the
|
|
80
|
+
Step-6 planner), cross-validates, and returns `challengeVerdict` alongside the ADR + plan — advisory. In
|
|
81
|
+
**plain** `/feature-adr`, offer the panel at the planning checkpoint before coding.
|
|
@@ -169,6 +169,21 @@ Estimated {K} files to create/modify
|
|
|
169
169
|
═══════════════════════════════════════════════════════
|
|
170
170
|
```
|
|
171
171
|
|
|
172
|
+
## Challenge panel (adversarial plan-gate — before coding)
|
|
173
|
+
|
|
174
|
+
Before leaving the plan checkpoint for code (especially L/XL), offer the **`challenge-panel`** skill — a
|
|
175
|
+
FRESH adversary (never the plan author) tries to BREAK the plan across the fixed C1-C8 owner questions,
|
|
176
|
+
every P0/P1 is cross-validated, and the verdict is ADVISORY (never an auto-block):
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
dz challenge --plan features/<slug>/06_implementation_plan.md --author <planner-model>
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Dispatch the panel on the CROSS-FAMILY model (`--author` prints which): plan by Claude → Codex adversary
|
|
183
|
+
(honest `codex exec`, never a stub); plan by Codex → fresh Claude. Drop any P0/P1 the cross-validator does
|
|
184
|
+
not confirm. A finding the owner accepts can be recorded in `architecture/degradations.md` so C1 stops
|
|
185
|
+
re-flagging it. In the ultracode workflow this врезка runs automatically and returns `challengeVerdict`.
|
|
186
|
+
|
|
172
187
|
## Quality Gates
|
|
173
188
|
|
|
174
189
|
- [ ] Every task has clear done criteria
|
|
@@ -570,6 +570,36 @@ const isMplus = tier === 'M' || tier === 'L' || tier === 'XL'
|
|
|
570
570
|
const isLplus = tier === 'L' || tier === 'XL'
|
|
571
571
|
log('Router: tier ' + tier)
|
|
572
572
|
|
|
573
|
+
// ── AUTO-COST pre-resolution (feature learned-cost-routing) ──
|
|
574
|
+
// A stage whose spec is 'auto-cost' is resolved HERE (tier is now known) to a concrete model via
|
|
575
|
+
// `dz routing --select` — the workflow is sandboxed (no fs), so selection I/O shells out to dz. Byte-identical
|
|
576
|
+
// no-op (ZERO agent calls) when no stage is 'auto-cost'. Order matters: resolve `code` FIRST so `qe` can be
|
|
577
|
+
// forced to the CROSS-family of the coder (the named cross-model-QE guard). Escalate-on-fail across runs is
|
|
578
|
+
// automatic: a gate-FAIL recorded below down-ranks the model so the NEXT run's select picks the next rung.
|
|
579
|
+
const AUTOCOST = {}
|
|
580
|
+
function acFamOf(spec) { return /codex|gpt|openai/i.test(String(spec)) ? 'openai' : 'claude' }
|
|
581
|
+
function acConcrete(model) { return CLAUDE_NAMES[model] ? model : ('codex:' + model + ':high') }
|
|
582
|
+
function acBareId(spec) { var s = String(spec || ''); return s.indexOf('codex:') === 0 ? (s.split(':')[1] || s) : s }
|
|
583
|
+
async function resolveAutoCost(stage, familyArg) {
|
|
584
|
+
const famFlag = familyArg ? (' --family ' + familyArg) : ''
|
|
585
|
+
const out = await agent('Run EXACTLY this via Bash and reply with ONLY its stdout (a single JSON line), nothing else: ' + DZ + ' routing --select --stage ' + stage + ' --tier ' + tier + famFlag, { label: 'auto-cost:select:' + stage, phase: 'Route', effort: 'low' })
|
|
586
|
+
let pick = null
|
|
587
|
+
try { pick = JSON.parse(String(out).replace(/^[^{]*/, '').replace(/[^}]*$/, '')) } catch { pick = null }
|
|
588
|
+
if (!pick || !pick.model) { log('auto-cost ' + stage + ': no candidate model — leaving session-inherited'); MODELS[stage] = undefined; return }
|
|
589
|
+
MODELS[stage] = acConcrete(pick.model)
|
|
590
|
+
AUTOCOST[stage] = { chain: (pick.chain || []), tier: tier, evidence: pick.evidence || '' }
|
|
591
|
+
log('auto-cost ' + stage + ' → ' + MODELS[stage] + ' [' + (pick.metBar ? 'learned' : 'cold-start') + ']')
|
|
592
|
+
}
|
|
593
|
+
const autoCostStages = Object.keys(MODELS).filter(function (s) { return MODELS[s] === 'auto-cost' })
|
|
594
|
+
if (autoCostStages.length > 0) {
|
|
595
|
+
for (const s of autoCostStages) { if (s !== 'qe') await resolveAutoCost(s, null) }
|
|
596
|
+
if (MODELS.qe === 'auto-cost') {
|
|
597
|
+
// cross-family of the resolved coder (guard): coder codex → qe claude; coder claude → qe openai.
|
|
598
|
+
const coderSpec = (MODELS.code !== undefined && MODELS.code !== null && MODELS.code !== 'auto-cost') ? MODELS.code : ((CODER === 'codex' || CODER === 'codex-fallback') ? 'codex' : 'opus')
|
|
599
|
+
await resolveAutoCost('qe', acFamOf(coderSpec) === 'openai' ? 'claude' : 'openai')
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
573
603
|
// GUARANTEED fa-panel write (the router, being low-effort + multi-job, tends to skip the fa-record
|
|
574
604
|
// Bash call). A dedicated single-command agent reliably lights up the live /feature-adr panel at the
|
|
575
605
|
// most visible moment. Uses the workspace bin (PATH-independent). Best-effort — never blocks.
|
|
@@ -663,6 +693,73 @@ if (plan === null) {
|
|
|
663
693
|
plan = claudePlan ? { wrote: claudePlan.wrote, summary: claudePlan.summary, planner: planIsCodex ? 'claude-fallback' : 'claude' } : null
|
|
664
694
|
}
|
|
665
695
|
|
|
696
|
+
// ── R6 challenge panel: adversarial plan-gate at the checkpoint (ADVISE, never block) ──
|
|
697
|
+
// The panel is NEVER the plan's own author (ADR §1 hard invariant): author=Claude → a cross-family Codex
|
|
698
|
+
// adversary via the HONEST synchronous codex exec path (safeCodexAgent — a stub would read as a clean
|
|
699
|
+
// review, so it is never used for this data-returning verdict); author=Codex → a FRESH Claude adversary.
|
|
700
|
+
// The adversary loads the WIDE context via `dz challenge` (plan + vision + testing + map + degradations),
|
|
701
|
+
// answers C1-C8 in break-it mode, then every P0/P1 is cross-validated by an independent agent and the
|
|
702
|
+
// non-validated ones are dropped (anti-noise). Failure anywhere degrades LOUDLY to a fresh Claude panel.
|
|
703
|
+
const CHALLENGE_SCHEMA = { type: 'object', additionalProperties: false, required: ['findings', 'summary'], properties: { findings: { type: 'array', items: { type: 'object', additionalProperties: false, required: ['c', 'severity', 'title', 'why'], properties: { c: { type: 'string' }, severity: { type: 'string', enum: ['P0', 'P1', 'P2'] }, title: { type: 'string' }, why: { type: 'string' }, where: { type: 'string' } } } }, summary: { type: 'string' } } }
|
|
704
|
+
const CHALLENGE_CIDS = new Set(['C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8'])
|
|
705
|
+
// sh-quote for safe interpolation into a Bash string (QE #13 injection via REPO path / dzBin).
|
|
706
|
+
function shq(s) { return "'" + String(s).replace(/'/g, "'\\''") + "'" }
|
|
707
|
+
// Validate a raw adversary payload → {findings,summary} or null (QE #7: {} / {findings:[]} / [null] are not
|
|
708
|
+
// a fake-clean pass). A value without a findings ARRAY is null → loud fallback; junk findings are dropped.
|
|
709
|
+
function sanitizeChallengeVerdict(raw) {
|
|
710
|
+
if (!raw || typeof raw !== 'object' || !Array.isArray(raw.findings)) return null
|
|
711
|
+
const findings = raw.findings.filter((f) => f && typeof f === 'object' && CHALLENGE_CIDS.has(String(f.c)) && (f.severity === 'P0' || f.severity === 'P1' || f.severity === 'P2') && typeof f.title === 'string' && f.title !== '' && typeof f.why === 'string' && f.why !== '')
|
|
712
|
+
return { findings: findings, summary: typeof raw.summary === 'string' ? raw.summary : '' }
|
|
713
|
+
}
|
|
714
|
+
async function runChallengePanel(planRel, plannerName) {
|
|
715
|
+
const authorIsCodex = /codex|gpt|openai|\bo[1-9]\b/i.test(String(plannerName || ''))
|
|
716
|
+
const dzChallenge = 'cd ' + shq(REPO) + ' && ' + shq(DZ) + ' challenge --plan ' + shq(planRel) + ' --author ' + shq(plannerName || 'claude')
|
|
717
|
+
// Preflight (QE #11): the plan artifact must EXIST and be non-empty, else an adversary hallucinates a
|
|
718
|
+
// verdict on a missing file. Surface a loud status instead of a fake review.
|
|
719
|
+
const pre = await agent('Run EXACTLY this via Bash and reply with ONLY its stdout: ' + 'cd ' + shq(REPO) + ' && (test -s ' + shq(planRel) + ' && echo PLAN_OK || echo PLAN_MISSING)', { label: 'challenge:preflight', phase: 'Plan', effort: 'low' })
|
|
720
|
+
if (!/PLAN_OK/.test(String(pre || ''))) { log('Challenge panel: plan artifact missing/empty (' + planRel + ') — panel skipped'); return { status: 'no-plan', adversary: null, findings: [], summary: '', note: 'Challenge panel skipped — the implementation plan artifact was missing or empty.' } }
|
|
721
|
+
let verdict = null
|
|
722
|
+
let adversary = authorIsCodex ? 'claude' : 'codex'
|
|
723
|
+
if (!authorIsCodex) {
|
|
724
|
+
// author=Claude → Codex adversary (cross-family). Compact prompt: Codex reads the files itself (no 24k
|
|
725
|
+
// brief inlined). safeCodexAgent is the honest exec path; null/invalid ⇒ loud Claude fallback below.
|
|
726
|
+
const cx = await safeCodexAgent('You are a FRESH adversarial reviewer of an implementation plan you did NOT write. Read these files: ' + planRel + ' , architecture/vision.md , architecture/degradations.md (relative to repo ' + REPO + '). BREAK the plan, do not confirm it. Answer C1 arch-anti-cement (deviating from a pattern in the degradations registry is NOT a finding), C2 prod-ready, C3 test sufficiency+honesty both ways, C4 overengineering, C5 silent decisions, C6 runtime consistency, C7 scope>1.5x, C8 executability. Output ONLY minified JSON {"findings":[{"c","severity":"P0|P1|P2","title","why","where"}],"summary"}.', { label: 'challenge:codex-adversary', phase: 'Plan' })
|
|
727
|
+
if (cx) { try { verdict = sanitizeChallengeVerdict(JSON.parse(String(cx).replace(/^[^{]*/, '').replace(/[^}]*$/, ''))) } catch { verdict = null } }
|
|
728
|
+
if (!verdict) { log('Challenge panel: Codex adversary unavailable/unparseable/invalid — falling back to a FRESH Claude panel (NOT cross-family; run `dz challenge` + codex manually for a cross-family pass)'); adversary = 'claude-fallback' }
|
|
729
|
+
}
|
|
730
|
+
if (!verdict) {
|
|
731
|
+
// Claude adversary (fresh instance ≠ the author): loads the WIDE brief via dz, then answers the schema.
|
|
732
|
+
const raw = await agent('You are a FRESH adversarial reviewer. You did NOT write this plan. First run EXACTLY this via Bash to load the wide challenge brief (plan + vision + testing + map + degradations + the C1-C8 questions): ' + dzChallenge + '\nThen BREAK the plan per C1-C8 (do NOT confirm it): a finding is a concrete failing input/condition, never a general worry; deviating from a pattern in the degradations registry is NOT a finding. Return the verdict.', { label: 'challenge:claude-adversary', phase: 'Plan', schema: CHALLENGE_SCHEMA })
|
|
733
|
+
verdict = sanitizeChallengeVerdict(raw)
|
|
734
|
+
}
|
|
735
|
+
if (!verdict) { log('Challenge panel: no usable verdict from any adversary — surfacing unavailable status (advisory)'); return { status: 'adversary-unavailable', adversary: adversary, findings: [], summary: '', note: 'Challenge panel could not produce a verdict — run `dz challenge` + the panel manually.' } }
|
|
736
|
+
// Cross-validate P0/P1 by INDEX (QE #5: never by title — duplicate titles cross-contaminate). Deterministic
|
|
737
|
+
// sorted order shared with the validator; results[i] aligns to pp[i].
|
|
738
|
+
const rank = { P0: 3, P1: 2, P2: 1 }
|
|
739
|
+
const pp = verdict.findings.filter((f) => f.severity === 'P0' || f.severity === 'P1').sort((a, b) => (rank[b.severity] - rank[a.severity]) || (a.c < b.c ? -1 : a.c > b.c ? 1 : 0))
|
|
740
|
+
let confirmed = verdict.findings.filter((f) => f.severity === 'P2')
|
|
741
|
+
let status = 'ok'
|
|
742
|
+
if (pp.length > 0) {
|
|
743
|
+
const numbered = pp.map((f, i) => ({ i: i, c: f.c, severity: f.severity, title: f.title, why: f.why }))
|
|
744
|
+
const cv = await agent('Independently CROSS-VALIDATE these adversarial plan findings against the plan at ' + planRel + ' (repo ' + REPO + '). For EACH by its "i" index, decide if it is REAL and reachable, or FP/theory. Default to real=false when uncertain. Findings JSON (with stable index i): ' + JSON.stringify(numbered) + '\nReturn {"results":[{"i":<the index>,"real":true|false}...]} covering EVERY index exactly once.', { label: 'challenge:cross-validate', phase: 'Plan', schema: { type: 'object', additionalProperties: false, required: ['results'], properties: { results: { type: 'array', items: { type: 'object', additionalProperties: false, required: ['i', 'real'], properties: { i: { type: 'number' }, real: { type: 'boolean' } } } } } } })
|
|
745
|
+
const realByIndex = new Map((cv && Array.isArray(cv.results) ? cv.results : []).map((r) => [Number(r.i), r.real === true]))
|
|
746
|
+
// QE #8: a validator OUTAGE (missing indices) must NOT read as "clean" — do not silently drop the P0/P1.
|
|
747
|
+
const covered = pp.every((_, i) => realByIndex.has(i))
|
|
748
|
+
if (!covered) {
|
|
749
|
+
status = 'cross-validation-incomplete'
|
|
750
|
+
log('Challenge panel: cross-validator did not cover every P0/P1 — surfacing them UNVALIDATED (not dropped, not confirmed)')
|
|
751
|
+
for (const f of pp) confirmed.push(Object.assign({}, f, { crossValidated: false, unvalidated: true }))
|
|
752
|
+
} else {
|
|
753
|
+
pp.forEach((f, i) => { if (realByIndex.get(i) === true) confirmed.push(Object.assign({}, f, { crossValidated: true })) })
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
confirmed.sort((a, b) => (rank[b.severity] - rank[a.severity]) || (a.c < b.c ? -1 : a.c > b.c ? 1 : 0))
|
|
757
|
+
const note = status === 'cross-validation-incomplete'
|
|
758
|
+
? 'ADVISORY — cross-validation was INCOMPLETE; P0/P1 shown are UNVALIDATED (verify manually). Nothing blocks.'
|
|
759
|
+
: 'ADVISORY — the owner decides; nothing blocks. Cross-validated P0/P1 + all P2 shown.'
|
|
760
|
+
return { status: status, adversary: adversary, findings: confirmed, summary: verdict.summary, note: note }
|
|
761
|
+
}
|
|
762
|
+
|
|
666
763
|
// Hybrid checkpoint for L/XL
|
|
667
764
|
const stopHere = STOP_AFTER === 'plan' || (isLplus && STOP_AFTER !== 'none')
|
|
668
765
|
if (stopHere) {
|
|
@@ -673,7 +770,11 @@ if (stopHere) {
|
|
|
673
770
|
const qePlanned = qeShouldUseCodex() ? modelLabel(resolveStageModel('qe')) : modelLabel(mergeOpts({ agentType: 'qe-code-reviewer' }, resolveStageModel('qe')))
|
|
674
771
|
const plannedModels = mergeOpts(modelsUsed, { code: codePlanned + ' (planned)', qe: qePlanned + ' (planned)' })
|
|
675
772
|
if (isLplus) plannedModels.fleet = modelLabel(resolveStageModel('fleet')) + ' (planned)'
|
|
676
|
-
|
|
773
|
+
// R6 врезка: adversarial plan-gate (advise). Panel ≠ plan author; wrapped so a panel failure never blocks the checkpoint.
|
|
774
|
+
let challengeVerdict = null
|
|
775
|
+
try { challengeVerdict = plan ? await runChallengePanel('features/' + SLUG + '/06_implementation_plan.md', plan.planner) : null }
|
|
776
|
+
catch (e) { log('Challenge panel errored (advisory, ignored): ' + (e && e.message ? e.message : String(e))) }
|
|
777
|
+
return { tier: tier, phase: 'checkpoint-after-plan', artifactsDir: FDIR, planner: (plan ? plan.planner : null), plan: (plan ? plan.summary : null), modelsUsed: plannedModels, challengeVerdict: challengeVerdict, 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), then re-invoke with args.stopAfter="none" to implement + QE.' }
|
|
677
778
|
}
|
|
678
779
|
|
|
679
780
|
// Step 7: Code (optional Codex fallback on Claude-limit exhaustion)
|
|
@@ -774,6 +875,55 @@ if (qe === null && qeIsCodex) {
|
|
|
774
875
|
const claimGate = step8ClaimGate(qe && qe.claimCheck ? qe.claimCheck : null)
|
|
775
876
|
log(claimGate.note)
|
|
776
877
|
|
|
878
|
+
// ── AUTO-COST outcome recording (feature learned-cost-routing) ──
|
|
879
|
+
// The two-phase label lands here: every auto-cost stage that produced an artifact records a PROVISIONAL (i);
|
|
880
|
+
// the CODE stage's authoritative gate (ii) is the Step-8 QE grade (A/B = pass) — a produced-but-gate-FAILED
|
|
881
|
+
// run is recorded as a FAILURE, down-ranking that model for the next run (ADR §2). Byte-identical no-op when
|
|
882
|
+
// nothing was auto-cost. Gate-less stages get weak provisional credit; richer per-stage gates are Phase-2.
|
|
883
|
+
if (Object.keys(AUTOCOST).length > 0) {
|
|
884
|
+
const recPhase = isLplus ? 'FleetQE' : 'QE'
|
|
885
|
+
// Record ONLY stages that ACTUALLY RAN and produced an artifact (QE finding: a blanket loop over configured
|
|
886
|
+
// auto-cost stages credited skipped/failed/fallback stages). `code`/`plan` are guarded by their result var.
|
|
887
|
+
// ATTRIBUTION guard (QE #2): the auto-cost pick's outcome must be attributed to the PICK's model — but a
|
|
888
|
+
// codex-FALLBACK produces the code on a DIFFERENT model when the picked model returned null. So:
|
|
889
|
+
// • the picked model ran & produced → provisional + finalize by the QE gate.
|
|
890
|
+
// • a fallback fired (pick returned null) → the PICK FAILED to deliver → finalize(pick, false), NOT the
|
|
891
|
+
// fallback model (which was not auto-cost-selected). This down-ranks the pick honestly.
|
|
892
|
+
const codeMid = acBareId(MODELS.code)
|
|
893
|
+
if (AUTOCOST.code && codeMid) {
|
|
894
|
+
const pickIsCodex = /codex|gpt/i.test(String(MODELS.code))
|
|
895
|
+
const pickRan = pickIsCodex ? (coderUsed === 'codex') : (coderUsed === 'claude')
|
|
896
|
+
if (pickRan && code) {
|
|
897
|
+
const codePassed = !!(qe && /^[AB]$/i.test(String(qe.grade || '').trim()))
|
|
898
|
+
await agent('Run EXACTLY this via Bash and reply with ONLY its stdout: ' + DZ + ' routing --record-provisional --stage code --tier ' + AUTOCOST.code.tier + ' --model ' + codeMid, { label: 'auto-cost:record:code', phase: recPhase, effort: 'low' })
|
|
899
|
+
await agent('Run EXACTLY this via Bash and reply with ONLY its stdout: ' + DZ + ' routing --finalize --stage code --tier ' + AUTOCOST.code.tier + ' --model ' + codeMid + ' --success ' + (codePassed ? 'true' : 'false'), { label: 'auto-cost:finalize:code', phase: recPhase, effort: 'low' })
|
|
900
|
+
} else {
|
|
901
|
+
// the picked model did not deliver (fallback fired or produced nothing) → record it as a failure.
|
|
902
|
+
log('auto-cost code: picked model ' + codeMid + ' did not deliver (coderUsed=' + coderUsed + ') — recording a failure')
|
|
903
|
+
await agent('Run EXACTLY this via Bash and reply with ONLY its stdout: ' + DZ + ' routing --finalize --stage code --tier ' + AUTOCOST.code.tier + ' --model ' + codeMid + ' --success false', { label: 'auto-cost:finalize:code-fail', phase: recPhase, effort: 'low' })
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
// PLAN: provisional only (its gate is landing, already enforced upstream); guarded by the plan result var.
|
|
907
|
+
const planMid = acBareId(MODELS.plan)
|
|
908
|
+
if (AUTOCOST.plan && plan && planMid) {
|
|
909
|
+
await agent('Run EXACTLY this via Bash and reply with ONLY its stdout: ' + DZ + ' routing --record-provisional --stage plan --tier ' + AUTOCOST.plan.tier + ' --model ' + planMid, { label: 'auto-cost:record:plan', phase: recPhase, effort: 'low' })
|
|
910
|
+
}
|
|
911
|
+
// QE: gate (ii) = produced a PARSEABLE verdict with a grade (qe non-null). A qe that named no grade fell back
|
|
912
|
+
// and is not the auto-cost pick, so record only when the picked reviewer actually delivered a verdict. Its
|
|
913
|
+
// model ran iff qeReviewerUsed matches the pick's family (else a belt/fallback reviewer ran).
|
|
914
|
+
const qeMid = acBareId(MODELS.qe)
|
|
915
|
+
if (AUTOCOST.qe && qeMid) {
|
|
916
|
+
const qePickIsCodex = /codex|gpt/i.test(String(MODELS.qe))
|
|
917
|
+
const qePickRan = qePickIsCodex ? (qeReviewerUsed === 'codex') : (qeReviewerUsed === 'claude')
|
|
918
|
+
if (qePickRan && qe && String(qe.grade || '').trim() !== '') {
|
|
919
|
+
await agent('Run EXACTLY this via Bash and reply with ONLY its stdout: ' + DZ + ' routing --record-provisional --stage qe --tier ' + AUTOCOST.qe.tier + ' --model ' + qeMid, { label: 'auto-cost:record:qe', phase: recPhase, effort: 'low' })
|
|
920
|
+
await agent('Run EXACTLY this via Bash and reply with ONLY its stdout: ' + DZ + ' routing --finalize --stage qe --tier ' + AUTOCOST.qe.tier + ' --model ' + qeMid + ' --success true', { label: 'auto-cost:finalize:qe', phase: recPhase, effort: 'low' })
|
|
921
|
+
} else {
|
|
922
|
+
await agent('Run EXACTLY this via Bash and reply with ONLY its stdout: ' + DZ + ' routing --finalize --stage qe --tier ' + AUTOCOST.qe.tier + ' --model ' + qeMid + ' --success false', { label: 'auto-cost:finalize:qe-fail', phase: recPhase, effort: 'low' })
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
777
927
|
// Step 9: Fleet QE (L/XL)
|
|
778
928
|
let fleet = 'skipped (S/M)'
|
|
779
929
|
if (isLplus) {
|
|
@@ -818,5 +968,6 @@ return {
|
|
|
818
968
|
brain: BRAIN,
|
|
819
969
|
polymorphism: POLY.hasManifest ? POLY.report : null,
|
|
820
970
|
claimGate: claimGate,
|
|
971
|
+
autoCost: Object.keys(AUTOCOST).length ? AUTOCOST : null,
|
|
821
972
|
promiseTags: tags,
|
|
822
973
|
}
|