@dzhechkov/harness-cli 0.3.258 → 0.3.260
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dz-manifest.json +8 -8
- package/README.md +70 -4
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +292 -8
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
- package/sbom.json +7 -7
- package/src/cli.ts +274 -7
package/.dz-manifest.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
{
|
|
11
11
|
"path": "README.md",
|
|
12
|
-
"sha256": "
|
|
12
|
+
"sha256": "6b3bcc252253732b9703f1eb43ac1f0e9f0599979b09d0fea3208932fd0f33a8"
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
"path": "coverage/coverage-final.json",
|
|
@@ -41,15 +41,15 @@
|
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
"path": "dist/cli.d.ts.map",
|
|
44
|
-
"sha256": "
|
|
44
|
+
"sha256": "4c5fa8c51cc350874413efceb0a2e4dce2f0c71f39914ddd8be425336a37ae2d"
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
"path": "dist/cli.js",
|
|
48
|
-
"sha256": "
|
|
48
|
+
"sha256": "a75034dfe70f0b57811dfad831366bd5c7995583a57db0f218621b026ac8d854"
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
51
|
"path": "dist/cli.js.map",
|
|
52
|
-
"sha256": "
|
|
52
|
+
"sha256": "dbe8fa418aedcb77dead3a374bba7cbf0fb5e305dd9a06002946a19604279684"
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
55
|
"path": "dist/index.d.ts",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
},
|
|
74
74
|
{
|
|
75
75
|
"path": "package.json",
|
|
76
|
-
"sha256": "
|
|
76
|
+
"sha256": "72858a1f0199832c7ad781f892b7b117ec980af33ff9598587e1ed1544046a35"
|
|
77
77
|
},
|
|
78
78
|
{
|
|
79
79
|
"path": "src/bin.ts",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
},
|
|
82
82
|
{
|
|
83
83
|
"path": "src/cli.ts",
|
|
84
|
-
"sha256": "
|
|
84
|
+
"sha256": "1481238a2fc3a60051f678dcf6b415f8052ce954a9e55ee7577d8c3b2261e062"
|
|
85
85
|
},
|
|
86
86
|
{
|
|
87
87
|
"path": "src/index.ts",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
},
|
|
90
90
|
{
|
|
91
91
|
"path": "test/cli.test.ts",
|
|
92
|
-
"sha256": "
|
|
92
|
+
"sha256": "24f2feb4c5c4fa939beb79cd658b03fbc7ce4bd87f15da563240abcb252c472c"
|
|
93
93
|
},
|
|
94
94
|
{
|
|
95
95
|
"path": "test/epoch-replay-cli.test.ts",
|
|
@@ -109,5 +109,5 @@
|
|
|
109
109
|
}
|
|
110
110
|
]
|
|
111
111
|
},
|
|
112
|
-
"signature": "
|
|
112
|
+
"signature": "uYnxiEzDxjwdXlp1SGaC0Zv2x/MW39skbUKw3+xpvnLcoNsEV4pp8kFD5q7urBvC87fC+lRKsIpatoXCNGzwBA=="
|
|
113
113
|
}
|
package/README.md
CHANGED
|
@@ -132,7 +132,7 @@ point any tool at (a skill is just Markdown). Dedicated targets are on the [road
|
|
|
132
132
|
|
|
133
133
|
## User Journey — from install to mastery
|
|
134
134
|
|
|
135
|
-
All
|
|
135
|
+
All 63 commands mapped to a real workflow:
|
|
136
136
|
|
|
137
137
|
```
|
|
138
138
|
DISCOVER → INSTALL → USE → CREATE → MAINTAIN → SHARE
|
|
@@ -604,6 +604,41 @@ ever enter a work order.
|
|
|
604
604
|
**When to use:** after `dz compounding` reports the replay as READY; before claiming that recall
|
|
605
605
|
"works"; and any time you want the claim re-checked as the corpus grows.
|
|
606
606
|
|
|
607
|
+
### Пересмотр после аварийного само-ревью — `dz reqe`
|
|
608
|
+
|
|
609
|
+
The feature-adr pipeline's cross-model guard says *the model that writes code must not review it*.
|
|
610
|
+
Under limit pressure (usage-adaptive `>=70%` switch) that guard is CONSCIOUSLY suspended: coder AND
|
|
611
|
+
Step-8 QE both run on Codex (FR-2.9 — a Claude reviewer is exactly the agent that dies at the
|
|
612
|
+
limit). The rule used to say "re-review manually after limits reset" — an instruction nobody
|
|
613
|
+
remembers. `dz reqe` turns it into a **debt with a lifecycle**: the run records
|
|
614
|
+
`features/<slug>/.fa-state/reqe-due.json`, `dz usage` surfaces the count the moment you check your
|
|
615
|
+
freed-up limits, and settlement is FAIL-CLOSED.
|
|
616
|
+
|
|
617
|
+
```bash
|
|
618
|
+
$ dz usage
|
|
619
|
+
usage: session ~12% (resets 03:00) · week ~41% (resets 06:00) · estimated
|
|
620
|
+
re-QE due: 1 usage-switched run(s) kept same-family QE — run `dz reqe` for the cross-family pass
|
|
621
|
+
|
|
622
|
+
$ dz reqe # the ledger
|
|
623
|
+
dz reqe — 1 unsettled re-QE debt(s):
|
|
624
|
+
add-x coder=openai qe=openai grade=B 2026-07-30T10:00:00Z → dz reqe --slug add-x
|
|
625
|
+
|
|
626
|
+
$ dz reqe --slug add-x # the ready cross-family review brief (the OTHER family than the coder)
|
|
627
|
+
$ dz reqe --slug add-x --done --report features/add-x/08b_reqe_report.md
|
|
628
|
+
dz reqe: debt settled: re-QE grade C (report …) — settlement appended to features/add-x/08_qe_report.md
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
**When to use:** any time `dz usage` prints a `re-QE due` line, or a feature-adr result carried
|
|
632
|
+
`reqeDue: true`. **Fail-closed settlement:** the report must exist, be non-trivial, and name exactly
|
|
633
|
+
one line-anchored `GRADE` (the boilerplate phrase `GRADE A-F` does not count); the run's own
|
|
634
|
+
`08_qe_report.md` can never settle its own debt (real-path AND inode compared — a hard link doesn't
|
|
635
|
+
fool it); the settlement epilogue lands in `08_qe_report.md` and the due-file rotates to
|
|
636
|
+
`reqe-settled.json` (evidence kept, never deleted). **Honest scope** (printed by the command):
|
|
637
|
+
nothing re-runs QE automatically, and the validator proves the settlement is procedurally sound —
|
|
638
|
+
which model authored the report stays with the human running the brief. RU: гард «кодер не ревьюит
|
|
639
|
+
сам себя» осознанно снимается под лимитом; `dz reqe` превращает инструкцию «перепроверь потом»
|
|
640
|
+
в долг на диске — виден в `dz usage`, гасится только настоящим кросс-семейным отчётом с грейдом.
|
|
641
|
+
|
|
607
642
|
### Usage estimates you can act on — `dz usage`
|
|
608
643
|
|
|
609
644
|
`dz usage` estimates how much of your Claude session (5h block) and week you have spent, from local
|
|
@@ -953,7 +988,7 @@ Get the whole set with `dz init --target claude-code --preset meta`, or pick one
|
|
|
953
988
|
|
|
954
989
|
> **A skill and its npx toolkit are not duplicates — they're a graduation.** Several skills (e.g. `feature-adr`, `design-thinking`) exist BOTH as a skill inside a `dz` preset AND as a standalone `npx` package. The preset's SKILL.md is **fully functional on its own** (the whole methodology — modules + references — travels with it, and it auto-activates by description), and it's the only way to compile that capability to the **non-Claude platforms** (Codex/OpenCode/Hermes/OpenClaude) via `dz`. The npx package adds **project-level runtime governance** around the same skill: a slash command, governance rules, a context shard, and (for feature-adr) reward-learning + `/harvest`. So: pick the **skill/preset** for a working capability across platforms; pick the **npx toolkit** when you want it as a governed, command-driven fixture of one project.
|
|
955
990
|
|
|
956
|
-
## All Commands (
|
|
991
|
+
## All Commands (63)
|
|
957
992
|
|
|
958
993
|
```
|
|
959
994
|
dz setup --target <name> [--preset <name>] [--select id,id,...] [--skills-dir <dir>] [--memory agentdb] [--no-memory] [--no-hooks] [--install-driver] [--force]
|
|
@@ -964,7 +999,7 @@ dz teach "<pattern>" [--reward <0-1>] [--domain <name>] [--type rule
|
|
|
964
999
|
dz teach --reinforce "<dzId-or-exact-text>" [--project <dir>] # bump an existing learned pattern instead of writing a near-duplicate
|
|
965
1000
|
dz teach --from-json <file> [--project <dir>] [--no-mirror] [--harmonize] # bulk-import a `dz recall --all --json` export; prints a harmonize dry-run advisory
|
|
966
1001
|
dz consolidate [--sessions-dir <dir>] [--project <dir>] [--no-mirror]
|
|
967
|
-
dz recall "<query>" [--limit <N>] [--semantic | --no-semantic] [--project <dir>] # hybrid lexical+vector when the vector tier is enabled
|
|
1002
|
+
dz recall "<query>" [--limit <N>] [--domain <name>] [--semantic | --no-semantic] [--project <dir>] # hybrid lexical+vector when the vector tier is enabled; --domain BOOSTS same-domain lessons (never filters)
|
|
968
1003
|
dz recall --all [--json] [--stats] # export the WHOLE learned store, or inspect learning stats/top uses
|
|
969
1004
|
dz recall --books "<query>" [--book <slug>] # digitized-book KUs; --book narrows to one book
|
|
970
1005
|
dz vector status [--project <dir>] [--json] # semantic tier: engine availability, mirrored vs lexical counts, pending queue
|
|
@@ -1017,6 +1052,7 @@ dz epoch-replay --emit [--project <dir>] [--limit <N>] [--seed <N>] [--margin <
|
|
|
1017
1052
|
dz epoch-replay --judge <filled-work-order.json> [--out <file>] [--json] # blind judge prompts from the filled plans — the file carries {id, prompt} ONLY (no assignment, no arm names); refuses an order whose digest or seed-derived assignment does not check out; half-pairs skipped WITH A REASON, on stdout
|
|
1018
1053
|
dz epoch-replay --score <judgments.json> --work-order <file> [--slice <name>] [--json] # un-blind against the VERIFIED pre-registered assignment; ONE paired binomial over DECISIVE pairs (ties excluded, reported) → SUPPORTED only when the lift interval (2p−1) lies entirely above zero; FALSIFIED only on harm or a passed non-superiority test (lift upper bound below the margin PRE-REGISTERED in the work order, default 0.05, at 10+ decisive pairs); else INCONCLUSIVE (min 5 decisive pairs). Refuses a forged work order, a --margin flag, or duplicate judgement ids; the verdict is data, not an exit code
|
|
1019
1054
|
dz score --slug <feature> [--project <dir>] [--json] # process scorecard for ONE feature-adr run, from its artifacts: ADR confirmation, discrimination proof, cross-model QE grade, live verification, README-first, learning loop, amendments — DESCRIPTIVE-ONLY (a low score exits 0); evidence lines are shown so the reader judges the heuristics
|
|
1055
|
+
dz reqe [--slug <feature> [--done --report <f>]] [--project <dir>] [--json] # the re-QE debt ledger: a usage-switched feature-adr run whose Step-8 QE ran on the coder's OWN family (cross-model guard suspended, FR-2.9) records a debt; list debts (also surfaced by dz usage), print the cross-family review brief, settle FAIL-CLOSED against an existing GRADED report (the run's own 08_qe_report.md — even hard-linked — can never settle its own debt); settlement lands in 08_qe_report.md, evidence rotates to reqe-settled.json
|
|
1020
1056
|
dz backlog <sub> add "<idea>" | list | show <id> | goals [--validate] | roulette [--seed n] [--commit <id>] | enrich <id> | jira <id> | harmonize [--apply] # brain-backed idea backlog: capture an idea → semantic dedup against past ideas/features via the REUSED agentdb vector engine (DUPLICATE≥0.92 / RELATED / NEW) + GoalMap alignment ("map+compass") → weighted seeded roulette picks one to work on → enrich STAGES an idea2prd hand-off → jira writes an auditable outbox via a configurable MCP adapter seam (jira-mcp|copilot-mcp|none). No 2nd vector store; without agentdb it degrades to exact-text dedup (honest)
|
|
1021
1057
|
dz sign --init --out <path> | --pack <dir> --key <path> # --init: generate the Ed25519 keypair (private OUTSIDE the repo, prints the public key for keys/dz.pub); else sign a pack's manifest + CycloneDX SBOM
|
|
1022
1058
|
dz sbom --pack <dir> [--out <file>] # emit the CycloneDX 1.5 SBOM for a pack standalone (file-level bill of materials); print to stdout or write to a file
|
|
@@ -2311,6 +2347,36 @@ guard, `--loc-cap <n>` tunes, default 700), **secret-scan** (the same high-signa
|
|
|
2311
2347
|
edit caps/paths/waivers in `guards.config.json`; the project-critic role should then *not* re-flag these
|
|
2312
2348
|
rules, only waivers without a reason.
|
|
2313
2349
|
|
|
2350
|
+
### Domain-scoped recall — `--domain <name>` (a boost, never a filter)
|
|
2351
|
+
|
|
2352
|
+
**When to use it.** One brain store, more than one *kind* of work. `dz teach --domain` has always
|
|
2353
|
+
recorded a domain, but until now the ranking ignored it — so medical-research lessons and coding
|
|
2354
|
+
lessons came back interleaved, and the dilution grew with the store.
|
|
2355
|
+
|
|
2356
|
+
```bash
|
|
2357
|
+
dz recall "signature" --limit 3 --domain security
|
|
2358
|
+
# dz recall "signature" — 3 hit(s), SQLite FTS5 + vector (agentdb) ranking
|
|
2359
|
+
# [0.90] (security) ⟨both⟩ Crypto anti-hallucination anti-pattern: verifying a signature …
|
|
2360
|
+
# [0.85] (publishing) ⟨both⟩ ⚠q 'Re-sign LAST' applies to EVERY signed pack a sweep touches …
|
|
2361
|
+
# [0.80] (testing) ⟨vector⟩ A structural registry-coverage test that only checks key-SET …
|
|
2362
|
+
# domain "security": 1 exact match(es); the order was already correct — nothing moved;
|
|
2363
|
+
# foreign-domain lessons kept (a boost, not a filter)
|
|
2364
|
+
|
|
2365
|
+
dz recall "signature" --limit 3 --domain health-research
|
|
2366
|
+
# domain "health-research": no lesson in this result carries it — order unchanged, nothing was hidden
|
|
2367
|
+
```
|
|
2368
|
+
|
|
2369
|
+
**Why a boost and not a filter.** Cross-domain transfer is the reason one shared store beats two
|
|
2370
|
+
separate ones: *"a reviewer's evidence needs the same execute-don't-describe discipline as your own
|
|
2371
|
+
claims"* was learned reviewing code and applies verbatim to medical sources. A filter would cut
|
|
2372
|
+
exactly those transfers. So matching hits move UP — at most **2 places** for an exact domain, **1**
|
|
2373
|
+
for a hierarchical relative (`health` ↔ `health-research`) — and nothing is ever dropped: the hit
|
|
2374
|
+
count with `--domain` equals the hit count without it.
|
|
2375
|
+
|
|
2376
|
+
The bound matters: a domain tag is a hint about relevance, not evidence of it, so it breaks near-ties
|
|
2377
|
+
instead of overruling lexical ranking. And the note is honest in both directions — it says when
|
|
2378
|
+
nothing matched, and it distinguishes *matched* from *actually moved*.
|
|
2379
|
+
|
|
2314
2380
|
### Semantic recall (vector tier)
|
|
2315
2381
|
|
|
2316
2382
|
`dz recall` is **hybrid** when the vector tier is available and **exactly the old lexical command** when it is not — enabling it never changes behavior for projects that skip it.
|
|
@@ -2729,7 +2795,7 @@ npx @dzhechkov/p-replicator init
|
|
|
2729
2795
|
|
|
2730
2796
|
## Status
|
|
2731
2797
|
|
|
2732
|
-
`v0.3.
|
|
2798
|
+
`v0.3.259` — published on npm. Also available as [Claude Plugin](#claude-plugin). Part of [DZ Harness Hub](https://github.com/djd1m/dz-harness-hub).
|
|
2733
2799
|
|
|
2734
2800
|
## Claude Plugin
|
|
2735
2801
|
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA8XH,2EAA2E;AAC3E,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,iBAAiB,CAAC;CAC5C;AAED,yFAAyF;AACzF,MAAM,MAAM,iBAAiB,GAAG,CAC9B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE,KACvD;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAk3O9E,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA8J5E"}
|
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @packageDocumentation
|
|
5
5
|
*/
|
|
6
|
-
import { chmodSync, closeSync, existsSync, fstatSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, readlinkSync, realpathSync, renameSync, rmdirSync, rmSync, statSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { appendFileSync, chmodSync, closeSync, existsSync, fstatSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, readlinkSync, realpathSync, renameSync, rmdirSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
7
7
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
import { execFileSync, execSync, spawn } from 'node:child_process';
|
|
@@ -15,7 +15,7 @@ assembleCandidates, renderPromotionReport, renderPromotionAdr, normalizePromotio
|
|
|
15
15
|
// Cold-vs-warm EPOCH RUNNER (feature epoch-replay) — orchestrates + scores, never calls a model.
|
|
16
16
|
replayableInstances, buildWorkOrder, buildJudgePrompts, unblindJudgments, verifyWorkOrder, isValidMargin, DIGEST_HONEST_SCOPE, scoreEpochReplay, generateMockOutcomes, renderEpochReplayResult, renderWorkOrderSummary, renderJudgePromptsSummary, WORK_ORDER_KIND, DEFAULT_MOCK_N, DEFAULT_MOCK_SEED, scoreRun, renderScorecard, renderCompoundingReport, readReinforcementState, readQuarantineState, registrationExitCode, renderRegistrationReport,
|
|
17
17
|
// Smart Backlog (feature smart-backlog) — goal-directed idea pipeline over the Brain vector engine.
|
|
18
|
-
readBacklogConfig, readIdeas, writeIdeas, ideaId, dedupIdea, readGoalMap, readGoalMapDetailed, parseEffort, ensureBacklogGitignored, isSafeId, alignIdea, mirrorIdeaVector, snapshotIdeas, spinRoulette, rankRoulette, seededRng, eligibleIdeas, stageEnrichment, buildJiraDraft, resolveJiraAdapter, makeBacklogIO, harmonizeBacklog, BACKLOG_BACKENDS, } from '@dzhechkov/harness-core';
|
|
18
|
+
readBacklogConfig, readIdeas, writeIdeas, ideaId, dedupIdea, readGoalMap, readGoalMapDetailed, parseEffort, ensureBacklogGitignored, isSafeId, alignIdea, mirrorIdeaVector, snapshotIdeas, spinRoulette, rankRoulette, seededRng, eligibleIdeas, stageEnrichment, buildJiraDraft, resolveJiraAdapter, makeBacklogIO, harmonizeBacklog, BACKLOG_BACKENDS, applyDomainBoost, countDisplacedByCut, renderDomainBoostNote, renderDomainCutNote, parseReqeDebt, buildReqeBrief, settleReqeDebt, renderReqeList, REQE_SCOPE, } from '@dzhechkov/harness-core';
|
|
19
19
|
import { getPreset, PRESET_NAMES } from '@dzhechkov/harness-presets';
|
|
20
20
|
import { scanGitHub, analyzeRepo, generateReport, deepAnalyze, scanAllSources, ScoutMemory } from '@dzhechkov/scout';
|
|
21
21
|
const USAGE = `dz - DZ cross-platform harness CLI
|
|
@@ -48,6 +48,7 @@ Usage:
|
|
|
48
48
|
dz epoch-replay --judge <filled-work-order.json> [--out <file>] (blind judge prompts from the filled plans)
|
|
49
49
|
dz epoch-replay --score <judgments.json> --work-order <file> [--slice <name>] [--json] (un-blind against the pre-registered assignment → SUPPORTED only when the two 95% Wilson CIs are DISJOINT, else FALSIFIED / INCONCLUSIVE)
|
|
50
50
|
dz score --slug <feature> [--project <dir>] [--json] (process scorecard for ONE feature-adr run, from its artifacts: ADR confirmation, discrimination, cross-model QE grade, live verification, README-first, learning loop, amendments — descriptive-only, a low score exits 0)
|
|
51
|
+
dz reqe [--slug <feature> [--done --report <f>]] [--json] (the re-QE debt ledger: a usage-switched run whose Step-8 QE ran on the coder's OWN family records a debt; list debts, print the cross-family review brief, settle FAIL-CLOSED against a graded report — the settlement lands in 08_qe_report.md)
|
|
51
52
|
dz backlog add "<idea>" [--effort 1-5] [--proposal <text>] [--dry-run] [--project <dir>] [--json] (capture an idea: semantic dedup against existing ideas via the Brain vector engine (DUPLICATE>=0.92 merges, RELATED links, NEW creates) + GoalMap alignment; --dry-run classifies without writing)
|
|
52
53
|
dz backlog list [--status <s>] [--goal <id>] [--project <dir>] [--json] (list captured ideas, filterable by status/goal)
|
|
53
54
|
dz backlog show <id> [--project <dir>] [--json] (full record for one idea)
|
|
@@ -60,7 +61,7 @@ Usage:
|
|
|
60
61
|
dz teach "<pattern>" [--reward <0-1>] [--domain <name>] [--type rule|success-pattern|lesson-learned] [--project <dir>] [--no-mirror] (--project pins the learned store to <dir>/.dz, not the cwd — pin to a canonical brain)
|
|
61
62
|
dz teach --from-json <file> [--project <dir>] [--no-mirror] (bulk-import a 'dz recall --all --json' export — share a learned store across machines)
|
|
62
63
|
dz consolidate [--sessions-dir <dir>] [--project <dir>] [--no-mirror] [--prune-noise [--apply]] [--prune-quarantine [--apply]] (both prunes: DRY-RUN by default; --apply snapshots then deletes; prune-quarantine = expired unproven lessons ONLY, never coupled to noise)
|
|
63
|
-
dz recall "<query>" [--limit <N>] [--semantic | --no-semantic] [--books [--book <slug>]] [--project <dir>] | dz recall --all [--json] | dz recall --usage [--json] | dz recall --forget <dzId>[,<dzId>] [--apply] | dz recall --promote <dzId>[,<dzId>] [--apply] (forget/promote: dry-run default; forget snapshots before removing; promote lifts lesson-quarantine)
|
|
64
|
+
dz recall "<query>" [--limit <N>] [--domain <name>] [--semantic | --no-semantic] [--books [--book <slug>]] [--project <dir>] | dz recall --all [--json] | dz recall --usage [--json] | dz recall --forget <dzId>[,<dzId>] [--apply] | dz recall --promote <dzId>[,<dzId>] [--apply] (--domain <name> BOOSTS lessons of that domain without dropping foreign ones — a shared store keeps its cross-domain transfers; forget/promote: dry-run default; forget snapshots before removing; promote lifts lesson-quarantine)
|
|
64
65
|
dz vector status [--project <dir>] [--json] (semantic tier: engine, mirrored vs lexical counts, pending queue)
|
|
65
66
|
dz vector reindex [--project <dir>] [--json] (snapshot, re-embed learned-pattern vectors, stamp current model)
|
|
66
67
|
dz vector export <path> [--project <dir>] (portable VECTOR form (.rvf, opt-in RVF engine); patterns ship via recall --all --json)
|
|
@@ -1227,11 +1228,26 @@ function cmdUsage(options, optionLists, flags, cwd, write) {
|
|
|
1227
1228
|
};
|
|
1228
1229
|
if (hasModelLimits && u.weeklyByModel !== undefined)
|
|
1229
1230
|
payload.weeklyByModel = u.weeklyByModel;
|
|
1231
|
+
// re-QE debt surfacing (backlog 6b40e667 — QE #9: the json contract must carry the debt too,
|
|
1232
|
+
// a probe is exactly the consumer that needs it). The field appears ONLY when a debt exists,
|
|
1233
|
+
// so the zero-debt contract stays byte-identical to the pinned legacy shape. Best-effort.
|
|
1234
|
+
try {
|
|
1235
|
+
const reqeCount = scanReqeDebts(resolve(cwd, options.get('project') ?? '.')).debts.length;
|
|
1236
|
+
if (reqeCount > 0)
|
|
1237
|
+
payload.reqeDue = reqeCount;
|
|
1238
|
+
}
|
|
1239
|
+
catch { /* advisory only */ }
|
|
1230
1240
|
write(JSON.stringify(payload));
|
|
1231
1241
|
return 0;
|
|
1232
1242
|
}
|
|
1233
1243
|
if (u.sessionPct === null && u.weeklyPct === null) {
|
|
1234
1244
|
write('usage: unconfigured — set memory.usage.sessionTokenLimit / weeklyTokenLimit in .dz/config.json (percentages are ESTIMATES calibrated from observed exhaustion)');
|
|
1245
|
+
try {
|
|
1246
|
+
const reqe = scanReqeDebts(resolve(cwd, options.get('project') ?? '.'));
|
|
1247
|
+
if (reqe.debts.length > 0)
|
|
1248
|
+
write('re-QE due: ' + reqe.debts.length + ' usage-switched run(s) kept same-family QE — run `dz reqe` for the cross-family pass');
|
|
1249
|
+
}
|
|
1250
|
+
catch { /* advisory only */ }
|
|
1235
1251
|
return 0;
|
|
1236
1252
|
}
|
|
1237
1253
|
// Compact human line — a short HH:MM / weekday hint on the resets, best-effort.
|
|
@@ -1249,6 +1265,14 @@ function cmdUsage(options, optionLists, flags, cwd, write) {
|
|
|
1249
1265
|
const binding = hasModelLimits && u.weeklyBindingModel !== undefined ? ' ' + u.weeklyBindingModel + '-bound' : '';
|
|
1250
1266
|
const w = u.weeklyPct === null ? 'n/a' : '~' + u.weeklyPct + '%' + binding;
|
|
1251
1267
|
write('usage: session ' + s + ' (resets ' + clock(u.sessionResetsAt) + ') · week ' + w + ' (resets ' + clock(u.weeklyResetsAt) + ') · estimated');
|
|
1268
|
+
// re-QE debt surfacing (backlog 6b40e667): the moment someone checks usage is the moment a
|
|
1269
|
+
// usage-switched self-review debt should be visible. Best-effort — never breaks the contract.
|
|
1270
|
+
try {
|
|
1271
|
+
const reqe = scanReqeDebts(resolve(cwd, options.get('project') ?? '.'));
|
|
1272
|
+
if (reqe.debts.length > 0)
|
|
1273
|
+
write('re-QE due: ' + reqe.debts.length + ' usage-switched run(s) kept same-family QE — run `dz reqe` for the cross-family pass');
|
|
1274
|
+
}
|
|
1275
|
+
catch { /* advisory only */ }
|
|
1252
1276
|
return 0;
|
|
1253
1277
|
}
|
|
1254
1278
|
catch {
|
|
@@ -1818,13 +1842,30 @@ async function cmdRecall(options, flags, cwd, write) {
|
|
|
1818
1842
|
: flags.has('no-semantic') || flags.has('lexical')
|
|
1819
1843
|
? 'lexical'
|
|
1820
1844
|
: 'hybrid';
|
|
1821
|
-
const
|
|
1845
|
+
const wantedDomain = options.get('domain');
|
|
1846
|
+
// OVER-FETCH before boosting (Codex QE #5): the boost used to run on hits ALREADY
|
|
1847
|
+
// truncated to `limit`, so an exact-domain lesson sitting at rank limit+1 could
|
|
1848
|
+
// never receive its promised lift — the feature was weakest in exactly the case it
|
|
1849
|
+
// exists for (foreign-domain dilution pushing a relevant lesson just past the cut).
|
|
1850
|
+
// Fetch a bounded surplus, re-rank, then trim to the limit the caller asked for.
|
|
1851
|
+
const fetchLimit = wantedDomain !== undefined ? Math.min(limit * 3, limit + 20) : limit;
|
|
1852
|
+
const result = await recallHybrid(projectRoot, query, { limit: fetchLimit, mode });
|
|
1822
1853
|
if (mode === 'semantic' && result.vectorEngine === 'none') {
|
|
1823
1854
|
// --semantic is an explicit ask — degrading it silently would be dishonest (FR-3).
|
|
1824
1855
|
write(`dz recall --semantic: ${result.vectorReason ?? 'no vector engine available — run: dz setup --memory agentdb'}`);
|
|
1825
1856
|
return 1;
|
|
1826
1857
|
}
|
|
1827
|
-
|
|
1858
|
+
// Domain-aware re-ranking (health-advisor slice H): `--domain <name>` lifts lessons
|
|
1859
|
+
// tagged with that domain WITHOUT dropping foreign ones — a boost, not a filter, so a
|
|
1860
|
+
// shared store keeps the cross-domain transfers that make it worth more than two stores.
|
|
1861
|
+
const boost = wantedDomain !== undefined ? applyDomainBoost(result.hits, wantedDomain) : null;
|
|
1862
|
+
const hits = (boost ? boost.hits : result.hits).slice(0, limit);
|
|
1863
|
+
// The boost never drops a hit, but the CUT still can: promoting a match into the top
|
|
1864
|
+
// `limit` pushes the last one out, so a lesson visible WITHOUT --domain can vanish
|
|
1865
|
+
// WITH it. Cross-model review called this out as a lie by omission — the note said
|
|
1866
|
+
// "foreign-domain lessons kept" about the pre-cut list while the printed list was
|
|
1867
|
+
// missing one. Count it and say so; the reader can act on it (raise --limit).
|
|
1868
|
+
const displaced = boost !== null ? countDisplacedByCut(result.hits, boost.hits, limit) : 0;
|
|
1828
1869
|
if (asJson) {
|
|
1829
1870
|
// Portable contract UNCHANGED (I-7/AC-6): a plain PatternRecord[] — round-trips through
|
|
1830
1871
|
// `dz teach --from-json` regardless of which backend ranked each hit.
|
|
@@ -1834,6 +1875,11 @@ async function cmdRecall(options, flags, cwd, write) {
|
|
|
1834
1875
|
if (hits.length === 0) {
|
|
1835
1876
|
write(`dz recall "${query}"`);
|
|
1836
1877
|
write(` No matching patterns (teach some with \`dz teach\`, or consolidate sessions).`);
|
|
1878
|
+
// The domain note must print here too (Codex QE #10): a --domain run with zero hits
|
|
1879
|
+
// silently said nothing about the domain, so the reader could not tell whether the
|
|
1880
|
+
// boost had been applied and found nothing, or had not run at all.
|
|
1881
|
+
if (boost !== null && wantedDomain !== undefined)
|
|
1882
|
+
write(renderDomainBoostNote(boost, wantedDomain));
|
|
1837
1883
|
return 0;
|
|
1838
1884
|
}
|
|
1839
1885
|
const vectorOn = result.vectorEngine !== 'none' && result.vectorError === undefined && mode !== 'lexical';
|
|
@@ -1846,12 +1892,26 @@ async function cmdRecall(options, flags, cwd, write) {
|
|
|
1846
1892
|
const qTag = h.quarantined === true ? ' ⚠q' : '';
|
|
1847
1893
|
if (h.quarantined === true)
|
|
1848
1894
|
sawQuarantined = true;
|
|
1849
|
-
|
|
1895
|
+
// ONE line per hit, always. A lesson may contain newlines, and printing them raw
|
|
1896
|
+
// let stored CONTENT forge lines that look like the tool's own output — including
|
|
1897
|
+
// the domain-boost note that learning_bridge.py reads as a capability probe. Any
|
|
1898
|
+
// consumer that parses this output line-wise has the same exposure, so the fix
|
|
1899
|
+
// belongs at the point of rendering rather than in each reader.
|
|
1900
|
+
const oneLine = h.pattern.pattern.replace(/[\r\n]+/g, ' ⏎ ').slice(0, 80);
|
|
1901
|
+
write(` [${h.pattern.reward.toFixed(2)}] (${h.pattern.domain})${backendTag}${qTag} ${oneLine}`);
|
|
1850
1902
|
}
|
|
1851
1903
|
if (sawQuarantined) {
|
|
1852
1904
|
// The loop stays VISIBLE (ADR D2): a quarantined hit is shown, marked, and explained.
|
|
1853
1905
|
write(' ⚠q = quarantined (unproven hypothesis, rank damped) — confirm with dz teach --reinforce, or dz recall --promote <dzId> --apply');
|
|
1854
1906
|
}
|
|
1907
|
+
if (boost !== null && wantedDomain !== undefined) {
|
|
1908
|
+
// Say what the boost did — INCLUDING when it did nothing. A silent reorder would
|
|
1909
|
+
// let the reader believe the ranking was domain-aware when it had no match to work with.
|
|
1910
|
+
write(renderDomainBoostNote(boost, wantedDomain));
|
|
1911
|
+
const cutNote = renderDomainCutNote(displaced, limit);
|
|
1912
|
+
if (cutNote !== '')
|
|
1913
|
+
write(cutNote);
|
|
1914
|
+
}
|
|
1855
1915
|
if (result.vectorError !== undefined && mode !== 'lexical') {
|
|
1856
1916
|
// Engine present but the semantic leg failed/timed out — one honest line, exit 0 (05 §2.3).
|
|
1857
1917
|
write(` ℹ vector search degraded: ${result.vectorError} — showing lexical ranking`);
|
|
@@ -5926,6 +5986,213 @@ function nameFor(t, outcome) {
|
|
|
5926
5986
|
* `dz score --slug <feature>` — score a feature-adr RUN's process discipline from its artifacts
|
|
5927
5987
|
* (feature dz-score, Reading C). Descriptive-only: exit 0 on any score; non-zero only on usage errors.
|
|
5928
5988
|
*/
|
|
5989
|
+
/** Scan features/<slug>/.fa-state/reqe-due.json under a project root. Symlinked ANCESTOR dirs are
|
|
5990
|
+
* skipped too, not just the leaf (Codex QE #5 — a symlinked feature dir could smuggle an outside
|
|
5991
|
+
* debt); an oversized or non-regular due-file counts as MALFORMED, never a silent skip (QE #8);
|
|
5992
|
+
* a debt whose embedded slug differs from its directory is MALFORMED — identity is the directory,
|
|
5993
|
+
* the JSON only confirms it (QE #4: an embedded foreign slug must not redirect settlement). */
|
|
5994
|
+
function scanReqeDebts(root) {
|
|
5995
|
+
const out = [];
|
|
5996
|
+
let malformed = 0;
|
|
5997
|
+
const featuresDir = join(root, 'features');
|
|
5998
|
+
let slugs = [];
|
|
5999
|
+
try {
|
|
6000
|
+
if (lstatSync(featuresDir).isSymbolicLink())
|
|
6001
|
+
return { debts: [], malformed: 0 }; // r2 #4: features/ itself
|
|
6002
|
+
slugs = readdirSync(featuresDir);
|
|
6003
|
+
}
|
|
6004
|
+
catch {
|
|
6005
|
+
return { debts: [], malformed: 0 };
|
|
6006
|
+
}
|
|
6007
|
+
for (const slug of slugs.sort()) {
|
|
6008
|
+
const dir = join(featuresDir, slug);
|
|
6009
|
+
const stateDir = join(dir, '.fa-state');
|
|
6010
|
+
const duePath = join(stateDir, 'reqe-due.json');
|
|
6011
|
+
try {
|
|
6012
|
+
if (lstatSync(dir).isSymbolicLink() || lstatSync(stateDir).isSymbolicLink())
|
|
6013
|
+
continue;
|
|
6014
|
+
}
|
|
6015
|
+
catch {
|
|
6016
|
+
continue; // no feature dir / no state dir — nothing to scan
|
|
6017
|
+
}
|
|
6018
|
+
let st;
|
|
6019
|
+
try {
|
|
6020
|
+
st = lstatSync(duePath);
|
|
6021
|
+
}
|
|
6022
|
+
catch {
|
|
6023
|
+
continue; // no due-file — the common, silent case
|
|
6024
|
+
}
|
|
6025
|
+
if (!st.isFile() || st.size > 64 * 1024) {
|
|
6026
|
+
malformed++; // exists but is not a plain small file — named, never silently dropped
|
|
6027
|
+
continue;
|
|
6028
|
+
}
|
|
6029
|
+
try {
|
|
6030
|
+
const debt = parseReqeDebt(readFileSync(duePath, 'utf-8'));
|
|
6031
|
+
if (debt && debt.slug === slug)
|
|
6032
|
+
out.push({ debt, duePath, dir });
|
|
6033
|
+
else
|
|
6034
|
+
malformed++;
|
|
6035
|
+
}
|
|
6036
|
+
catch {
|
|
6037
|
+
malformed++;
|
|
6038
|
+
}
|
|
6039
|
+
}
|
|
6040
|
+
return { debts: out, malformed };
|
|
6041
|
+
}
|
|
6042
|
+
/**
|
|
6043
|
+
* `dz reqe` — the re-QE debt ledger (backlog 6b40e667): list usage-switched same-family QE debts,
|
|
6044
|
+
* print the cross-family review brief, settle FAIL-CLOSED against a graded report.
|
|
6045
|
+
*/
|
|
6046
|
+
function cmdReqe(options, flags, cwd, write) {
|
|
6047
|
+
const json = flags.has('json');
|
|
6048
|
+
if (flags.has('help')) {
|
|
6049
|
+
const usage = 'dz reqe [--slug <feature> [--done --report <file>]] [--project <dir>] [--json]';
|
|
6050
|
+
if (json)
|
|
6051
|
+
write(JSON.stringify({ help: usage, exitCode: 0 }));
|
|
6052
|
+
else {
|
|
6053
|
+
write(usage + ' — the re-QE debt ledger');
|
|
6054
|
+
write(' (no args) list unsettled debts (runs whose Step-8 QE ran on the coder’s own family under the usage override)');
|
|
6055
|
+
write(' --slug <s> print the ready cross-family review brief for one debt');
|
|
6056
|
+
write(' --slug <s> --done --report <file> settle the debt — FAIL-CLOSED: requires an existing, non-trivial, GRADED report; appends the settlement to 08_qe_report.md');
|
|
6057
|
+
write(' ' + REQE_SCOPE);
|
|
6058
|
+
}
|
|
6059
|
+
return 0;
|
|
6060
|
+
}
|
|
6061
|
+
for (const flag of flags) {
|
|
6062
|
+
if (!new Set(['json', 'help', 'done']).has(flag)) {
|
|
6063
|
+
write(json ? JSON.stringify({ error: `unknown option --${flag}`, exitCode: 1 }) : `dz reqe: unknown option --${flag}\n allowed: --slug <feature>, --done, --report <file>, --project <dir>, --json`);
|
|
6064
|
+
return 1;
|
|
6065
|
+
}
|
|
6066
|
+
}
|
|
6067
|
+
for (const key of options.keys()) {
|
|
6068
|
+
if (key.startsWith('_positional_') || !new Set(['slug', 'report', 'project']).has(key)) {
|
|
6069
|
+
const what = key.startsWith('_positional_') ? `unexpected argument "${options.get(key)}"` : `unknown option --${key}`;
|
|
6070
|
+
write(json ? JSON.stringify({ error: what, exitCode: 1 }) : `dz reqe: ${what}`);
|
|
6071
|
+
return 1;
|
|
6072
|
+
}
|
|
6073
|
+
}
|
|
6074
|
+
const root = resolve(cwd, options.get('project') ?? '.');
|
|
6075
|
+
const slug = options.get('slug') ?? '';
|
|
6076
|
+
if (slug !== '' && (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(slug) || slug.includes('..'))) {
|
|
6077
|
+
write(json ? JSON.stringify({ error: 'a plain --slug <feature> is required (no path separators)', exitCode: 1 }) : 'dz reqe: a plain --slug <feature> is required (no path separators)');
|
|
6078
|
+
return 1;
|
|
6079
|
+
}
|
|
6080
|
+
const { debts, malformed } = scanReqeDebts(root);
|
|
6081
|
+
if (slug === '') {
|
|
6082
|
+
if (flags.has('done')) {
|
|
6083
|
+
write(json ? JSON.stringify({ error: '--done requires --slug', exitCode: 1 }) : 'dz reqe: --done requires --slug <feature>');
|
|
6084
|
+
return 1;
|
|
6085
|
+
}
|
|
6086
|
+
if (json)
|
|
6087
|
+
write(JSON.stringify({ debts: debts.map((d) => d.debt), malformed, scope: REQE_SCOPE, exitCode: 0 }));
|
|
6088
|
+
else
|
|
6089
|
+
for (const line of renderReqeList(debts.map((d) => d.debt), malformed))
|
|
6090
|
+
write(line);
|
|
6091
|
+
return 0;
|
|
6092
|
+
}
|
|
6093
|
+
const hit = debts.find((d) => d.debt.slug === slug) ?? null;
|
|
6094
|
+
if (!hit) {
|
|
6095
|
+
// distinguish "no debt file" from "malformed debt file" — a corrupt debt must be named
|
|
6096
|
+
const duePath = join(root, 'features', slug, '.fa-state', 'reqe-due.json');
|
|
6097
|
+
const exists = existsSync(duePath);
|
|
6098
|
+
const msg = exists
|
|
6099
|
+
? `debt file at features/${slug}/.fa-state/reqe-due.json is MALFORMED — inspect it manually (a corrupt debt is named, never auto-cleared)`
|
|
6100
|
+
: `no re-QE debt recorded for "${slug}"`;
|
|
6101
|
+
write(json ? JSON.stringify({ error: msg, exitCode: 1 }) : `dz reqe: ${msg}`);
|
|
6102
|
+
return 1;
|
|
6103
|
+
}
|
|
6104
|
+
if (!flags.has('done')) {
|
|
6105
|
+
const brief = buildReqeBrief(hit.debt, join('features', slug));
|
|
6106
|
+
if (json)
|
|
6107
|
+
write(JSON.stringify({ debt: hit.debt, brief, exitCode: 0 }));
|
|
6108
|
+
else {
|
|
6109
|
+
write(brief.header);
|
|
6110
|
+
for (const line of brief.instructions)
|
|
6111
|
+
write(' ' + line);
|
|
6112
|
+
if (brief.codexCmdTemplate)
|
|
6113
|
+
write(' codex template: ' + brief.codexCmdTemplate);
|
|
6114
|
+
write(' ' + REQE_SCOPE);
|
|
6115
|
+
}
|
|
6116
|
+
return 0;
|
|
6117
|
+
}
|
|
6118
|
+
// --done: FAIL-CLOSED settlement
|
|
6119
|
+
const reportOpt = options.get('report') ?? '';
|
|
6120
|
+
if (reportOpt === '') {
|
|
6121
|
+
write(json ? JSON.stringify({ error: '--done requires --report <file> (the cross-family re-QE report)', exitCode: 1 }) : 'dz reqe: --done requires --report <file>');
|
|
6122
|
+
return 1;
|
|
6123
|
+
}
|
|
6124
|
+
const reportPath = resolve(root, reportOpt);
|
|
6125
|
+
const qeReportPath = join(hit.dir, '08_qe_report.md');
|
|
6126
|
+
// QE #1: the run's OWN same-family Step-8 report must never settle its own debt — the exact
|
|
6127
|
+
// laundering this ledger exists to prevent. Compared by REAL path so a symlink cannot alias it.
|
|
6128
|
+
try {
|
|
6129
|
+
const sameReal = realpathSync(reportPath) === realpathSync(qeReportPath);
|
|
6130
|
+
// r2 #1: a HARD LINK to the same report has a different path but the same inode — compare
|
|
6131
|
+
// dev+ino, not just resolved paths
|
|
6132
|
+
const rSt = statSync(reportPath);
|
|
6133
|
+
const qSt = statSync(qeReportPath);
|
|
6134
|
+
const sameInode = rSt.dev === qSt.dev && rSt.ino === qSt.ino;
|
|
6135
|
+
if (sameReal || sameInode) {
|
|
6136
|
+
write(json ? JSON.stringify({ error: 'the report IS this run’s own 08_qe_report.md (same file/inode) — the same-family review cannot settle its own debt; provide the independent cross-family report', exitCode: 1 }) : 'dz reqe: the report IS this run’s own 08_qe_report.md (same file/inode) — the same-family review cannot settle its own debt (fail-closed)');
|
|
6137
|
+
return 1;
|
|
6138
|
+
}
|
|
6139
|
+
}
|
|
6140
|
+
catch { /* one of the two does not resolve — the reads below decide */ }
|
|
6141
|
+
let reportText = '';
|
|
6142
|
+
try {
|
|
6143
|
+
const st = lstatSync(reportPath);
|
|
6144
|
+
if (!st.isFile())
|
|
6145
|
+
throw new Error('not a regular file'); // symlinked report refused (QE #5)
|
|
6146
|
+
reportText = readFileSync(reportPath, 'utf-8');
|
|
6147
|
+
}
|
|
6148
|
+
catch {
|
|
6149
|
+
write(json ? JSON.stringify({ error: `report not readable at ${reportOpt} (must be a regular file, not a symlink) — refusing to settle`, exitCode: 1 }) : `dz reqe: report not readable at ${reportOpt} (must be a regular file, not a symlink) — refusing to settle (fail-closed)`);
|
|
6150
|
+
return 1;
|
|
6151
|
+
}
|
|
6152
|
+
const settlement = settleReqeDebt(hit.debt, reportText, reportOpt);
|
|
6153
|
+
if (!settlement.ok || settlement.epilogue === null) {
|
|
6154
|
+
write(json ? JSON.stringify({ error: settlement.error, exitCode: 1 }) : `dz reqe: ${settlement.error}`);
|
|
6155
|
+
return 1;
|
|
6156
|
+
}
|
|
6157
|
+
// QE #6 ordering: rotate the debt FIRST, append the human-readable epilogue LAST — a crash may
|
|
6158
|
+
// leave a settled debt without its epilogue (recoverable from reqe-settled.json), never an
|
|
6159
|
+
// epilogue claiming clearance while the debt is still live.
|
|
6160
|
+
const settledBase = join(hit.dir, '.fa-state', 'reqe-settled');
|
|
6161
|
+
let settledPath = settledBase + '.json';
|
|
6162
|
+
for (let n = 2; existsSync(settledPath); n++)
|
|
6163
|
+
settledPath = settledBase + '-' + n + '.json'; // never overwrite prior evidence
|
|
6164
|
+
try {
|
|
6165
|
+
const settled = { ...hit.debt, settledGrade: settlement.grade, settledReport: reportOpt };
|
|
6166
|
+
writeFileSync(settledPath, JSON.stringify(settled) + '\n', { flag: 'wx' }); // wx: never through a planted symlink
|
|
6167
|
+
try {
|
|
6168
|
+
unlinkSync(hit.duePath);
|
|
6169
|
+
}
|
|
6170
|
+
catch (e) {
|
|
6171
|
+
// r2 #5: a failed unlink must not leave a settled marker beside a live debt — roll back
|
|
6172
|
+
try {
|
|
6173
|
+
unlinkSync(settledPath);
|
|
6174
|
+
}
|
|
6175
|
+
catch { /* best-effort rollback */ }
|
|
6176
|
+
throw e;
|
|
6177
|
+
}
|
|
6178
|
+
}
|
|
6179
|
+
catch {
|
|
6180
|
+
write(json ? JSON.stringify({ error: 'could not rotate the debt file — debt NOT cleared, nothing was appended', exitCode: 1 }) : 'dz reqe: could not rotate the debt file — debt NOT cleared, nothing was appended (fail-closed)');
|
|
6181
|
+
return 1;
|
|
6182
|
+
}
|
|
6183
|
+
try {
|
|
6184
|
+
if (lstatSync(qeReportPath).isSymbolicLink())
|
|
6185
|
+
throw new Error('symlinked 08_qe_report.md');
|
|
6186
|
+
appendFileSync(qeReportPath, settlement.epilogue);
|
|
6187
|
+
}
|
|
6188
|
+
catch {
|
|
6189
|
+
write(json ? JSON.stringify({ settled: true, grade: settlement.grade, warning: 'debt cleared (see .fa-state/reqe-settled*.json) but the epilogue could NOT be appended to 08_qe_report.md — append it manually', exitCode: 0 }) : 'dz reqe: debt cleared (evidence in .fa-state/) but the epilogue could NOT be appended to 08_qe_report.md — append it manually');
|
|
6190
|
+
return 0;
|
|
6191
|
+
}
|
|
6192
|
+
const okMsg = `debt settled: re-QE grade ${settlement.grade} (report ${reportOpt}) — settlement appended to features/${slug}/08_qe_report.md`;
|
|
6193
|
+
write(json ? JSON.stringify({ settled: true, grade: settlement.grade, exitCode: 0 }) : `dz reqe: ${okMsg}`);
|
|
6194
|
+
return 0;
|
|
6195
|
+
}
|
|
5929
6196
|
function cmdScore(options, flags, cwd, write) {
|
|
5930
6197
|
const json = flags.has('json');
|
|
5931
6198
|
if (flags.has('help')) {
|
|
@@ -6003,10 +6270,25 @@ function cmdScore(options, flags, cwd, write) {
|
|
|
6003
6270
|
};
|
|
6004
6271
|
walk(dir, '', 0);
|
|
6005
6272
|
const card = scoreRun(slug, artifacts);
|
|
6273
|
+
// re-QE debt note (backlog 6b40e667): a scorecard that praised cross-model QE while an unsettled
|
|
6274
|
+
// same-family debt sits on disk would be half the truth. PARSED, not existence-guessed (QE #9);
|
|
6275
|
+
// a malformed debt file is named. Advisory in both modes — never moves the score or exit code.
|
|
6276
|
+
let reqeNote = null;
|
|
6277
|
+
{
|
|
6278
|
+
const scan = scanReqeDebts(root);
|
|
6279
|
+
const mine = scan.debts.find((d) => d.debt.slug === slug);
|
|
6280
|
+
if (mine)
|
|
6281
|
+
reqeNote = 'UNSETTLED re-QE debt — Step-8 ran same-family under the usage override; run `dz reqe --slug ' + slug + '`';
|
|
6282
|
+
else if (existsSync(join(dir, '.fa-state', 'reqe-due.json')))
|
|
6283
|
+
reqeNote = 'a reqe-due.json exists but is MALFORMED/mismatched — inspect features/' + slug + '/.fa-state/ manually';
|
|
6284
|
+
}
|
|
6006
6285
|
if (json)
|
|
6007
|
-
write(JSON.stringify({ ...card, exitCode: 0 }, null, 2));
|
|
6008
|
-
else
|
|
6286
|
+
write(JSON.stringify({ ...card, reqeDebt: reqeNote, exitCode: 0 }, null, 2));
|
|
6287
|
+
else {
|
|
6009
6288
|
write(renderScorecard(card));
|
|
6289
|
+
if (reqeNote)
|
|
6290
|
+
write('note: ' + reqeNote);
|
|
6291
|
+
}
|
|
6010
6292
|
return 0;
|
|
6011
6293
|
}
|
|
6012
6294
|
/**
|
|
@@ -7603,6 +7885,8 @@ export async function runCli(argv, io = {}) {
|
|
|
7603
7885
|
return cmdEpochReplay(options, flags, cwd, write);
|
|
7604
7886
|
case 'score':
|
|
7605
7887
|
return cmdScore(options, flags, cwd, write);
|
|
7888
|
+
case 'reqe':
|
|
7889
|
+
return cmdReqe(options, flags, cwd, write);
|
|
7606
7890
|
case 'backlog':
|
|
7607
7891
|
return await cmdBacklog(options, flags, cwd, write);
|
|
7608
7892
|
case 'routing':
|