@dzhechkov/harness-core 0.3.147 → 0.3.149

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.
@@ -0,0 +1,221 @@
1
+ /**
2
+ * feature-adr durable checkpoints — the PURE half (backlog 49e4a95b).
3
+ *
4
+ * Problem: .claude/workflows/feature-adr.js restarts an L/XL run from scratch when its session dies
5
+ * (the exact failure that forced usage-adaptive routing — one run cost 623k subagent tokens), and the
6
+ * STANDARD L/XL two-phase flow (stop-after-plan → re-invoke) re-runs router+design+plan wholesale.
7
+ * The Workflow harness's own resumeFromRunId is same-session only, so it cannot cover either case.
8
+ *
9
+ * Design: the heavyweight state is ALREADY durable — the 00–09 artifacts in features/<slug>/. The
10
+ * checkpoint layer is deliberately THIN: after each expensive stage the workflow appends one JSONL
11
+ * line { stage, inputHash, result } to features/<slug>/.fa-state/checkpoints.jsonl (via a cheap
12
+ * effort-low agent — the workflow sandbox has no fs). On the next run with the same slug, a stage is
13
+ * SKIPPED only when its recorded inputHash matches the freshly computed one AND its expected artifact
14
+ * is still on disk. Granularity is per-STAGE, not per-agent-call: a death mid-Step-7 re-runs Step 7,
15
+ * never Steps 0–6.
16
+ *
17
+ * Provenance: concept (checkpoint keyed by input hash + call cache) from ADR-157
18
+ * darwin-checkpoints-durable-execution (status PROPOSED) in agent-harness-generator. Its "~39% resume
19
+ * saving" figure is from a SYNTHETIC deterministic simulation — deliberately NOT quoted as expected
20
+ * field saving anywhere in this feature.
21
+ *
22
+ * Everything here is pure and deterministic (no Date/random — the workflow sandbox forbids them);
23
+ * the workflow script mirrors these functions inline (it is self-contained and cannot import), and
24
+ * the wiring test asserts the mirror stays present.
25
+ */
26
+
27
+ /** Stages the workflow checkpoints, in pipeline order. Cheap side-channel agents (usage probes,
28
+ * fa-record, auto-cost selects) are never checkpointed; the opt-in Delivery gate re-runs by design
29
+ * (advisory verdicts should reflect the CURRENT tree). */
30
+ export const CHECKPOINT_STAGES = ['router', 'design', 'plan', 'code', 'qe', 'fleet'] as const;
31
+ export type CheckpointStage = (typeof CHECKPOINT_STAGES)[number];
32
+
33
+ /** The artifact(s) (relative to features/<slug>/) whose PRESENCE a resume additionally requires in
34
+ * 'auto' mode — EVERY listed path must exist. null = result-only stage (hash match suffices).
35
+ * Tier-dependent stages (design) take extra artifacts at the call site via `extraArtifacts` —
36
+ * an M+ design must probe its ADR/ideation/architecture files too, not just requirements
37
+ * (Codex QE #2: a one-file probe accepted a materially incomplete design). */
38
+ export const STAGE_ARTIFACTS: Record<CheckpointStage, string | null> = {
39
+ router: null,
40
+ design: '01_requirements.md',
41
+ plan: '06_implementation_plan.md',
42
+ code: '07_code_changes/change_manifest.md',
43
+ qe: '08_qe_report.md',
44
+ fleet: '09_fleet_qe_assessment.md',
45
+ };
46
+
47
+ /** A checkpoint line as persisted (one JSON object per line). */
48
+ export interface CheckpointEntry {
49
+ stage: string;
50
+ inputHash: string;
51
+ result: unknown;
52
+ }
53
+
54
+ /** Oversize guard: a result JSON above this is NOT checkpointed (the stage simply re-runs on resume).
55
+ * Keeps the read-back prompt bounded; artifacts on disk carry the heavy state anyway. */
56
+ export const CHECKPOINT_MAX_RESULT_CHARS = 12_000;
57
+
58
+ /** Checkpoint format/logic version — SALTED into every input hash. Bump it whenever the workflow's
59
+ * stage semantics, prompts, or composite result shapes change: every pre-existing checkpoint then
60
+ * hashes stale and re-runs, instead of an old-format entry resuming into new logic (Codex QE #5). */
61
+ export const CKPT_SCHEMA_VERSION = 'fa-ckpt-2';
62
+
63
+ /** FNV-1a 32-bit over UTF-16 code units, hex-encoded (one pass; building block for the 64-bit form). */
64
+ export function fnv1a(str: string): string {
65
+ let h = 0x811c9dc5;
66
+ for (let i = 0; i < str.length; i++) {
67
+ h ^= str.charCodeAt(i);
68
+ h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0;
69
+ }
70
+ return h.toString(16).padStart(8, '0');
71
+ }
72
+
73
+ /** 64 bits from two independent FNV-1a passes (plain + salted). A single 32-bit hash admits
74
+ * findable collisions (Codex QE #9 produced a real pair at `11a08b58`); two passes make the
75
+ * single-pair collision odds ~2^-64 — adequate for one slug's checkpoint file. */
76
+ export function fnv1a64(str: string): string {
77
+ return fnv1a(str) + fnv1a('fa-ckpt-salt' + str);
78
+ }
79
+
80
+ /** The stage's input fingerprint: a JSON-tuple (delimiter-ambiguity class — never a separator join)
81
+ * of the schema version + stage name + every input that would change the stage's output, hashed.
82
+ * Upstream stage RESULTS are included as their serialized form, so a stale upstream auto-invalidates
83
+ * downstream. HONEST SCOPE: the hash proves the run INPUTS are unchanged — it does NOT fingerprint
84
+ * the working tree (a crash-resume legitimately sees the dead run's uncommitted writes, so a tree
85
+ * hash would invalidate every real resume). Tree-level staleness is out of the checkpoint contract:
86
+ * use resume:'never' (or delete .fa-state/) after manual edits, and re-QE independently — the ADR
87
+ * names this as the accepted limitation (Codex QE #1). */
88
+ export function checkpointInputHash(stage: string, parts: readonly unknown[]): string {
89
+ return fnv1a64(JSON.stringify([CKPT_SCHEMA_VERSION, stage, ...parts.map((p) => (p === undefined ? null : p))]));
90
+ }
91
+
92
+ export type ResumeMode = 'auto' | 'never' | 'force';
93
+
94
+ /** Normalize args.resume: anything but the two explicit strings means the default 'auto'. */
95
+ export function resumeMode(raw: unknown): ResumeMode {
96
+ return raw === 'never' ? 'never' : raw === 'force' ? 'force' : 'auto';
97
+ }
98
+
99
+ export interface ResumeDecision {
100
+ resume: boolean;
101
+ reason:
102
+ | 'resumed'
103
+ | 'resumed-force'
104
+ | 'mode-never'
105
+ | 'no-checkpoint'
106
+ | 'stale-input'
107
+ | 'artifact-missing';
108
+ }
109
+
110
+ /** The pure resume decision. 'auto' resumes only on (hash match AND every required artifact
111
+ * present); 'force' trusts the hash alone; 'never' always runs live. A STALE-INPUT hash NEVER
112
+ * resumes in any mode — force skips only the artifact probe, never the input check (a checkpoint
113
+ * for different inputs is a different feature). A malformed/null recorded result is treated as
114
+ * no-checkpoint (Codex QE #8 — a null result must not resume as a real one). */
115
+ export function decideCheckpointResume(opts: {
116
+ mode: ResumeMode;
117
+ entry: CheckpointEntry | undefined;
118
+ inputHash: string;
119
+ artifactRel: string | readonly string[] | null;
120
+ listing: ReadonlySet<string>;
121
+ }): ResumeDecision {
122
+ if (opts.mode === 'never') return { resume: false, reason: 'mode-never' };
123
+ if (!opts.entry || opts.entry.result === null || opts.entry.result === undefined) {
124
+ return { resume: false, reason: 'no-checkpoint' };
125
+ }
126
+ if (opts.entry.inputHash !== opts.inputHash) return { resume: false, reason: 'stale-input' };
127
+ if (opts.mode === 'force') return { resume: true, reason: 'resumed-force' };
128
+ const required = opts.artifactRel === null ? [] : (typeof opts.artifactRel === 'string' ? [opts.artifactRel] : opts.artifactRel);
129
+ for (const rel of required) {
130
+ if (!opts.listing.has(rel)) return { resume: false, reason: 'artifact-missing' };
131
+ }
132
+ return { resume: true, reason: 'resumed' };
133
+ }
134
+
135
+ /** Serialize one checkpoint line, or null when the result is null/oversize/unserializable —
136
+ * the caller logs the skip loudly; a missing checkpoint only costs a re-run, never corrupts.
137
+ * A null result is never persisted (Codex QE #8: it would later parse as a resumable entry). */
138
+ export function serializeCheckpoint(stage: string, inputHash: string, result: unknown): string | null {
139
+ if (result === null || result === undefined) return null;
140
+ let line: string;
141
+ try {
142
+ line = JSON.stringify({ stage, inputHash, result });
143
+ } catch {
144
+ return null;
145
+ }
146
+ if (typeof line !== 'string' || line.length > CHECKPOINT_MAX_RESULT_CHARS) return null;
147
+ return line;
148
+ }
149
+
150
+ export interface ParsedCheckpointRead {
151
+ entries: Record<string, CheckpointEntry>;
152
+ listing: Set<string>;
153
+ malformedLines: number;
154
+ }
155
+
156
+ /** Sentinel separating the checkpoint file body from the artifact listing in the single read-back
157
+ * command's stdout. */
158
+ export const CHECKPOINT_LS_SENTINEL = '---FA-CKPT-LS---';
159
+
160
+ /** Parse the read-back agent's stdout: JSONL entries (LAST occurrence of a stage wins — a re-run
161
+ * overwrites by append), then the sentinel ON ITS OWN LINE, then one artifact path per line
162
+ * (relative to the feature dir). The sentinel match is LINE-ANCHORED: JSON.stringify never emits
163
+ * literal newlines, so a sentinel string INSIDE a recorded result shares its line with JSON syntax
164
+ * and can never split the stream (Codex QE #10). Malformed JSONL lines are COUNTED, never silently
165
+ * ignored (corruption is named); entries with a null result are malformed, not resumable. */
166
+ export function parseCheckpointRead(text: string): ParsedCheckpointRead {
167
+ const out: ParsedCheckpointRead = { entries: {}, listing: new Set(), malformedLines: 0 };
168
+ const raw = String(text ?? '');
169
+ const lines = raw.split('\n');
170
+ const sentinelAt = lines.findIndex((l) => l.trim() === CHECKPOINT_LS_SENTINEL);
171
+ const body = sentinelAt === -1 ? lines : lines.slice(0, sentinelAt);
172
+ const ls = sentinelAt === -1 ? [] : lines.slice(sentinelAt + 1);
173
+ for (const line of body) {
174
+ const t = line.trim();
175
+ if (t === '') continue;
176
+ try {
177
+ const e = JSON.parse(t) as CheckpointEntry;
178
+ if (e && typeof e === 'object' && typeof e.stage === 'string' && typeof e.inputHash === 'string' && 'result' in e && e.result !== null && e.result !== undefined) {
179
+ out.entries[e.stage] = e;
180
+ } else {
181
+ // last-wins holds for BAD records too: a stage-identifiable null/invalid record ERASES the
182
+ // older entry for that stage instead of silently reactivating it (Codex QE r2 #6).
183
+ if (e && typeof e === 'object' && typeof (e as CheckpointEntry).stage === 'string') delete out.entries[(e as CheckpointEntry).stage];
184
+ out.malformedLines++;
185
+ }
186
+ } catch {
187
+ out.malformedLines++;
188
+ }
189
+ }
190
+ for (const line of ls) {
191
+ const t = line.trim();
192
+ if (t !== '') out.listing.add(t);
193
+ }
194
+ return out;
195
+ }
196
+
197
+ /** Single-quote shell escaping (the workflow's shq twin). */
198
+ export function shellQuote(s: string): string {
199
+ return "'" + String(s).replace(/'/g, "'\\''") + "'";
200
+ }
201
+
202
+ /** The one Bash command the read-back agent runs: checkpoint file body (absent file = empty),
203
+ * the sentinel, then the artifact listing as feature-dir-relative paths (find prints them with a
204
+ * leading ./ that sed strips). Never fails: every leg is || true. */
205
+ export function checkpointReadCmd(fdirAbs: string): string {
206
+ const q = shellQuote(fdirAbs);
207
+ return (
208
+ 'cat ' + q + '/.fa-state/checkpoints.jsonl 2>/dev/null || true; ' +
209
+ "echo '" + CHECKPOINT_LS_SENTINEL + "'; " +
210
+ 'cd ' + q + ' 2>/dev/null && find . -maxdepth 2 -type f 2>/dev/null | sed "s|^\\./||" || true'
211
+ );
212
+ }
213
+
214
+ /** The one Bash command the write agent runs: mkdir the state dir, then append ONE line. The line
215
+ * is single-quote-escaped as a whole — JSON.stringify output never contains literal newlines, so
216
+ * printf '%s\n' emits exactly one record. */
217
+ export function checkpointAppendCmd(fdirAbs: string, line: string): string {
218
+ const dir = shellQuote(fdirAbs + '/.fa-state');
219
+ const file = shellQuote(fdirAbs + '/.fa-state/checkpoints.jsonl');
220
+ return 'mkdir -p ' + dir + " && printf '%s\\n' " + shellQuote(line) + ' >> ' + file;
221
+ }
package/src/index.ts CHANGED
@@ -138,6 +138,36 @@ export { hookDecision, isFenced, isNewLine, ESCAPE_TEACHING } from './claim-chec
138
138
  export type { HookDecision, HookDecisionOpts } from './claim-check-hook-policy.js';
139
139
  export { step8ClaimGate } from './feature-adr-claim-gate.js';
140
140
  export type { Step8ClaimCounts, Step8ClaimGate } from './feature-adr-claim-gate.js';
141
+ export {
142
+ CHECKPOINT_STAGES,
143
+ STAGE_ARTIFACTS,
144
+ CHECKPOINT_MAX_RESULT_CHARS,
145
+ CHECKPOINT_LS_SENTINEL,
146
+ CKPT_SCHEMA_VERSION,
147
+ fnv1a,
148
+ fnv1a64,
149
+ checkpointInputHash,
150
+ resumeMode,
151
+ decideCheckpointResume,
152
+ serializeCheckpoint,
153
+ parseCheckpointRead,
154
+ checkpointReadCmd,
155
+ checkpointAppendCmd,
156
+ } from './feature-adr-checkpoints.js';
157
+ export type { CheckpointStage, CheckpointEntry, ResumeMode, ResumeDecision, ParsedCheckpointRead } from './feature-adr-checkpoints.js';
158
+ export {
159
+ REQE_SCHEMA,
160
+ REQE_SCOPE,
161
+ modelFamily,
162
+ shouldEmitReqeDebt,
163
+ buildReqeDebt,
164
+ parseReqeDebt,
165
+ buildReqeBrief,
166
+ extractReportGrade,
167
+ settleReqeDebt,
168
+ renderReqeList,
169
+ } from './reqe.js';
170
+ export type { ModelFamily, ReqeEmitDecision, ReqeDebt, ReqeBrief, ReqeSettlement } from './reqe.js';
141
171
  export {
142
172
  detectQueryLang,
143
173
  relevanceFloorFor,
package/src/reqe.ts ADDED
@@ -0,0 +1,243 @@
1
+ /**
2
+ * re-QE debt — the pure half of `dz reqe` (backlog 6b40e667, goal honest-quality).
3
+ *
4
+ * The cross-model-QE guard ("the model that writes code must not self-review") is an ADR-named
5
+ * safety property of the feature-adr pipeline. The usage-adaptive override consciously SUSPENDS it
6
+ * (FR-2.9): at >= threshold usage every remaining stage — including Step-8 QE — switches to Codex,
7
+ * so coder and reviewer become the SAME family. The rule doc said "run an independent re-QE after
8
+ * limits reset" — a human instruction on the weakest detection layer. This module turns it into a
9
+ * DEBT with a lifecycle:
10
+ *
11
+ * emit — the workflow records features/<slug>/.fa-state/reqe-due.json when Step-8 actually ran
12
+ * same-family under the override (not on every switch — a switch before Step 8 that
13
+ * still got cross-family QE creates no debt);
14
+ * list — `dz reqe` scans the debts; `dz usage` surfaces the count so the moment limits free up
15
+ * is the moment the debt is visible;
16
+ * brief — `dz reqe --slug <s>` prints a ready cross-family review brief (the OTHER family than
17
+ * the coder);
18
+ * settle — `dz reqe --slug <s> --done --report <file>` clears the debt FAIL-CLOSED: only against
19
+ * an existing, non-trivial report that names a grade; the settlement is appended to
20
+ * 08_qe_report.md so the artifact trail closes.
21
+ *
22
+ * HONEST SCOPE: nothing here re-runs QE automatically (no background spend — the human decides);
23
+ * the same-family CLAUDE belt fallback (codex unavailable) is out of scope by design — it is
24
+ * already logged loudly at run time and is not a limit-pressure artifact; runs from before this
25
+ * feature carry no marker and are UNDETERMINABLE, not debt-free.
26
+ */
27
+
28
+ export const REQE_SCHEMA = 'reqe-due-1';
29
+
30
+ export const REQE_SCOPE =
31
+ 'scope: debt is emitted only when Step-8 QE actually ran on the coder’s own family under the ' +
32
+ 'usage override; settling requires a graded cross-family report (fail-closed); nothing re-runs QE ' +
33
+ 'automatically.';
34
+
35
+ export type ModelFamily = 'claude' | 'openai';
36
+
37
+ /** Family classification shared with the workflow's acFamOf (codex/gpt/openai markers ⇒ openai).
38
+ * DELIBERATELY binary over the workflow's own CONTROLLED vocabulary (coderUsed ∈ claude | codex |
39
+ * codex-fallback; qeReviewerUsed ∈ claude | codex) — this is never fed arbitrary model ids, so the
40
+ * claude default is the correct reading of "not a codex marker", not a fail-open (Codex QE #11,
41
+ * accepted with this documentation). */
42
+ export function modelFamily(spec: string | null | undefined): ModelFamily {
43
+ return /codex|gpt|openai/i.test(String(spec ?? '')) ? 'openai' : 'claude';
44
+ }
45
+
46
+ export interface ReqeEmitDecision {
47
+ emit: boolean;
48
+ reason: string;
49
+ }
50
+
51
+ /** Emit iff the QE stage label carries the workflow's ' (usage-switched)' marker AND the reviewer
52
+ * family equals the coder family. Marker-only (cross-family survived the switch) or same-family
53
+ * WITHOUT the marker (the codex-unavailable Claude belt — degraded loudly at run time, not a
54
+ * limit-pressure artifact) both create NO debt. */
55
+ export function shouldEmitReqeDebt(input: {
56
+ coderUsed: string | null | undefined;
57
+ qeReviewerUsed: string | null | undefined;
58
+ qeModelLabel: string | null | undefined;
59
+ }): ReqeEmitDecision {
60
+ const switched = /\(usage-switched\)/.test(String(input.qeModelLabel ?? ''));
61
+ const coderFam = modelFamily(input.coderUsed);
62
+ const qeFam = modelFamily(input.qeReviewerUsed);
63
+ if (switched && coderFam === qeFam) {
64
+ return {
65
+ emit: true,
66
+ reason:
67
+ 'usage-switched self-review: Step-8 QE ran on the coder’s own family (' + coderFam +
68
+ ') under the limit override — the cross-model guard was suspended (FR-2.9)',
69
+ };
70
+ }
71
+ if (switched) return { emit: false, reason: 'usage-switched but QE stayed cross-family (' + qeFam + ' vs coder ' + coderFam + ')' };
72
+ if (coderFam === qeFam) return { emit: false, reason: 'same-family without the usage override (belt degrade — logged loudly at run time; out of re-QE-debt scope)' };
73
+ return { emit: false, reason: 'cross-family QE ran' };
74
+ }
75
+
76
+ export interface ReqeDebt {
77
+ schema: typeof REQE_SCHEMA;
78
+ slug: string;
79
+ coderFamily: ModelFamily;
80
+ qeFamily: ModelFamily;
81
+ qeGrade: string | null;
82
+ reason: string;
83
+ emittedAt: string | null;
84
+ /** The emitting run's identity (the workflow's qe inputHash). Lets a LATER run on the same slug
85
+ * emit a fresh debt even though an older settlement exists, while the SAME run's resume never
86
+ * re-opens a debt its settlement already covered (Codex QE round-2 #2). Optional: debts from
87
+ * before this field settle normally. */
88
+ runStamp?: string | null;
89
+ }
90
+
91
+ /** Build the debt record (the workflow serializes this; emittedAt is stamped by the writer agent's
92
+ * shell `date`, so the sandbox needs no Date). */
93
+ export function buildReqeDebt(input: {
94
+ slug: string;
95
+ coderUsed: string | null | undefined;
96
+ qeReviewerUsed: string | null | undefined;
97
+ qeGrade: string | null | undefined;
98
+ reason: string;
99
+ emittedAt?: string | null;
100
+ }): ReqeDebt {
101
+ return {
102
+ schema: REQE_SCHEMA,
103
+ slug: input.slug,
104
+ coderFamily: modelFamily(input.coderUsed),
105
+ qeFamily: modelFamily(input.qeReviewerUsed),
106
+ qeGrade: input.qeGrade == null || String(input.qeGrade).trim() === '' ? null : String(input.qeGrade).trim(),
107
+ reason: input.reason,
108
+ emittedAt: input.emittedAt ?? null,
109
+ };
110
+ }
111
+
112
+ /** Parse + validate a debt file's text. null = not a valid debt (the caller reports it as
113
+ * malformed — a corrupt debt file is NAMED, never silently dropped). */
114
+ export function parseReqeDebt(text: string): ReqeDebt | null {
115
+ let raw: unknown;
116
+ try {
117
+ raw = JSON.parse(String(text ?? ''));
118
+ } catch {
119
+ return null;
120
+ }
121
+ const d = raw as Partial<ReqeDebt>;
122
+ if (!d || typeof d !== 'object') return null;
123
+ if (d.schema !== REQE_SCHEMA) return null;
124
+ if (typeof d.slug !== 'string' || d.slug.trim() === '') return null;
125
+ if (d.coderFamily !== 'claude' && d.coderFamily !== 'openai') return null;
126
+ if (d.qeFamily !== 'claude' && d.qeFamily !== 'openai') return null;
127
+ if (typeof d.reason !== 'string' || d.reason.trim() === '') return null;
128
+ return {
129
+ schema: REQE_SCHEMA,
130
+ slug: d.slug,
131
+ coderFamily: d.coderFamily,
132
+ qeFamily: d.qeFamily,
133
+ qeGrade: typeof d.qeGrade === 'string' && d.qeGrade.trim() !== '' ? d.qeGrade.trim() : null,
134
+ reason: d.reason,
135
+ emittedAt: typeof d.emittedAt === 'string' && d.emittedAt.trim() !== '' ? d.emittedAt : null,
136
+ runStamp: typeof d.runStamp === 'string' && d.runStamp.trim() !== '' ? d.runStamp : null,
137
+ };
138
+ }
139
+
140
+ export interface ReqeBrief {
141
+ reviewFamily: ModelFamily;
142
+ header: string;
143
+ instructions: readonly string[];
144
+ codexCmdTemplate: string | null;
145
+ }
146
+
147
+ /** The ready-to-run cross-family review brief. Review family = the OTHER family than the CODER
148
+ * (reviewing with the other-than-reviewer family would let a codex-coded, codex-reviewed run be
149
+ * "re-reviewed" by codex again). */
150
+ export function buildReqeBrief(debt: ReqeDebt, artifactsDir: string): ReqeBrief {
151
+ const reviewFamily: ModelFamily = debt.coderFamily === 'openai' ? 'claude' : 'openai';
152
+ const files = [
153
+ artifactsDir + '/07_code_changes/change_manifest.md',
154
+ artifactsDir + '/08_qe_report.md',
155
+ artifactsDir + '/03_adr/',
156
+ ];
157
+ const instructions = [
158
+ 'Independent re-QE for "' + debt.slug + '": the recorded Step-8 review ran on the coder’s own family (' + debt.coderFamily + ') under the usage override' + (debt.qeGrade ? ' and graded ' + debt.qeGrade : '') + '.',
159
+ 'Review with the ' + reviewFamily.toUpperCase() + ' family (the OTHER family than the coder — the suspended guard, restored).',
160
+ 'Read: ' + files.join(' , ') + ' plus every file the change manifest lists.',
161
+ 'Adversarially verify: correctness, edge cases, the ADR-named load-bearing property HAS a test, and whether the same-family review missed anything.',
162
+ 'Output: GRADE A-F + numbered findings with file:line and severity; write the report to ' + artifactsDir + '/08b_reqe_report.md.',
163
+ 'Then settle the debt: dz reqe --slug ' + debt.slug + ' --done --report ' + artifactsDir + '/08b_reqe_report.md',
164
+ ];
165
+ const codexCmdTemplate = reviewFamily === 'openai'
166
+ ? 'codex exec -m <probed-id> -c model_reasoning_effort="high" --sandbox read-only "<the brief above>" < /dev/null # probe the id first: ids are account-specific'
167
+ : null;
168
+ return {
169
+ reviewFamily,
170
+ header: 're-QE brief for ' + debt.slug + ' (' + (debt.emittedAt ?? 'emitted: unknown') + ')',
171
+ instructions,
172
+ codexCmdTemplate,
173
+ };
174
+ }
175
+
176
+ export interface ReqeSettlement {
177
+ ok: boolean;
178
+ error: string | null;
179
+ grade: string | null;
180
+ epilogue: string | null;
181
+ }
182
+
183
+ /** Extract the verdict grade from a report, or null. LINE-ANCHORED and range-proof (Codex QE #7):
184
+ * the boilerplate phrase `GRADE A-F` must not read as grade A, so a letter followed by a dash and
185
+ * another grade letter is rejected; and the grade must head its line (a quoted "do not assign
186
+ * GRADE A" mid-paragraph is not a verdict). Conflicting distinct grades ⇒ null (ambiguous). */
187
+ export function extractReportGrade(text: string): string | null {
188
+ // the lookahead rejects RANGES in punctuation form (A-F, A/F) AND word form (A through F,
189
+ // A to F) — Codex QE round-2 #6: 'GRADE A through F' must not read as grade A
190
+ const matches = [...String(text ?? '').matchAll(/^\s*(?:\*{0,2}#{0,4}\s*)?GRADE\s*[:=—–-]?\s*([A-F])\b(?!\s*(?:[-–—/]|through|to|thru)\s*[A-F]\b)/gim)];
191
+ const distinct = new Set(matches.map((m) => (m[1] ?? '').toUpperCase()).filter((g) => g !== ''));
192
+ if (distinct.size !== 1) return null;
193
+ return [...distinct][0] ?? null;
194
+ }
195
+
196
+ /** FAIL-CLOSED settlement validation: the report must be non-trivial (>= 200 chars of substance)
197
+ * and must NAME exactly one line-anchored grade. A settlement that cannot cite its evidence is
198
+ * refused — clearing a debt against an empty file would re-open the exact hole this feature closes.
199
+ * HONEST LIMIT (documented, not hidden): the validator proves the settlement is PROCEDURALLY sound
200
+ * (a distinct, graded report exists); it cannot prove which model authored the text — attribution
201
+ * stays with the human running the brief. */
202
+ export function settleReqeDebt(debt: ReqeDebt, reportText: string, reportPath: string): ReqeSettlement {
203
+ const text = String(reportText ?? '');
204
+ if (text.trim().length < 200) {
205
+ return { ok: false, error: 'report too small to be a review (< 200 chars of substance) — refusing to settle', grade: null, epilogue: null };
206
+ }
207
+ const grade = extractReportGrade(text);
208
+ if (grade === null) {
209
+ return { ok: false, error: 'report names no unambiguous line-anchored GRADE (A-F) — text without exactly one verdict grade is not a verdict; refusing to settle', grade: null, epilogue: null };
210
+ }
211
+ const reviewFamily: ModelFamily = debt.coderFamily === 'openai' ? 'claude' : 'openai';
212
+ const epilogue = [
213
+ '',
214
+ '---',
215
+ '',
216
+ '## re-QE settlement (cross-model debt cleared)',
217
+ '',
218
+ 'The original Step-8 review ran on the coder’s own family (' + debt.coderFamily + ') under the',
219
+ 'usage override (' + debt.reason + '). An independent ' + reviewFamily.toUpperCase() + '-family re-QE was performed:',
220
+ '',
221
+ '- report: `' + reportPath + '`',
222
+ '- re-QE grade: **' + grade + '**' + (debt.qeGrade ? ' (same-family grade on record: ' + debt.qeGrade + ')' : ''),
223
+ '- settled via `dz reqe --done` (fail-closed: an existing graded report is required).',
224
+ '',
225
+ ].join('\n');
226
+ return { ok: true, error: null, grade, epilogue };
227
+ }
228
+
229
+ /** Render the debt list for `dz reqe` / the `dz usage` surfacing line. */
230
+ export function renderReqeList(debts: readonly ReqeDebt[], malformed: number): string[] {
231
+ const lines: string[] = [];
232
+ if (debts.length === 0 && malformed === 0) {
233
+ lines.push('dz reqe: no re-QE debts — every recorded run kept cross-model QE (or none used the usage override).');
234
+ } else {
235
+ lines.push('dz reqe — ' + debts.length + ' unsettled re-QE debt(s):');
236
+ for (const d of debts) {
237
+ lines.push(' ' + d.slug + ' coder=' + d.coderFamily + ' qe=' + d.qeFamily + (d.qeGrade ? ' grade=' + d.qeGrade : '') + (d.emittedAt ? ' ' + d.emittedAt : '') + ' → dz reqe --slug ' + d.slug);
238
+ }
239
+ }
240
+ if (malformed > 0) lines.push(' ' + malformed + ' malformed debt file(s) skipped (named, never silent).');
241
+ lines.push(REQE_SCOPE);
242
+ return lines;
243
+ }