@dzhechkov/harness-cli 0.4.4 → 0.4.6
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 +2 -2
- package/README.md +135 -32
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +83 -16
- package/dist/cli.js.map +1 -1
- package/package.json +10 -10
- package/sbom.json +1 -1
- package/src/cli.ts +88 -15
- package/LICENSE +0 -21
package/.dz-manifest.json
CHANGED
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
},
|
|
70
70
|
{
|
|
71
71
|
"path": "package.json",
|
|
72
|
-
"sha256": "
|
|
72
|
+
"sha256": "00a53276d145112e3c2ae5ca2950ac74877ea4b069b2b81040d49c5903eb6482"
|
|
73
73
|
},
|
|
74
74
|
{
|
|
75
75
|
"path": "src/bin.ts",
|
|
@@ -221,5 +221,5 @@
|
|
|
221
221
|
}
|
|
222
222
|
]
|
|
223
223
|
},
|
|
224
|
-
"signature": "
|
|
224
|
+
"signature": "6IQyjN4aazsr6EyD1+y/VrZ1+bclvxc9nXsCLR3WVDomAn6AhNtlTJQWdfbvw49A4/HH+KTW+KkJlj/gFFnqCw=="
|
|
225
225
|
}
|
package/README.md
CHANGED
|
@@ -1027,7 +1027,7 @@ Each pack is an npm package — click through for the **full per-skill documenta
|
|
|
1027
1027
|
| [@dzhechkov/skills-qe](https://www.npmjs.com/package/@dzhechkov/skills-qe) | 20 | Quality engineering — test-gen, coverage, chaos, defect intelligence, QCSD swarms |
|
|
1028
1028
|
| [@dzhechkov/skills-reasoning](https://www.npmjs.com/package/@dzhechkov/skills-reasoning) | 4 | Generic reasoning & code-quality — investigate (root-cause), solid (SOLID/TDD), karpathy-guidelines, agents-md-creator |
|
|
1029
1029
|
| [@dzhechkov/skills-ecc](https://www.npmjs.com/package/@dzhechkov/skills-ecc) | 20 | Claude-Code engineering craft — agent architecture, autonomous loops, framework patterns |
|
|
1030
|
-
| [@dzhechkov/skills-meta](https://www.npmjs.com/package/@dzhechkov/skills-meta) |
|
|
1030
|
+
| [@dzhechkov/skills-meta](https://www.npmjs.com/package/@dzhechkov/skills-meta) | 19 | Dev-process meta skills — explore, feature-adr, design-thinking, audit, skill-advisor, loop-plan-author |
|
|
1031
1031
|
| [@dzhechkov/skills-academic](https://www.npmjs.com/package/@dzhechkov/skills-academic) | 5 | Thesis-defense toolkit — dissertation review, questions, doc-check, defense eval |
|
|
1032
1032
|
| [@dzhechkov/skills-news](https://www.npmjs.com/package/@dzhechkov/skills-news) | 3 | *dz-original* — news digests (`news-digest`) + delta watches (`news-monitor`) + bundled `goap-research-ed25519` verified-research backend (mandatory) |
|
|
1033
1033
|
| [@dzhechkov/skills-idea2prd](https://www.npmjs.com/package/@dzhechkov/skills-idea2prd) | 1 | *dz-original* — `idea2prd-manual`: idea/problem → PRD+ADR+DDD+C4+Pseudocode+Tests+Completion (9 checkpoints); bundles the analyst trio as a sources.json-tracked vendor ([ADR-0001](https://github.com/djd1m/dz-harness-hub/blob/main/docs/adr/0001-skill-canonicalization-and-dependency-model.md)) |
|
|
@@ -1097,7 +1097,7 @@ Get the whole set with `dz init --target claude-code --preset meta`, or pick one
|
|
|
1097
1097
|
|
|
1098
1098
|
Custom loops used to be born by copy-pasting a 1470-line workflow script; nothing deterministic
|
|
1099
1099
|
checked the copy. The loop-designer meta-factory replaces that: a versioned typed plan
|
|
1100
|
-
(`loop-plan/1`), a schema-driven generator,
|
|
1100
|
+
(`loop-plan/1`), a schema-driven generator, an 18-rule lint gate, and a local trace plane.
|
|
1101
1101
|
|
|
1102
1102
|
```bash
|
|
1103
1103
|
# 1. Scaffold a plan (pipeline | barrier | fanout | gate), edit the TODO prompts:
|
|
@@ -1116,7 +1116,7 @@ dz workflow render triage-loop.plan.json --o triage-loop.js
|
|
|
1116
1116
|
|
|
1117
1117
|
# 4. Gate it (generated-loop CI mode; exit 0/1/3 — inconclusive is NEVER a pass):
|
|
1118
1118
|
dz workflow-lint triage-loop.js --plan triage-loop.plan.json --require-plan
|
|
1119
|
-
# → dz workflow-lint: PASS (mode=require-plan; 0 fail, 0 warn, 0 inconclusive over
|
|
1119
|
+
# → dz workflow-lint: PASS (mode=require-plan; 0 fail, 0 warn, 0 inconclusive over 18 rules)
|
|
1120
1120
|
|
|
1121
1121
|
# 5. Run it via Workflow({scriptPath:'triage-loop.js', args:{traceDir:'/abs/run-dir'}}), then read
|
|
1122
1122
|
# the run's own trace (seq-ordered; wallTime is diagnostic only):
|
|
@@ -1141,12 +1141,16 @@ host run there is nothing to read, and it says so rather than inventing a timeli
|
|
|
1141
1141
|
follows from the same boundary: on a non-Claude-Code target the authoring and lint verbs work
|
|
1142
1142
|
unchanged, and only execution is absent.
|
|
1143
1143
|
|
|
1144
|
-
### Build a loop for YOUR scenario — the end-to-end use case
|
|
1144
|
+
### Build a loop for YOUR scenario — the end-to-end use case (a real one)
|
|
1145
1145
|
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1146
|
+
This walkthrough is not an invented example. It is the loop that closed a real roadmap item in this
|
|
1147
|
+
very repo: a test pinned `34 canonical packages` while the workspace had grown past 50 directories —
|
|
1148
|
+
red forever, and nobody trusted a hand count. The job: produce a **verified inventory** of every
|
|
1149
|
+
directory under `packages/@dzhechkov` (canonical vs private vs not-a-package), too much for one
|
|
1150
|
+
sitting → an audit fanout: package chunks audited in parallel lanes, one synthesis, and a quality
|
|
1151
|
+
gate that rejects an inventory that doesn't cover every directory. Every command and output below is
|
|
1152
|
+
a capture from the run that actually fixed the test (MEASURED — reproducer
|
|
1153
|
+
`node --test tests/canonical-packages.test.mjs`: 289 pass / 0 fail after the re-pin).
|
|
1150
1154
|
|
|
1151
1155
|
**Install (once):**
|
|
1152
1156
|
|
|
@@ -1162,8 +1166,8 @@ RUNS one. Execution belongs to a host with the `Workflow({scriptPath})` runtime,
|
|
|
1162
1166
|
#### Step 1 — scaffold the plan shape closest to your scenario
|
|
1163
1167
|
|
|
1164
1168
|
```bash
|
|
1165
|
-
dz workflow init --name
|
|
1166
|
-
# → wrote
|
|
1169
|
+
dz workflow init --name pkg-inventory-audit --pattern fanout --o audit.plan.json
|
|
1170
|
+
# → wrote audit.plan.json (pattern: fanout)
|
|
1167
1171
|
# → Next: edit the TODO prompts, then `dz workflow validate` + `dz workflow render`.
|
|
1168
1172
|
```
|
|
1169
1173
|
|
|
@@ -1173,14 +1177,20 @@ template you fight.
|
|
|
1173
1177
|
|
|
1174
1178
|
#### Step 2 — make it yours (edit the JSON)
|
|
1175
1179
|
|
|
1176
|
-
Fill the `TODO` prompts, name your
|
|
1177
|
-
|
|
1180
|
+
Fill the `TODO` prompts, name your work units in `fanouts[].registry`, and add the quality gate
|
|
1181
|
+
with a redo route. The real plan's shape (abridged from
|
|
1182
|
+
`features/pkg-inventory-audit/audit.plan.json` in this repo):
|
|
1178
1183
|
|
|
1179
1184
|
```json
|
|
1180
|
-
"
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
"
|
|
1185
|
+
"fanouts": [ { "stage": "fan", "chain": ["lane"], "maxFanout": 3,
|
|
1186
|
+
"registry": ["chunk-1","chunk-2","chunk-3","chunk-4","chunk-5","chunk-6"] } ],
|
|
1187
|
+
"joins": [ { "stage": "jn", "forStage": "fan", "joinPolicy": "all-activated",
|
|
1188
|
+
"onInvalid": "named-failure" } ],
|
|
1189
|
+
"steps": [ …lanes…, { "stepId": "synthesize", "kind": "agent", "deps": ["jn"],
|
|
1190
|
+
"prompt": "Merge the lane JSONs into one inventory. Return STRICT JSON…" },
|
|
1191
|
+
{ "stepId": "check", "kind": "gate", "deps": ["synthesize"],
|
|
1192
|
+
"prompt": "…counts.total must equal 52… answer GATE: PASS or GATE: FAIL" } ],
|
|
1193
|
+
"gates": [ { "stepId": "check", "kind": "quality", "failRoute": "synthesize", "maxRedos": 1 } ]
|
|
1184
1194
|
```
|
|
1185
1195
|
|
|
1186
1196
|
The plan surface is deliberately NARROW and fully enacted: anything the generated loop would not
|
|
@@ -1190,34 +1200,87 @@ accepted and ignored.
|
|
|
1190
1200
|
#### Step 3 — validate, render, lint (the three gates before any run)
|
|
1191
1201
|
|
|
1192
1202
|
```bash
|
|
1193
|
-
dz workflow validate
|
|
1194
|
-
# → dz workflow validate: OK (digest sha256:
|
|
1203
|
+
dz workflow validate audit.plan.json
|
|
1204
|
+
# → dz workflow validate: OK (digest sha256:70992d9ddc86de15…)
|
|
1195
1205
|
|
|
1196
|
-
dz workflow render
|
|
1197
|
-
# → wrote
|
|
1206
|
+
dz workflow render audit.plan.json --o audit.loop.js
|
|
1207
|
+
# → wrote audit.loop.plan.json then audit.loop.js (exec-fp sha256:49feb8a2c1e17038…, blobs: trace)
|
|
1198
1208
|
|
|
1199
|
-
dz workflow-lint
|
|
1200
|
-
# → dz workflow-lint: PASS (0 fail, 1 warn, 0 inconclusive over
|
|
1209
|
+
dz workflow-lint audit.loop.js --plan audit.loop.plan.json --require-plan
|
|
1210
|
+
# → dz workflow-lint: PASS (mode=require-plan; 0 fail, 1 warn, 0 inconclusive over 18 rules)
|
|
1201
1211
|
```
|
|
1202
1212
|
|
|
1203
1213
|
`inconclusive` is never a pass, and the rendered script keeps your hand edits across re-renders
|
|
1204
1214
|
(USER regions are preserved).
|
|
1205
1215
|
|
|
1206
|
-
####
|
|
1216
|
+
#### Declare each step's tool perimeter — `LoopStep.tools`
|
|
1207
1217
|
|
|
1208
|
-
|
|
1218
|
+
**When to use it:** any loop whose steps reach external systems (a ticket tracker, a wiki, a repo
|
|
1219
|
+
host) through MCP, and you want the intended perimeter written down, well-formed, and gated rather
|
|
1220
|
+
than living in a prompt someone will edit.
|
|
1209
1221
|
|
|
1210
|
-
|
|
1222
|
+
```jsonc
|
|
1223
|
+
// in your plan.json — one array per DISPATCHING step (agent | gate)
|
|
1224
|
+
{"stepId":"discovery","kind":"agent","phase":"Discovery",
|
|
1225
|
+
"tools":["gitlab:read","jira:read","wiki:read"], "budget":{"maxAgents":1}},
|
|
1226
|
+
{"stepId":"verdict-gate","kind":"gate","phase":"Discovery","deps":["discovery"],
|
|
1227
|
+
"tools":[], "budget":{"maxAgents":1}}
|
|
1228
|
+
```
|
|
1211
1229
|
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
`GATE: FAIL` → the redo route re-runs `synthesize` → `check` answers `GATE: PASS` → `COMPLETED`.
|
|
1215
|
-
13 agent calls, one ledger row, the trace flushed at every settle. Then:
|
|
1230
|
+
`tools: []` is the **meaningful** value for a step that touches no external tool — absence is a lint
|
|
1231
|
+
finding, because silence is never permission:
|
|
1216
1232
|
|
|
1217
1233
|
```bash
|
|
1218
|
-
dz workflow-
|
|
1234
|
+
dz workflow-lint my-loop.js --plan my-loop.plan.json --require-plan
|
|
1235
|
+
# → FAIL tool-perimeter-declared: dispatching step sa declares no `tools` perimeter — absence
|
|
1236
|
+
# FLAGS: silence is never permission (declare `tools: []` if the step touches no external tool)
|
|
1237
|
+
```
|
|
1238
|
+
|
|
1239
|
+
A non-empty array is **enacted**, not decorative: it renders a fixed contract line into that step's
|
|
1240
|
+
prompt, and the lint rule cross-checks the script against the plan (so a plan edit you forgot to
|
|
1241
|
+
re-render fails too). Severity is staged — **WARN by default, FAIL only under `--require-plan`.**
|
|
1242
|
+
|
|
1243
|
+
**It is a DECLARATION, not enforcement.** `agent()` exposes no tool restriction; real enforcement
|
|
1244
|
+
lives at the MCP server. Do not read this field as a sandbox.
|
|
1245
|
+
|
|
1246
|
+
Worked consumer in this repo: `.claude/workflows/cfr-pipeline.js` — a 12-step, 6-gate Customer
|
|
1247
|
+
Feature Request pipeline with seven typed terminal exits, whose five source-touching stages each
|
|
1248
|
+
declare their perimeter (`features/cfr-pipeline/`).
|
|
1249
|
+
|
|
1250
|
+
#### Step 4 — run it (Claude Code), read it back (anywhere)
|
|
1251
|
+
|
|
1252
|
+
In **Claude Code**, paste exactly this shape:
|
|
1253
|
+
|
|
1254
|
+
> Запусти мой цикл: `Workflow({ scriptPath: 'audit.loop.js', args: { traceDir: '.dz/loop-trace/pkg-audit-1', runId: 'pkg-audit-1' } })` — и когда закончит, покажи `dz workflow-trace --run pkg-audit-1`.
|
|
1255
|
+
|
|
1256
|
+
What the real run produced (captured verbatim; the trace lives at `.dz/loop-trace/pkg-audit-1/` in
|
|
1257
|
+
this repo):
|
|
1258
|
+
|
|
1259
|
+
```
|
|
1260
|
+
run pkg-audit-1; sources: trace, ledger
|
|
1261
|
+
1 run.opened plan=70992d9ddc86 exec-fp=49feb8a2c1e1
|
|
1262
|
+
2 dispatch lane:chunk-1#1 phase=Lanes
|
|
1263
|
+
3 dispatch lane:chunk-2#1 phase=Lanes
|
|
1264
|
+
4 dispatch lane:chunk-3#1 phase=Lanes
|
|
1265
|
+
5..7 settle lane:chunk-{1,2,3} outcome=ok
|
|
1266
|
+
8 dispatch synthesize#1 phase=Synthesize
|
|
1267
|
+
9 settle synthesize#4 outcome=ok
|
|
1268
|
+
10 dispatch check#1 phase=Gate causedBy=[9]
|
|
1269
|
+
11 settle check#5 outcome=ok ← GATE: PASS
|
|
1270
|
+
12 run.closed {"dispatched":5,"settled":5}
|
|
1219
1271
|
```
|
|
1220
1272
|
|
|
1273
|
+
The gate passed, the inventory (52 directories = 49 canonical + 2 private + 1 non-package) was
|
|
1274
|
+
cross-checked deterministically against the filesystem, and the stale test was re-pinned to it.
|
|
1275
|
+
|
|
1276
|
+
**And the run surfaced a real defect — kept here on purpose.** Read the trace again: the registry
|
|
1277
|
+
names six chunks, but only `chunk-1..3` ever dispatched — `maxFanout: 3` **silently truncated** the
|
|
1278
|
+
other three lanes, and the run stayed green only because the synthesize agent noticed the gap and
|
|
1279
|
+
audited the missing chunks with its own tools. That is exactly what the trace is for: it shows what
|
|
1280
|
+
your loop *did*, not what the plan promised. (Filed in this repo's backlog: over-registry items must
|
|
1281
|
+
become a named failure or a loud trace event, never a silent drop. Until then: keep
|
|
1282
|
+
`fanouts[].registry.length ≤ maxFanout`, and read the trace after the first run of any new plan.)
|
|
1283
|
+
|
|
1221
1284
|
#### How to phrase the ASK — Claude Code vs Codex
|
|
1222
1285
|
|
|
1223
1286
|
**In Claude Code** (it has both `dz` and the `Workflow` runtime — one message does the whole thing):
|
|
@@ -1396,7 +1459,7 @@ dz drift-check [--all] [--json] [--project <dir>] # CI gate: exit 1 on N
|
|
|
1396
1459
|
dz sync-canonical <skill> [--check] [--from <dir>] [--auto] [--project <dir>] # heal every copy from skills-meta/<skill> or --from; no canonical + --check = compare copies to each other (exit 1 on drift); no canonical + write = refuse unless --auto (LOUD, picks most-complete copy); --check writes nothing
|
|
1397
1460
|
dz scout [--topics <list>] [--since <date>] [--deep] [--output <file>] [--diff] [--report]
|
|
1398
1461
|
dz workflow init --name <n> [--pattern pipeline|barrier|fanout|gate] [--o <plan.json>] | validate <plan.json> [--json] | render <plan.json> --o <script.js> [--check] [--force] | blobs [--check] # loop-plan/1 authoring (the ADR-005 templates are retired)
|
|
1399
|
-
dz workflow-lint <script.js> [--plan <plan.json>] [--require-plan|--legacy] [--json] #
|
|
1462
|
+
dz workflow-lint <script.js> [--plan <plan.json>] [--require-plan|--legacy] [--json] # 18-rule deterministic gate; exit 0/1/3 — inconclusive is never a pass
|
|
1400
1463
|
dz workflow-trace <runDir|--slug <s>|--run <id>> [--invariants <plan.json>] [--html <out.html>] [--json] # timeline + SEQ invariant runner over the loop's own trace.jsonl
|
|
1401
1464
|
dz workflow-trace export <run> --o <file> [--include-pairs --yes] [--strict] # one run's telemetry as ONE movable file
|
|
1402
1465
|
dz workflow-trace import <bundle> --into <root> [--force] [--with-pairs] # reconstruct that run; fail-closed against clobbering
|
|
@@ -1411,6 +1474,44 @@ dz import-ecc [--local-path <dir>] [--select id,id,...] [--limit N] [--out
|
|
|
1411
1474
|
dz help
|
|
1412
1475
|
```
|
|
1413
1476
|
|
|
1477
|
+
## Global: `dz --version` / `-v` / `dz version`
|
|
1478
|
+
|
|
1479
|
+
```bash
|
|
1480
|
+
dz --version # 0.4.6
|
|
1481
|
+
dz --version --json # {"name":"dz","version":"0.4.6","node":"v22.22.0","schemas":{"loopPlan":"loop-plan/1"}}
|
|
1482
|
+
```
|
|
1483
|
+
|
|
1484
|
+
One line, exit 0. Unresolvable ⇒ the literal `unknown` and exit **1** — never a fabricated number,
|
|
1485
|
+
and never a zero exit for "I could not tell you".
|
|
1486
|
+
|
|
1487
|
+
This is a pre-dispatch GLOBAL FLAG, not a 67th command: the count above is unchanged, and a test
|
|
1488
|
+
derives it from the rendered `dz --help` command list so a reformat cannot move it silently.
|
|
1489
|
+
|
|
1490
|
+
**Why it exists.** `dz --version` used to print the entire USAGE manual and exit 0 (MEASURED
|
|
1491
|
+
2026-08-17 on 0.4.5 — reproducer: `node dist/bin.js --version` before this change). Any wrapper
|
|
1492
|
+
guarding a version range — `@dzhechkov/loop-designer-plugin` is the first — would read exit 0 as "it
|
|
1493
|
+
answered me" while finding no version to parse, and then call a possibly-stale binary. Recognised
|
|
1494
|
+
only as the FIRST argument, so a later positional `-v` still belongs to its subcommand.
|
|
1495
|
+
|
|
1496
|
+
## `dz skills-verify` also sees SLASH COMMANDS now
|
|
1497
|
+
|
|
1498
|
+
```bash
|
|
1499
|
+
# does a plugin's skill AND its five commands actually register?
|
|
1500
|
+
dz skills-verify --dir /tmp/probe --plugin-dir packages/@dzhechkov/loop-designer-plugin \
|
|
1501
|
+
--expect loop-designer:loop-plan-author \
|
|
1502
|
+
--expect-commands loop-designer:init,loop-designer:validate,loop-designer:render,loop-designer:lint,loop-designer:trace
|
|
1503
|
+
```
|
|
1504
|
+
|
|
1505
|
+
- `--plugin-dir <dir>` loads a plugin into the probe session (session-scoped, no marketplace). With
|
|
1506
|
+
no `--expect-commands`, the expectation DEFAULTS to the manifest's own `commands[]`; an unreadable
|
|
1507
|
+
manifest is refused rather than defaulted to an empty (vacuously passing) expectation.
|
|
1508
|
+
- `--expect-commands a,b` names the slash commands that must appear in the authoritative listing.
|
|
1509
|
+
- An **absent** `slash_commands` key is `inconclusive`, never an empty list — schema drift and "your
|
|
1510
|
+
commands did not load" are different facts and must not be collapsed.
|
|
1511
|
+
- `--static` now also PRINTS its advisories. A `.claude-plugin/plugin.json` under `.claude/skills/`
|
|
1512
|
+
previously produced `no layout problems found` and nothing else; the shape most likely to be a
|
|
1513
|
+
silent non-registration was invisible in the mode CI runs. It is reported, and still never fatal.
|
|
1514
|
+
|
|
1414
1515
|
### Grounding: three tiers + the token trade-off (`dz brain ground` / `dz brain expand`)
|
|
1415
1516
|
|
|
1416
1517
|
The `dz brain ground` hook fires on **every turn**, so what it injects is a token trade-off. There are
|
|
@@ -3405,7 +3506,9 @@ npx @dzhechkov/p-replicator init
|
|
|
3405
3506
|
|
|
3406
3507
|
## Status
|
|
3407
3508
|
|
|
3408
|
-
`v0.4.
|
|
3509
|
+
`v0.4.6` — published on npm. Also available as [Claude Plugin](#claude-plugin). Part of [DZ Harness Hub](https://github.com/djd1m/dz-harness-hub).
|
|
3510
|
+
|
|
3511
|
+
New in this change: the global `dz --version` / `-v` / `dz version` surface (one parseable line), and `dz skills-verify --plugin-dir` / `--expect-commands` so slash-command registration is gate-visible. Both exist for `@dzhechkov/loop-designer-plugin`, which requires `dz` in `^0.4`, verifies it at run time, and falls back to `npx -y @dzhechkov/harness-cli@^0.4` when the `dz` on PATH is stale, unparseable or missing.
|
|
3409
3512
|
|
|
3410
3513
|
## Claude Plugin
|
|
3411
3514
|
|
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;AAucH,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;IAC3C;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACjE;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;AA4oR9E,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAwL5E"}
|
package/dist/cli.js
CHANGED
|
@@ -12,7 +12,7 @@ import { homedir, tmpdir } from 'node:os';
|
|
|
12
12
|
import { createRequire } from 'node:module';
|
|
13
13
|
import { createSkill, getSkillInfo, isTargetName, listSkills, runDoctor, runInit, resolvePackageSkillRoots, PACKAGE_SKILL_LAYOUTS, benchmarkSkill, benchmarkSkills, scanMcp, reconcileCapabilities, RECONCILE_BANNER, buildRegistry, discoverSkillPackDirs, checkUpstream, compareSkills, checkAllUpstream, sweepSkillDrift, syncCanonicalSkill, checkUpgrades, discoverPackages, discoverSourcePackages, fetchAllDownloads, filterByCategory, pretrain, recommend, generatePlugin, publishPackages, runSetup, runMigrate, searchRegistry, runSync, runVerify, runInitAgentsMd, runInitGeminiMd, TARGET_NAMES, buildParityMatrix, TARGET_CAPABILITIES, TARGET_SHORT_LABELS, WORKFLOW_TEMPLATES_RETIRED_MESSAGE, parsePlan, isParseErrors, validatePlan, normalizePlan, planDigest, toTraceProjection, renderPlan, mergeRender, lint, lintExitCode, LOOP_BLOBS, parseTrace, assembleTimeline, runInvariants, renderTimelineHtml, importEcc, recordPattern, resolveLearningBackend, storeStats, consolidateSessions, pruneNoisePatterns, lessonDeltaReport, removePatternsByIds, snapshotStore, recallHybrid, teachGuard, mirrorPatternsToVector, mirrorEntriesToVector, patternVectorEntry, readMemoryLearningConfig, promotePatterns, quarantineExpiryCandidates, pruneQuarantinePatterns, clearAgentdbQuarantine, vectorMirrorEnabled, vectorTierStatus, resolveVectorEngine, reindexVectorStore, harmonizeVectorStore, importRvfCheckpoint, statuslineData, writeFeatureAdrState, computeUsage, deriveCostLedger, renderCostLedger, verifyCostLedgerReport, writeCostLedgerJsonl, COST_LEDGER_SCOPE, deriveUsageCalibration, normalizeClaudeUsageModelKey, readUsageLimits, parseWeeklyResetAnchor, claimCheck, summarize, queryBookKnowledge, loadStorePatternsSync, patternRecordId, loadStoreRecords, recordToPattern, bundleSkills, brainHome, listBrain, promoteProjectToBrain, updateBrainSource, queryBrain, groundPrompt, expandKu, reindexBrainVectors, buildPrimer, exportBrainSlice, importBrainSlice, registerKusToBrain, RECALL_USAGE_LOG_RELATIVE, RECALL_USAGE_LOG_MAX_BYTES, parseRecallUsageLog, buildRecallUsageReport, EVENT_CHAIN_TAIL_BYTES, EMPTY_LOG_TAIL, readTailInfo, appendChainedLines, verifyEventChainText, buildManifest, buildSbom, resolveTrustRoot, decideVerifyPolicy, generateSigningKeypair, evaluateGuard, resolveRules, auditRecord, guardExitCode, DEFAULT_RULES, parsePnpmLockImporters, scannableStubPath,
|
|
14
14
|
// guard-promotion (feature guard-promotion, scout idea #1)
|
|
15
|
-
assembleCandidates, renderPromotionReport, renderPromotionAdr, normalizePromotionState, nextPromotionState, globMatch, promotionAdrRelPath, DEFAULT_WINDOW_DAYS, DEFAULT_PERIODS, MAX_CONTENT_FETCHES, BUILTIN_COVERAGE, decideProvenance, isInsideTree, signManifest, verifyManifest, listSignablePackFiles, assertKeyOutsideTree, decidePublishGate, collectPackageFacts, planReleaseGates, selectAffectedPackages, classifyGateExecutions, buildFailureIssue, buildReleaseNotes, releaseTagName, firstOutputLine, formatPublishError, MANIFEST_NAME, SBOM_NAME, buildArchitectureMap, renderMapHuman, findArchitectureDrift, renderDriftReport, scanWorkspacePackages, loadSubsystemManifest, loadProductVision, checkFeatureAgainstArchitecture, renderArchCheck, planProjectSkills, guidanceForStage, renderInjectionReport, analyzeCorpus, renderRakeReport, renderCriticSection, rakeAsLesson, rakeReward, DEFAULT_RAKE_THRESHOLDS, streamSessionEvents, findLatestTranscript, detectProcessRakes, buildRetro, renderRetro, retroLessonText, PROCESS_SIGNATURES, RETRO_DOMAIN, scanForSetup, buildSetupPlan, scaffoldFromSpec, renderScaffoldPreview, readExistingForScaffold, assembleChallengeContext, buildChallengeBrief, planDiscriminationCheck, classifyDiscrimination, pickAdversaryModel, CHALLENGE_QUESTIONS, loadOutcomes, renderOutcomes, statsForKey, selectAutoCost, recordProvisional, finalizeOutcome, harvestStageOutcomes, recommendModels, planFeed, GRADE_SUCCESS_FLOOR, COST_LADDER, splitScenarios, budgetPlan, selectWinner, proseScopeOk, renderProseDiff, readScenarioIds, DEFAULT_MAX_JUDGE_RUNS, collectDeliveryFacts, planDeliveryCheck, renderDeliveryBrief, classifyDelivery, isUsablePlaneResult, renderDeliveryReview, scanSkillsLayout, parseInitFacts, verifyRegistration, buildContentProbePrompt, classifyContentProbe, renderContentProbe, findNonRegistrableSkillDirs, assembleCompoundingReport,
|
|
15
|
+
assembleCandidates, renderPromotionReport, renderPromotionAdr, normalizePromotionState, nextPromotionState, globMatch, promotionAdrRelPath, DEFAULT_WINDOW_DAYS, DEFAULT_PERIODS, MAX_CONTENT_FETCHES, BUILTIN_COVERAGE, decideProvenance, isInsideTree, signManifest, verifyManifest, listSignablePackFiles, assertKeyOutsideTree, decidePublishGate, collectPackageFacts, planReleaseGates, selectAffectedPackages, classifyGateExecutions, buildFailureIssue, buildReleaseNotes, releaseTagName, firstOutputLine, formatPublishError, MANIFEST_NAME, SBOM_NAME, buildArchitectureMap, renderMapHuman, findArchitectureDrift, renderDriftReport, scanWorkspacePackages, loadSubsystemManifest, loadProductVision, checkFeatureAgainstArchitecture, renderArchCheck, planProjectSkills, guidanceForStage, renderInjectionReport, analyzeCorpus, renderRakeReport, renderCriticSection, rakeAsLesson, rakeReward, DEFAULT_RAKE_THRESHOLDS, streamSessionEvents, findLatestTranscript, detectProcessRakes, buildRetro, renderRetro, retroLessonText, PROCESS_SIGNATURES, RETRO_DOMAIN, scanForSetup, buildSetupPlan, scaffoldFromSpec, renderScaffoldPreview, readExistingForScaffold, assembleChallengeContext, buildChallengeBrief, planDiscriminationCheck, classifyDiscrimination, pickAdversaryModel, CHALLENGE_QUESTIONS, loadOutcomes, renderOutcomes, statsForKey, selectAutoCost, recordProvisional, finalizeOutcome, harvestStageOutcomes, recommendModels, planFeed, GRADE_SUCCESS_FLOOR, COST_LADDER, splitScenarios, budgetPlan, selectWinner, proseScopeOk, renderProseDiff, readScenarioIds, DEFAULT_MAX_JUDGE_RUNS, collectDeliveryFacts, planDeliveryCheck, renderDeliveryBrief, classifyDelivery, isUsablePlaneResult, renderDeliveryReview, scanSkillsLayout, declaredPluginSurface, parseInitFacts, verifyRegistration, buildContentProbePrompt, classifyContentProbe, renderContentProbe, findNonRegistrableSkillDirs, assembleCompoundingReport,
|
|
16
16
|
// Cold-vs-warm EPOCH RUNNER (feature epoch-replay) — orchestrates + scores, never calls a model.
|
|
17
17
|
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,
|
|
18
18
|
// Smart Backlog (feature smart-backlog) — goal-directed idea pipeline over the Brain vector engine.
|
|
@@ -119,6 +119,8 @@ Usage:
|
|
|
119
119
|
dz import-ecc [--local-path <dir>] [--select id,id,...] [--limit N] [--output <dir>] [--force]
|
|
120
120
|
dz help
|
|
121
121
|
|
|
122
|
+
Global: --version | -v [--json] (prints this CLI's own semver on one line, exit 0; "unknown" + exit 1 when unresolvable)
|
|
123
|
+
|
|
122
124
|
Workflows: author loop-plan/1 plans with dz workflow init/validate/render; gate them with dz workflow-lint; read runs with dz workflow-trace (the ADR-005 templates are retired)
|
|
123
125
|
|
|
124
126
|
Targets: ${TARGET_NAMES.join(', ')}
|
|
@@ -529,8 +531,8 @@ function workflowInitPlan(name, pattern) {
|
|
|
529
531
|
...base,
|
|
530
532
|
steps: [
|
|
531
533
|
{ stepId: 'fan', kind: 'fanout', phase: 'Work', concurrency: 'pipeline', budget: { maxAgents: 8 } },
|
|
532
|
-
{ stepId: 'a', kind: 'agent', phase: 'Work', prompt: 'TODO: stage A per item', budget: { maxAgents: 4 } }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
|
|
533
|
-
{ stepId: 'b', kind: 'agent', phase: 'Work', prompt: 'TODO: stage B per item', budget: { maxAgents: 4 } }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
|
|
534
|
+
{ stepId: 'a', kind: 'agent', phase: 'Work', prompt: 'TODO: stage A per item', budget: { maxAgents: 4 }, tools: [] }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
|
|
535
|
+
{ stepId: 'b', kind: 'agent', phase: 'Work', prompt: 'TODO: stage B per item', budget: { maxAgents: 4 }, tools: [] }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
|
|
534
536
|
{ stepId: 'jn', kind: 'join', phase: 'Work', deps: ['fan'] },
|
|
535
537
|
],
|
|
536
538
|
fanouts: [{ stage: 'fan', registry: ['item1', 'item2', 'item3'], maxFanout: 3, chain: ['a', 'b'] }],
|
|
@@ -542,10 +544,10 @@ function workflowInitPlan(name, pattern) {
|
|
|
542
544
|
...base,
|
|
543
545
|
steps: [
|
|
544
546
|
{ stepId: 'fan', kind: 'fanout', phase: 'Lanes', concurrency: 'barrier', budget: { maxAgents: 6 } },
|
|
545
|
-
{ stepId: 'lane', kind: 'agent', phase: 'Lanes', prompt: 'TODO: one lane', budget: { maxAgents: 6 } }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
|
|
547
|
+
{ stepId: 'lane', kind: 'agent', phase: 'Lanes', prompt: 'TODO: one lane', budget: { maxAgents: 6 }, tools: [] }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
|
|
546
548
|
{ stepId: 'jn', kind: 'join', phase: 'Lanes', deps: ['fan'] },
|
|
547
549
|
// the consumer hangs off the BARRIER (jn), never the fork — barrier-postdominates teaches this
|
|
548
|
-
{ stepId: 'synthesize', kind: 'agent', phase: 'Synthesize', deps: ['jn'], prompt: 'TODO: synthesize across lanes', budget: { maxAgents: 1 } }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
|
|
550
|
+
{ stepId: 'synthesize', kind: 'agent', phase: 'Synthesize', deps: ['jn'], prompt: 'TODO: synthesize across lanes', budget: { maxAgents: 1 }, tools: [] }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
|
|
549
551
|
],
|
|
550
552
|
fanouts: [{ stage: 'fan', registry: ['lane1', 'lane2', 'lane3'], maxFanout: 3, chain: ['lane'] }],
|
|
551
553
|
joins: [{ stage: 'jn', forStage: 'fan', joinPolicy: 'all-activated', onInvalid: 'named-failure' }],
|
|
@@ -555,8 +557,8 @@ function workflowInitPlan(name, pattern) {
|
|
|
555
557
|
return {
|
|
556
558
|
...base,
|
|
557
559
|
steps: [
|
|
558
|
-
{ stepId: 'work', kind: 'agent', phase: 'Work', prompt: 'TODO: produce the artifact', budget: { maxAgents: 2 } }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
|
|
559
|
-
{ stepId: 'gate', kind: 'gate', phase: 'Gate', deps: ['work'], prompt: 'TODO: gate check (parse the verdict, never synthesize one)', budget: { maxAgents: 1 } }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
|
|
560
|
+
{ stepId: 'work', kind: 'agent', phase: 'Work', prompt: 'TODO: produce the artifact', budget: { maxAgents: 2 }, tools: [] }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
|
|
561
|
+
{ stepId: 'gate', kind: 'gate', phase: 'Gate', deps: ['work'], prompt: 'TODO: gate check (parse the verdict, never synthesize one)', budget: { maxAgents: 1 }, tools: [] }, // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
|
|
560
562
|
],
|
|
561
563
|
gates: [{ stepId: 'gate', kind: 'parse-verdict', failRoute: 'work', maxRedos: 1 }],
|
|
562
564
|
};
|
|
@@ -564,7 +566,7 @@ function workflowInitPlan(name, pattern) {
|
|
|
564
566
|
// minimal default: one agent step
|
|
565
567
|
return {
|
|
566
568
|
...base,
|
|
567
|
-
steps: [{ stepId: 'main', kind: 'agent', phase: 'Work', prompt: 'TODO: the one step', budget: { maxAgents: 1 } }], // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
|
|
569
|
+
steps: [{ stepId: 'main', kind: 'agent', phase: 'Work', prompt: 'TODO: the one step', budget: { maxAgents: 1 }, tools: [] }], // no-stubs: workflow-init scaffold sentinel the author replaces (deliberate authoring cue, not unfinished code)
|
|
568
570
|
};
|
|
569
571
|
}
|
|
570
572
|
/**
|
|
@@ -762,8 +764,12 @@ function cmdWorkflowLint(options, flags, cwd, write) {
|
|
|
762
764
|
/** `dz workflow-trace` — timeline + invariant runner over a run's trace.jsonl. Scope is CAPPED
|
|
763
765
|
* (AM-8): <runDir|--slug|--run>, --invariants, --html, --json. NO watch/filter/compare/search/
|
|
764
766
|
* retention/access-control — adding one needs an ADR amendment (the surface test pins this). */
|
|
765
|
-
/**
|
|
766
|
-
|
|
767
|
+
/**
|
|
768
|
+
* This CLI's OWN semver, read from its package.json. `unknown` is honest; a throw is not, and an
|
|
769
|
+
* invented number is worse than either — a downstream version guard that is handed a fabricated
|
|
770
|
+
* version happily calls a binary it should have refused.
|
|
771
|
+
*/
|
|
772
|
+
function dzOwnVersion() {
|
|
767
773
|
try {
|
|
768
774
|
const req = createRequire(import.meta.url);
|
|
769
775
|
const pkg = req('../package.json');
|
|
@@ -773,6 +779,10 @@ function traceBundleToolVersion() {
|
|
|
773
779
|
return 'unknown';
|
|
774
780
|
}
|
|
775
781
|
}
|
|
782
|
+
/** The tool version stamped into a bundle's provenance. Unknown is honest; a throw is not. */
|
|
783
|
+
function traceBundleToolVersion() {
|
|
784
|
+
return dzOwnVersion();
|
|
785
|
+
}
|
|
776
786
|
/** Resolve a run the SAME three ways the timeline reader does — positional, --slug, --run — so a
|
|
777
787
|
* bundle can never address a run by a scheme the rest of the command does not understand. */
|
|
778
788
|
function resolveTraceRun(options, cwd, positional) {
|
|
@@ -7926,7 +7936,7 @@ const PROBE_SCRUB_ENV = [
|
|
|
7926
7936
|
* lands, then kill it — the model never answers, so the probe costs ~no tokens. Never throws:
|
|
7927
7937
|
* every failure becomes an `error` string, which the pure classifier turns into `inconclusive`.
|
|
7928
7938
|
*/
|
|
7929
|
-
function probeInitStream(projectDir, timeoutMs) {
|
|
7939
|
+
function probeInitStream(projectDir, timeoutMs, pluginDir = null) {
|
|
7930
7940
|
return new Promise((resolveProbe) => {
|
|
7931
7941
|
const env = { ...process.env };
|
|
7932
7942
|
for (const key of PROBE_SCRUB_ENV)
|
|
@@ -7952,7 +7962,13 @@ function probeInitStream(projectDir, timeoutMs) {
|
|
|
7952
7962
|
const timer = setTimeout(() => finish(`no init event within ${Math.round(timeoutMs / 1000)}s (is \`claude\` logged in?)`), timeoutMs);
|
|
7953
7963
|
try {
|
|
7954
7964
|
// NOT `--bare`: that mode skips plugin credentials and fails with "Not logged in".
|
|
7955
|
-
|
|
7965
|
+
const args = ['-p', 'ok', '--output-format', 'stream-json', '--verbose'];
|
|
7966
|
+
// Session-scoped plugin load — the marketplace-free vehicle (ADR-003 D-3). Without this the
|
|
7967
|
+
// probe reads a session in which the plugin was never loaded, and reports its commands
|
|
7968
|
+
// missing for a reason that has nothing to do with the package under test.
|
|
7969
|
+
if (pluginDir !== null)
|
|
7970
|
+
args.push('--plugin-dir', pluginDir);
|
|
7971
|
+
child = spawn('claude', args, {
|
|
7956
7972
|
cwd: projectDir,
|
|
7957
7973
|
env,
|
|
7958
7974
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
@@ -8026,8 +8042,11 @@ function probeContent(projectDir, prompt, timeoutMs) {
|
|
|
8026
8042
|
async function cmdSkillsVerify(options, flags, cwd, write) {
|
|
8027
8043
|
const json = flags.has('json');
|
|
8028
8044
|
if (flags.has('help')) {
|
|
8029
|
-
write('dz skills-verify [--dir <project>] [--expect a,b] [--static] [--strict] [--timeout <s>] [--json]');
|
|
8045
|
+
write('dz skills-verify [--dir <project>] [--expect a,b] [--expect-commands a,b] [--plugin-dir <dir>] [--static] [--strict] [--timeout <s>] [--json]');
|
|
8030
8046
|
write(' Verifies that a project\'s .claude/skills/ actually register in Claude Code.');
|
|
8047
|
+
write(' --plugin-dir <dir> load a plugin into the probe session (session-scoped, no marketplace);');
|
|
8048
|
+
write(' with no --expect-commands, the expectation defaults to the manifest\'s own commands[]');
|
|
8049
|
+
write(' --expect-commands slash commands that MUST appear in the session listing, e.g. loop-designer:init');
|
|
8031
8050
|
write(' --static layout scan only (no Claude session, CI-safe): flags dirs that can never register');
|
|
8032
8051
|
write(' --live-content ADVISORY extra turn: ask a live model to name the skills and quote one, proving');
|
|
8033
8052
|
write(' the CONTENT is usable — registration is not usability. Never changes the exit code.');
|
|
@@ -8036,8 +8055,8 @@ async function cmdSkillsVerify(options, flags, cwd, write) {
|
|
|
8036
8055
|
return 0;
|
|
8037
8056
|
}
|
|
8038
8057
|
const allowedFlags = new Set(['json', 'help', 'static', 'strict', 'live-content']);
|
|
8039
|
-
const allowedOptions = new Set(['dir', 'expect', 'timeout']);
|
|
8040
|
-
const usage = ' allowed: --dir <project>, --expect a,b, --timeout <s>, --static, --strict, --live-content, --json';
|
|
8058
|
+
const allowedOptions = new Set(['dir', 'expect', 'expect-commands', 'plugin-dir', 'timeout']);
|
|
8059
|
+
const usage = ' allowed: --dir <project>, --expect a,b, --expect-commands a,b, --plugin-dir <dir>, --timeout <s>, --static, --strict, --live-content, --json';
|
|
8041
8060
|
if (options.has('_positional_0')) {
|
|
8042
8061
|
const message = `unexpected argument "${options.get('_positional_0')}"`;
|
|
8043
8062
|
write(json ? JSON.stringify({ error: message, exitCode: 1 }) : `dz skills-verify: ${message}\n${usage}`);
|
|
@@ -8065,6 +8084,27 @@ async function cmdSkillsVerify(options, flags, cwd, write) {
|
|
|
8065
8084
|
.map((s) => s.trim())
|
|
8066
8085
|
.filter(Boolean)
|
|
8067
8086
|
: scan.registrable;
|
|
8087
|
+
// A plugin loaded with `--plugin-dir` is session-scoped, so its surface is NOT on the project's
|
|
8088
|
+
// disk and `scan.registrable` cannot describe it. The manifest can — and defaulting to the
|
|
8089
|
+
// manifest's own `commands[]` keeps the gate honest without a hand-typed list that silently
|
|
8090
|
+
// drifts from the manifest it is supposed to be checking. An UNREADABLE manifest is refused
|
|
8091
|
+
// rather than defaulted to an empty expectation: an empty expectation passes without checking.
|
|
8092
|
+
const pluginDir = options.has('plugin-dir') ? resolve(cwd, options.get('plugin-dir') ?? '') : null;
|
|
8093
|
+
let expectedCommands = options.has('expect-commands')
|
|
8094
|
+
? (options.get('expect-commands') ?? '')
|
|
8095
|
+
.split(',')
|
|
8096
|
+
.map((s) => s.trim())
|
|
8097
|
+
.filter(Boolean)
|
|
8098
|
+
: [];
|
|
8099
|
+
if (pluginDir !== null && !options.has('expect-commands')) {
|
|
8100
|
+
const surface = declaredPluginSurface(pluginDir);
|
|
8101
|
+
if (surface === null) {
|
|
8102
|
+
const message = `cannot read ${join(pluginDir, '.claude-plugin', 'plugin.json')} (or it declares no name) — pass --expect-commands explicitly`;
|
|
8103
|
+
write(json ? JSON.stringify({ error: message, exitCode: 1 }) : `dz skills-verify: ${message}`);
|
|
8104
|
+
return 1;
|
|
8105
|
+
}
|
|
8106
|
+
expectedCommands = surface.commands;
|
|
8107
|
+
}
|
|
8068
8108
|
// ── L1 only: deterministic, no session, safe for CI ──
|
|
8069
8109
|
if (flags.has('static')) {
|
|
8070
8110
|
const exitCode = scan.findings.length > 0 ? 1 : 0;
|
|
@@ -8078,6 +8118,12 @@ async function cmdSkillsVerify(options, flags, cwd, write) {
|
|
|
8078
8118
|
for (const f of scan.findings)
|
|
8079
8119
|
write(` [${f.kind}] ${f.detail}`);
|
|
8080
8120
|
write(scan.findings.length ? ` ${scan.findings.length} layout problem(s) — these can never register` : ' no layout problems found');
|
|
8121
|
+
// Advisories were collected but never PRINTED in static mode: a `.claude-plugin/plugin.json`
|
|
8122
|
+
// under `.claude/skills` produced "no layout problems found" and nothing else, so the one
|
|
8123
|
+
// shape most likely to be a silent non-registration was invisible in exactly the mode CI and
|
|
8124
|
+
// humans run most. Reported, still never fatal (that distinction is the whole point).
|
|
8125
|
+
for (const a of scan.advisories)
|
|
8126
|
+
write(` [${a.kind}] ADVISORY: ${a.detail}`);
|
|
8081
8127
|
write(' (static is a PROXY — run without --static to read the real registration listing)');
|
|
8082
8128
|
}
|
|
8083
8129
|
return exitCode;
|
|
@@ -8087,7 +8133,7 @@ async function cmdSkillsVerify(options, flags, cwd, write) {
|
|
|
8087
8133
|
const timeoutMs = Number.isFinite(timeoutSec) && timeoutSec > 0 ? timeoutSec * 1000 : 180_000;
|
|
8088
8134
|
if (!json)
|
|
8089
8135
|
write(`dz skills-verify: starting a session in ${projectDir} to read the real registration listing…`);
|
|
8090
|
-
const { stream, error } = await probeInitStream(projectDir, timeoutMs);
|
|
8136
|
+
const { stream, error } = await probeInitStream(projectDir, timeoutMs, pluginDir);
|
|
8091
8137
|
// `init.skills` carries names, not provenance: a USER-level skill of the same name would satisfy
|
|
8092
8138
|
// the expectation while the project's own copy stays broken. Collect the collisions so the
|
|
8093
8139
|
// classifier can refuse to attribute registration to this project (Codex QE #2).
|
|
@@ -8109,6 +8155,7 @@ async function cmdSkillsVerify(options, flags, cwd, write) {
|
|
|
8109
8155
|
// The provenance check RAN (that is what `checked: true` asserts) — see `ambiguous` above.
|
|
8110
8156
|
provenance: { checked: true, ambiguous },
|
|
8111
8157
|
...(options.has('expect') ? { expected } : {}),
|
|
8158
|
+
...(expectedCommands.length > 0 ? { expectedCommands } : {}),
|
|
8112
8159
|
}, { resolvePath: canonical });
|
|
8113
8160
|
const exitCode = registrationExitCode(result.verdict, flags.has('strict'));
|
|
8114
8161
|
// ADVISORY layer: registration is not usability. Costs a real model turn, so it is opt-in, and it
|
|
@@ -9164,6 +9211,26 @@ export async function runCli(argv, io = {}) {
|
|
|
9164
9211
|
}
|
|
9165
9212
|
};
|
|
9166
9213
|
const { command, options, optionLists, flags } = parseArgs(argv);
|
|
9214
|
+
// ── `dz --version` / `dz -v` / `dz version` — PRE-DISPATCH, before the help branch ──
|
|
9215
|
+
//
|
|
9216
|
+
// Until now `dz --version` printed the whole USAGE manual and exited 0 (MEASURED 2026-08-17,
|
|
9217
|
+
// reproducer `node dist/bin.js --version`). Exit 0 plus prose is the worst possible answer for a
|
|
9218
|
+
// caller that must decide whether a `dz` it found on PATH is safe to invoke: the status code says
|
|
9219
|
+
// "fine" and there is no number to parse. Any wrapper guarding a version range needs exactly one
|
|
9220
|
+
// parseable line. Recognised only as the FIRST token (or the `version` subcommand) so that a
|
|
9221
|
+
// later positional `-v` belonging to a subcommand keeps its own meaning.
|
|
9222
|
+
if (argv[0] === '--version' || argv[0] === '-v' || command === 'version') {
|
|
9223
|
+
const version = dzOwnVersion();
|
|
9224
|
+
if (flags.has('json')) {
|
|
9225
|
+
write(JSON.stringify({ name: 'dz', version, node: process.version, schemas: { loopPlan: 'loop-plan/1' } }));
|
|
9226
|
+
}
|
|
9227
|
+
else {
|
|
9228
|
+
write(version);
|
|
9229
|
+
}
|
|
9230
|
+
// An unresolvable version is a FAILURE, not a value: exiting 0 with the literal `unknown` would
|
|
9231
|
+
// let a guard treat "I could not tell you" as "I answered you".
|
|
9232
|
+
return version === 'unknown' ? 1 : 0;
|
|
9233
|
+
}
|
|
9167
9234
|
if (command === '' || command === 'help' || flags.has('help')) {
|
|
9168
9235
|
write(USAGE);
|
|
9169
9236
|
return 0;
|