@dzhechkov/harness-cli 0.3.262 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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 63 commands mapped to a real workflow:
135
+ All 66 commands (MEASURED — reproducer: `grep -c "^ case '" src/cli.ts`, the dispatch cases) mapped to a real workflow:
136
136
 
137
137
  ```
138
138
  DISCOVER → INSTALL → USE → CREATE → MAINTAIN → SHARE
@@ -230,9 +230,10 @@ dz upgrade --target openclaude # check specific platform
230
230
  dz sync # canonical → project skills
231
231
  dz migrate # detect legacy installations
232
232
 
233
- # Orchestrate dynamic workflows:
234
- dz workflow --task coverage-lift # parallel coverage improvement
235
- dz workflow --task security-audit # adversarial security scan
233
+ # Author + gate custom Workflow loops (loop-plan/1):
234
+ dz workflow init --name my-loop --pattern pipeline --o my-loop.plan.json
235
+ dz workflow render my-loop.plan.json --o my-loop.js
236
+ dz workflow-lint my-loop.js --plan my-loop.plan.json --require-plan
236
237
 
237
238
  # Cross-host state sync:
238
239
  dz roam --apply # sync agent state across machines
@@ -838,6 +839,110 @@ the discriminator is a heuristic — a package counts as a skill pack only if it
838
839
  registrable skill, and a directory is flagged only when a `SKILL.md` really exists inside it but in the
839
840
  wrong place. Ordinary `scripts/`, `docs/` and pure npx toolkits stay silent.
840
841
 
842
+ ### Does your test actually DEFEND the protection? — `dz mutation-gate`
843
+
844
+ A green test proves the code works. It does NOT prove the test would have noticed the protection
845
+ being deleted. Three cross-model QE rounds on `@dzhechkov/health-advisor` produced 53 → 14 → 14
846
+ findings while the whole suite stayed green (MEASURED — reproducer: the three QE rounds recorded in
847
+ that package's history; the round-1 exploit string the code comment records as MEASURED passed a
848
+ 444-test green suite). The one technique that proved a protection real in those rounds was:
849
+ delete the protection in a scratch copy, re-run the suite, require red. `dz mutation-gate` is that
850
+ measurement as a layer-1 repo check.
851
+
852
+ The registry is declarative DATA (data is harder to make lie): one entry per NAMED safety property,
853
+ with an exact `{find, replace}` mutation that deletes the protection.
854
+
855
+ ```jsonc
856
+ // test/mutation-registry.json
857
+ {
858
+ "testCommand": "npm test",
859
+ "entries": [{
860
+ "id": "verbatim-door-requires-resolvable-locator",
861
+ "property": "An evidence quote is exempt from content scans only when its locator resolves…",
862
+ "file": "lib/appraisal-core.js",
863
+ "mutation": { "find": "<exact source text>", "replace": "<neutered text>" },
864
+ "minFailing": 1, // the entry's contract: at least this many tests MUST go red
865
+ "observed": 4 // how many did when written — a later lower count prints a COVERAGE DROP warning
866
+ }]
867
+ }
868
+ ```
869
+
870
+ ```bash
871
+ cd packages/@dzhechkov/health-advisor
872
+ dz mutation-gate # registry auto-found at test/mutation-registry.json
873
+ dz mutation-gate --only lock-root-resolves-to-itself # one entry while iterating
874
+ dz mutation-gate --rebaseline final # cheap flake guard: one re-run at the end instead of per red entry
875
+ dz mutation-gate --json # machine contract {baseline, results, summary, warnings, exitCode}
876
+ npm run test:mutation # the package's own alias for the full run
877
+ ```
878
+
879
+ Expected output (abridged from a real run — MEASURED 2026-08-07, reproducer: `cd
880
+ packages/@dzhechkov/health-advisor && dz mutation-gate`, 18 entries over a 484-test `node --test`
881
+ suite; wall-clock 9m32s with the default per-entry re-baseline, 4m53s with `--rebaseline final` —
882
+ same reproducer, both modes timed on the same tree):
883
+
884
+ ```
885
+ mutation-gate: baseline suite in scratch copy of …/health-advisor …
886
+ mutation-gate: verbatim-door-requires-resolvable-locator — mutating lib/appraisal-core.js, running suite …
887
+ baseline: GREEN — baseline suite green in the scratch copy
888
+ ✓ verbatim-door-requires-resolvable-locator applied=yes failing=4 PROVEN
889
+ ✗ cli-swallows-only-epipe applied=yes failing=0 UNDEFENDED
890
+ suite stayed GREEN with the protection deleted — property UNDEFENDED: "Only EPIPE is
891
+ swallowed on stdout/stderr — every other stream error stays a loud crash." …
892
+ summary: 17/18 proven · 1 undefended · 0 not-applied · 0 below-min · 0 unparseable · 0 over-failing · 0 inconclusive · 0 coverage drop(s)
893
+ verdict: FAIL — at least one named protection is undefended, unmutable, or unproven
894
+ ```
895
+
896
+ That `UNDEFENDED` line is the command earning its keep: on its FIRST run against health-advisor the
897
+ gate found 2 protections whose suite stayed green with the protection deleted (MEASURED 2026-08-07 —
898
+ reproducer: `dz mutation-gate --package packages/@dzhechkov/health-advisor --json` at the commit
899
+ before `test/case-state-alias-and-stream-error-discrimination.test.js` landed), plus a third
900
+ (the post-acquisition lock-scope re-assert) that was undefended in BOTH the pre-fix and current
901
+ trees. All three now have discriminating tests and registry entries.
902
+
903
+ Four rules the gate itself obeys — these are what distinguish it from a green-looking script:
904
+
905
+ 1. **A mutation that does not apply is a FAILURE, never a skip** — `find` absent, or present more
906
+ than once, fails loudly (code drifts; a silently-skipped mutation reports a protection as proven
907
+ having tested nothing — the `dz skills-verify` lesson: inconclusive ≠ pass).
908
+ 2. **A green suite under mutation is a FAILURE** that names WHICH property is undefended.
909
+ 3. **The working tree is never mutated.** The package is copied into a scratch shadow of its repo
910
+ (siblings symlinked, `node_modules` linked, the copy git-initialized so hygiene tests keep
911
+ working); the baseline suite must be green there BEFORE any mutation, and a red baseline is a
912
+ setup error (exit 2), never a mutation result.
913
+ 4. **The gate carries its own discrimination proof**: a fixture package with a deliberately
914
+ undefended property lives in this repo's test suite, and the gate MUST fail on it — a gate that
915
+ cannot fail cannot pass.
916
+
917
+ When to reach for it: after a QE round names safety properties (seed the registry so they STAY
918
+ defended); in CI for a package whose protections have burned you before (`npm run test:mutation`);
919
+ and in feature-adr Step 8, where the QE reviewer runs it whenever the touched package has a
920
+ registry — the Step-8 assertion is now "the safety property the ADR names has a test that
921
+ DISCRIMINATES", not merely "has a test".
922
+
923
+ Verdicts per entry: `PROVEN` (red, count ≥ `minFailing`) · `UNDEFENDED` (green — the failure this
924
+ gate exists for) · `NOT_APPLIED` (find-text absent/ambiguous) · `BELOW_MIN` (red but a reliable
925
+ count under the entry's `minFailing` contract) · `MUTATION_UNPARSEABLE` (the mutated file no
926
+ longer parses — structural redness, a registry error) · `MUTATION_LOAD_FATAL` (THE SUITE RUN'S OWN
927
+ OUTPUT reports a test FILE failing to load under the mutation — a file-named `node --test` TAP
928
+ point carrying `exitCode:`/`signal:` fields, or a vitest `Failed Suites` entry — as opposed to
929
+ assertion failures inside running tests; the signal comes from the SAME run that produced the
930
+ failing count, never from a separate isolated import, so there is no environment mismatch to
931
+ false-PASS through) · `OVER_FAILING` (a reliable count far
932
+ above the entry's bound — the mutation broke much more than the protection's own tests) ·
933
+ `INCONCLUSIVE` (no exit code — timeout/kill — or red output whose SHAPE matches no runner the
934
+ classifier knows — jest, mocha, an opaque wrapper: a loud runner-coverage gap, never a silent
935
+ pass — or a restored baseline that did not reproduce
936
+ green; a failure, never a pass). Rule-3 containment is enforced on REALPATHS: an entry whose file
937
+ resolves outside the scratch copy (a symlink escape — writing would mutate the REAL tree) is
938
+ refused with exit 2, and a registry `file` under `node_modules/` is refused outright.
939
+ The red/green verdict rides the suite's EXIT CODE; failing-test counts are
940
+ a best-effort secondary (TAP `# fail N`, vitest/jest summaries) used only for `BELOW_MIN` and the
941
+ COVERAGE-DROP warning — count-parse failure never decides a verdict. A drop (`failing < observed`
942
+ but still ≥ `minFailing`) WARNS loudly instead of failing: `observed` is history, `minFailing` is
943
+ the contract; history drifting down while the contract holds is the early warning that arrives
944
+ before the property breaks.
945
+
841
946
  ### Portable delivery gate — `dz delivery-check` (Step-10, on every shell target)
842
947
 
843
948
  The feature-adr Step-10 Delivery Gate reviews a landed feature across four orthogonal planes and
@@ -988,7 +1093,76 @@ Get the whole set with `dz init --target claude-code --preset meta`, or pick one
988
1093
 
989
1094
  > **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.
990
1095
 
991
- ## All Commands (63)
1096
+ ## Design custom Workflow loops (`workflow` · `workflow-lint` · `workflow-trace`)
1097
+
1098
+ Custom loops used to be born by copy-pasting a 1470-line workflow script; nothing deterministic
1099
+ checked the copy. The loop-designer meta-factory replaces that: a versioned typed plan
1100
+ (`loop-plan/1`), a schema-driven generator, a 17-rule lint gate, and a local trace plane.
1101
+
1102
+ ```bash
1103
+ # 1. Scaffold a plan (pipeline | barrier | fanout | gate), edit the TODO prompts:
1104
+ dz workflow init --name triage-loop --pattern barrier --o triage-loop.plan.json
1105
+ # → dz workflow init: wrote /abs/triage-loop.plan.json (pattern: barrier)
1106
+
1107
+ # 2. Validate it (CI-runnable; INV-1..8 — unbounded fanout, dangling deps, retry-on-non-idempotent
1108
+ # are all hard failures):
1109
+ dz workflow validate triage-loop.plan.json
1110
+ # → dz workflow validate: OK (digest sha256:1df9e5582f8ec888…)
1111
+
1112
+ # 3. Render the executable loop (sidecar plan first, then the script; USER regions survive
1113
+ # re-renders byte-for-byte; a marker-less hand-written target is REFUSED without --force):
1114
+ dz workflow render triage-loop.plan.json --o triage-loop.js
1115
+ # → dz workflow render: wrote …/triage-loop.plan.json then …/triage-loop.js (exec-fp sha256:…, blobs: checkpoints, trace)
1116
+
1117
+ # 4. Gate it (generated-loop CI mode; exit 0/1/3 — inconclusive is NEVER a pass):
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 17 rules)
1120
+
1121
+ # 5. Run it via Workflow({scriptPath:'triage-loop.js', args:{traceDir:'/abs/run-dir'}}), then read
1122
+ # the run's own trace (seq-ordered; wallTime is diagnostic only):
1123
+ dz workflow-trace /abs/run-dir --invariants triage-loop.plan.json --html report.html
1124
+ # → run fitness-run-1; sources: trace, … + INVARIANT PASS rows + a self-contained report.html
1125
+ ```
1126
+
1127
+ **When to use:** any new multi-agent Workflow loop (a pipeline over items, blind parallel lanes
1128
+ behind a barrier, a bounded fanout, a gated produce-check loop) — instead of copy-pasting
1129
+ `feature-adr.js`. `dz workflow-lint --legacy` also runs over hand-written loops (plan-anchored
1130
+ rules honestly report `inconclusive` there, never a silent green). `dz workflow blobs --check`
1131
+ self-checks the shared-subsystem blob registry (checkpoints, model-resolver, trace, …) that the
1132
+ generator injects verbatim — edit the canonical TS in harness-core, regenerate, never the copies.
1133
+
1134
+ **Scope, stated plainly: `dz` AUTHORS, GATES and READS loops — it never RUNS one.** There is no
1135
+ `dz workflow run`, and step 5 above is not a `dz` command: execution belongs to the Claude Code
1136
+ host's `Workflow({scriptPath})` runtime, which owns the agent dispatch the generated script calls
1137
+ into. So every claim on this page is about the plan, the generated script, the lint verdict, and the
1138
+ run's own trace file — not about runtime behaviour `dz` could observe itself. `dz workflow-trace`
1139
+ reads what a HOST run already wrote (`trace.jsonl`, seq-ordered by the loop's own counter); with no
1140
+ host run there is nothing to read, and it says so rather than inventing a timeline. Portability
1141
+ follows from the same boundary: on a non-Claude-Code target the authoring and lint verbs work
1142
+ unchanged, and only execution is absent.
1143
+
1144
+ **The v1 plan surface is deliberately NARROW and fully enacted** — `dz workflow validate` REJECTS
1145
+ (named diagnostics, never a silent no-op) anything the generated loop would not perform: retry
1146
+ timing (`initialDelayMs`/`backoffMultiplier`/`maxDelayMs`/`jitter` — v1 retries are immediate;
1147
+ `ENACT-RETRY-TIMING`), non-inline dispatch routes (`codex-wrapper`/`codex-exec`;
1148
+ `ENACT-DISPATCH`), per-step checkpoint granularity and `checkpointing.schemaVersion`
1149
+ (`ENACT-CKPT-OPT` — checkpointing is all-or-nothing per run, schema stamp pinned), plus caching,
1150
+ jitterless subsystems and branch schemas as before (`ENACT-*`). It also enforces required-field
1151
+ presence per record kind (an absent `fanouts[].stage` is a parse error, not a zero-agent run),
1152
+ unique stepIds that lower to DISTINCT generated identifiers (`a-b` and `a.b` no longer collide in
1153
+ the generated script — the lowering is collision-resistant, not injective, and `IDENT-1` is the
1154
+ parse check that actually rejects a collision),
1155
+ and dependency order at EFFECTIVE execution positions (a fanout member executes at its region's
1156
+ position, not its declaration index). The schema is CLOSED-WORLD: an unknown non-`x-` key is a
1157
+ parse error at every level (so a typo like `trcae`, or a second spelling like `retry.delayMs` /
1158
+ `dispatchRoute`, fails loudly instead of parsing and doing nothing), `x-` vendor keys are accepted
1159
+ only at their documented scopes (plan + step), and `fanouts[].registry` items must live in the same
1160
+ ItemKey domain the trace plane enforces — `trace.emit` can never decide whether a valid plan runs.
1161
+ Deferred options are on the loop-designer roadmap.
1162
+
1163
+ ## All Commands (66)
1164
+
1165
+ *(66 MEASURED — reproducer: `grep -c "^ case '" src/cli.ts`, the dispatch cases.)*
992
1166
 
993
1167
  ```
994
1168
  dz setup --target <name> [--preset <name>] [--select id,id,...] [--skills-dir <dir>] [--memory agentdb] [--no-memory] [--no-hooks] [--install-driver] [--force]
@@ -1000,7 +1174,7 @@ dz teach --reinforce "<dzId-or-exact-text>" [--project <dir>] # bump an existing
1000
1174
  dz teach --from-json <file> [--project <dir>] [--no-mirror] [--harmonize] # bulk-import a `dz recall --all --json` export; prints a harmonize dry-run advisory
1001
1175
  dz consolidate [--sessions-dir <dir>] [--project <dir>] [--no-mirror]
1002
1176
  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)
1003
- dz recall --all [--json] [--stats] # export the WHOLE learned store, or inspect learning stats/top uses
1177
+ dz recall --all [--json] [--stats] [--include-domain <name,…>] # export the learned store (held-out domains are withheld unless named), or inspect learning stats/top uses
1004
1178
  dz recall --books "<query>" [--book <slug>] # digitized-book KUs; --book narrows to one book
1005
1179
  dz vector status [--project <dir>] [--json] # semantic tier: engine availability, mirrored vs lexical counts, pending queue
1006
1180
  dz vector reindex [--project <dir>] [--json] # re-embed learned vectors with the CONFIGURED model + stamp the manifest (snapshot first; atomic — a mid-way failure restores the store). Warns about task types it does not own (e.g. book-knowledge → run `dz brain reindex`)
@@ -1044,6 +1218,7 @@ dz challenge --plan <plan.md> [--json] [--context-only] [--author <model>] # a
1044
1218
  dz routing [--stage <s>] [--json] # inspect the learned cost-optimal routing store: what `args.models.<stage>='auto-cost'` believes per (stage, complexity-tier, model) — gated attempts/successes/rate (feeds feature-adr model selection)
1045
1219
  dz bto-optimize --split|--plan|--select|--scope-check|--diff [--json] # deterministic engine behind /bto-optimize: hold-out split + hard-capped budget + no-regress-on-holdout winner selection (defeats judge-gaming); prose-only, diff-confirmed, never auto-writes
1046
1220
  dz discrimination-check --test <f[,f]> [--base <ref>] [--name <filter>] [--runner <cmd>] [--json] # §42 test-discrimination gate for feature-adr Step-8: run the ADR's property test in an isolated git worktree at pre-feature base — it MUST go red without the fix; a green is a false green (HIGH finding, advisory, never auto-aborts)
1221
+ dz mutation-gate [--package <dir>] [--registry <file>] [--test-cmd "<cmd>"] [--only <id[,id]>] [--timeout <ms>] [--rebaseline per-entry|final] [--keep-scratch] [--json] # the mutation gate: for each NAMED protection in a declarative registry, copy the package to a scratch dir (shadow-repo layout, node_modules symlinked, git-initialized), verify the baseline is green, apply the entry's exact {find, replace} mutation, run the suite, REQUIRE red, restore — and require the red to be ATTRIBUTABLE to the protection: a mutated file that no longer parses is MUTATION_UNPARSEABLE, a failing count far above the entry's bound (maxFailing, default from observed) is OVER_FAILING, and a restored tree that does not reproduce green makes the entry INCONCLUSIVE (flaky suite). A mutation that does not apply, a green suite, or an inconclusive run is a FAILURE — never a skip. exit 0 all proven / 1 gate failed / 2 setup error
1047
1222
  dz delivery-check --slug <slug> [--context-only] [--findings <f.json>] [--strict] [--author <model>] [--json] # portable Step-10 Delivery Gate: the `manual` form that travels to every shell target — prints the 4-plane review brief (regressions ‖ security ‖ code-quality ‖ product-honesty) + artifact probes; --findings classifies a fed-back review into a fail-closed ready|blocked hand-off (only cross-validated BLOCKER/HIGH count) and writes features/<slug>/10_delivery_review.md; --strict exits 1 on blocked
1048
1223
  dz skills-verify [--dir <project>] [--expect a,b] [--static] [--strict] [--timeout <s>] [--json] # does .claude/skills/ actually REGISTER? --static = instant layout scan (CI-safe, no session): flags dirs that can never register; default also starts a real session and reads the authoritative system/init listing. exit 0 pass / 1 fail / 2 inconclusive — an unobservable registration is NEVER a pass
1049
1224
  dz compounding [--project <dir>] [--json] # honest learning-loop payoff report: pool write-only ratio, guard repeat-violation trajectory (rate per audit-half), cold-vs-warm replay readiness over unique untruncated prompt events, apply-leg staleness, and EVENT-CHAIN health of the two evidence logs (verified / defects / uncovered pre-chain prefix) — a gate without enough data says INSUFFICIENT_DATA, never a verdict
@@ -1053,7 +1228,7 @@ dz epoch-replay --judge <filled-work-order.json> [--out <file>] [--json] # bli
1053
1228
  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
1054
1229
  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
1230
  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
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)
1231
+ dz backlog <sub> add "<idea>" | list | show <id> | goals [--validate] | roulette [--seed n] [--commit <id>] | ship <id…> | drop <id…> | reopen <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 (two-signal: bounded-excerpt cosine DUPLICATE≥0.92 corroborated by shared subject vocabulary — a register-only 0.94 is demoted to RELATED, a length-only re-capture is caught as a subset duplicate; absorbed texts kept in absorbed.jsonl) + 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)
1057
1232
  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
1058
1233
  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
1059
1234
  dz guard check --op <publish|teach|consolidate|reindex> [--text <s>] [--json] [--force <reason>] # declarative constraint layer before self-mutating ops: HARD violation → block (exit 1), SOFT → warn; zero-config defaults, .dz/guard.json to customise; dz guard --init | dz guard log (append-only audit). dz publish runs it automatically (--no-guard "<reason>" = logged escape hatch)
@@ -1067,7 +1242,9 @@ dz sync-upstream [--package <dir>] [--list] [--all]
1067
1242
  dz drift-check [--all] [--json] [--project <dir>] # CI gate: exit 1 on NEW shared-skill drift (baseline: .dz/drift-allowlist.json; --all incl .claude dogfood)
1068
1243
  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
1069
1244
  dz scout [--topics <list>] [--since <date>] [--deep] [--output <file>] [--diff] [--report]
1070
- dz workflow --task <name> [--dry-run]
1245
+ 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)
1246
+ dz workflow-lint <script.js> [--plan <plan.json>] [--require-plan|--legacy] [--json] # 17-rule deterministic gate; exit 0/1/3 — inconclusive is never a pass
1247
+ 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
1071
1248
  dz plugin [--version <ver>]
1072
1249
  dz downloads
1073
1250
  dz migrate [--project <dir>]
@@ -1126,6 +1303,38 @@ distills new learnings. It's **opt-in** and **non-clobbering**: `--install` merg
1126
1303
  existing `statusLine` config rather than overwriting it, and nothing changes until you run it.
1127
1304
  Modeled on the Agentic QE statusline pattern (e.g. AQE's `🎓 12 patterns`).
1128
1305
 
1306
+ #### Live run segment — `dz statusline --fa-record`
1307
+
1308
+ A long-running pipeline can PREPEND its own segment to that line, so the bar shows what is in
1309
+ flight rather than only the standing pattern count. The `/feature-adr` pipeline records its
1310
+ Pattern-memory state at each step; a generated `loop-designer` loop records its current step on
1311
+ every trace flush:
1312
+
1313
+ ```bash
1314
+ # the /feature-adr pipeline — reports the learning loop it is actually running
1315
+ dz statusline --fa-record --slug add-user-auth --step "Step 8 QE" --recalled 7 --stored 2 --mode full
1316
+
1317
+ # a generated loop — same file, different producer, so it says so
1318
+ dz statusline --fa-record --slug my-loop --step build --kind loop
1319
+ ```
1320
+
1321
+ ```
1322
+ 📐 feature-adr Step 8 QE · 🎓 224 pool · ↑7 used · +2 new · ↻0 reinforced · 🎓 dz: 224 patterns · 🧠 3 sources
1323
+ 🔁 loop build · 🎓 dz: 224 patterns · 🧠 3 sources
1324
+ ```
1325
+
1326
+ `--kind <feature-adr|loop>` names the PRODUCER and defaults to `feature-adr`; an unrecognised value
1327
+ exits 1 rather than silently weakening panel arbitration. It is load-bearing in two places:
1328
+
1329
+ - **the label**: a loop renders `🔁 loop <step>` and shows **no** learning counters, because a loop
1330
+ never populates them — a `↑0 used` there would be a false statement about the Pattern-memory loop
1331
+ rather than a measurement of it;
1332
+ - **the arbitration**: each producer writes its own per-slug slot under
1333
+ `.dz/feature-adr/learning-state/`, and a fresh `feature-adr` state outranks any `loop` state, so a
1334
+ loop running alongside a live pipeline can no longer blank that pipeline's counters. Slots are
1335
+ discovered newest-first, nothing older than 30 minutes is surfaced, and the write path prunes at
1336
+ 24 h. Use `--project <dir>` to pin the panel to a specific project root.
1337
+
1129
1338
  ### Usage estimate (`dz usage`)
1130
1339
 
1131
1340
  `dz usage` prints a READONLY, never-throw ESTIMATE of Claude SESSION and WEEKLY token usage,
@@ -1457,6 +1666,18 @@ dz backlog add "add a compounding metric to strengthen dz recall" --effort 3
1457
1666
  # A RELATED/DUPLICATE verdict now names the pair, so you can calibrate the 0.92 band on real data:
1458
1667
  # dz backlog: RELATED — captured 67c5883e…
1459
1668
  # top match 9286f5eb… @ cosine 0.793 (DUPLICATE band ≥ 0.92)
1669
+ # Dedup is TWO-SIGNAL (register-inflation fix, measured on the real store): the embedding compares a
1670
+ # BOUNDED 400-char excerpt (full-length embeds of long same-register texts scored unrelated pairs up
1671
+ # to 0.9195 while true paraphrases of the same ideas scored 0.35–0.61 — the signal was inverted), and
1672
+ # a DUPLICATE verdict additionally needs shared subject vocabulary (lexical containment ≥ 0.3):
1673
+ # near-duplicate demoted: 977e9dcb… @ cosine 0.941 cleared the band but shares no subject
1674
+ # vocabulary (containment 0.077 < 0.3) — kept as related
1675
+ # The same containment signal works the other way too: a re-capture of an existing idea at a
1676
+ # different LENGTH (containment ≥ 0.95, cosine ≥ 0.75) is a duplicate even below the 0.92 band
1677
+ # ("subset match"). Knobs: backlog.dedup.{corroborationFloor,subsetContainment,subsetCosineFloor}
1678
+ # in .dz/config.json. On first use after the fix the mirrored idea vectors are re-embedded once into
1679
+ # the bounded form ("re-embedded N idea vector(s)…"); a DUPLICATE absorption also keeps the full
1680
+ # incoming text in .dz/backlog/absorbed.jsonl, so a wrong verdict is reversible (re-add from there).
1460
1681
  # An out-of-scale --effort is ECHOED, never silently clamped:
1461
1682
  dz backlog add "an idea I mis-scaled" --effort 13
1462
1683
  # → dz backlog: effort 13 → clamped to 5 (scale 1-5)
@@ -1484,6 +1705,19 @@ dz backlog roulette --seed 7
1484
1705
  dz backlog roulette --commit 268d3cb1 # → 268d3cb1 · → in-progress
1485
1706
  dz backlog roulette 268d3cb1 # → refused (exit 1): "an idea id alone does not start it"
1486
1707
 
1708
+ # 3c. FINISHED the work? Say so — or the roulette keeps re-drawing it forever. Most work happens
1709
+ # WITHOUT a --commit (spin, see the pick, just do it), so `ship` is the verb that closes the loop.
1710
+ # Short 8-char prefixes (what the roulette prints) resolve if unique; ambiguous/unknown = loud
1711
+ # exit-1 error, never a silent no-op. Batches are all-or-nothing.
1712
+ dz backlog ship 268d3cb1 --reason "shipped in harness-core 0.3.151"
1713
+ # → dz backlog ship: 268d3cb1… new → shipped add a compounding metric …
1714
+ # (shipping an already-shipped idea is a SAID no-op, exit 0 — safe in cleanup batches)
1715
+ dz backlog drop 9286f5eb --reason "superseded by 268d3cb1" # retire without shipping (→ dropped)
1716
+ dz backlog reopen 268d3cb1 # changed your mind → back to the pool (new)
1717
+ # reopen on an already-new idea is REFUSED (exit 1) — that is almost always the wrong id.
1718
+ # Terminal→terminal never happens silently: ship on a dropped idea (or drop on shipped) is refused;
1719
+ # go through reopen first. --dry-run previews any of the three without writing.
1720
+
1487
1721
  # 4. Work it: stage an idea2prd hand-off (writes the PRD-pipeline input scaffold)
1488
1722
  dz backlog enrich 268d3cb1 # → features/<slug>/ scaffold; run the idea2prd-manual skill on it
1489
1723
 
@@ -1505,9 +1739,55 @@ agentdb memory backend installed the dedup honestly degrades to exact-text and a
1505
1739
  dz install @dzhechkov/skills-devops
1506
1740
  dz install @dzhechkov/skills-web3 --target openclaude
1507
1741
  dz install @lythos/skill-curator --target claude-code
1742
+ dz install @dzhechkov/skills-analyst-manual # npx-init pack — works too
1508
1743
  ```
1509
1744
 
1510
- Runs `npm install`, discovers SKILL.md files in the package, copies them to the target platform directory. Works with any agentskills.io-compatible npm package.
1745
+ Runs `npm install`, resolves where the package keeps its skills, and compiles them into the
1746
+ target platform directory. Works with any agentskills.io-compatible npm package, in **any of
1747
+ the three known layouts** (probed in order, first non-empty wins):
1748
+
1749
+ | Layout | Skills live at | Example packs |
1750
+ |---|---|---|
1751
+ | `flat` | `<pkg>/<skill>/SKILL.md` | skills-devops, skills-qe, skills-ecc |
1752
+ | `npx-template` | `<pkg>/templates/.claude/skills/<skill>/SKILL.md` | skills-analyst-manual, keysarium, skills-feature-adr |
1753
+ | `skills-dir` | `<pkg>/skills/<skill>/SKILL.md` | health-advisor, evidence-wiki |
1754
+
1755
+ Real npx-init-pack run (captured):
1756
+
1757
+ ```bash
1758
+ $ dz install @dzhechkov/skills-analyst-manual
1759
+ Installing @dzhechkov/skills-analyst-manual...
1760
+ dz install @dzhechkov/skills-analyst-manual: 4 skill(s), 18 file(s) written, 0 skipped [layout: npx-template]
1761
+ analyst-manual-full: 1 written, 0 skipped
1762
+ explore: 3 written, 0 skipped
1763
+ goap-research-ed25519: 13 written, 0 skipped
1764
+ problem-solver-enhanced: 1 written, 0 skipped
1765
+ note: @dzhechkov/skills-analyst-manual also ships commands/hooks/agents — `npx -y @dzhechkov/skills-analyst-manual init` installs the full kit.
1766
+ ```
1767
+
1768
+ The `[layout: …]` tag appears for non-flat layouts so the resolution is observable; classic
1769
+ flat packs print exactly what they always did. The `note:` line appears only when the pack
1770
+ ships companion assets (slash commands, hooks) that `dz install` does not install — for the
1771
+ full claude-code kit of such a pack, `npx -y <pkg> init` remains the richer path, while
1772
+ `dz install --target codex|cursor|opencode|…` compiles the same skills for the non-claude
1773
+ targets, which the pack's own installer does not do (MEASURED — its installer copies
1774
+ `templates/.claude/**` only; reproducer: `src/utils.js` `COMPONENTS` in
1775
+ `@dzhechkov/skills-analyst-manual`).
1776
+
1777
+ A package with **no** skills in any known layout now fails loudly — `exit 1` with the probed
1778
+ paths named (MEASURED — reproducer: `npx vitest run test/install-layouts.test.ts`, case
1779
+ T5.2; the pre-fix behavior was a misleading `exit 0` advisory):
1780
+
1781
+ ```bash
1782
+ $ dz install @dz/broken-pack
1783
+ Installing @dz/broken-pack...
1784
+ dz install: no SKILL.md found in @dz/broken-pack. Probed: ., templates/.claude/skills, skills.
1785
+ If this package installs itself, try: npx -y @dz/broken-pack init
1786
+ ```
1787
+
1788
+ When to use: first install of any skills pack. If you previously hit
1789
+ `no SKILL.md files found` on an npx-init pack (e.g. `@dzhechkov/skills-analyst-manual`) —
1790
+ that resolver bug is fixed; `dz install` now handles those packs directly.
1511
1791
 
1512
1792
  ### dz sync-upstream — check for upstream updates
1513
1793
 
@@ -2218,7 +2498,7 @@ invariants: HARD rules **block** the operation, SOFT rules warn. Zero config nee
2218
2498
  cover the known rakes; `.dz/guard.json` (via `dz guard --init`) exists only if you want to tune a severity
2219
2499
  or disable a rule.
2220
2500
  ```bash
2221
- dz guard check --op publish # no-workspace-star · no-skill-drift · no-secrets · readme-consistency · readme-first · skills-registrable · lockfile-in-sync
2501
+ dz guard check --op publish # no-workspace-star · no-skill-drift · no-secrets · readme-consistency · readme-first · skills-registrable · lockfile-in-sync · no-stubs · licence-hold
2222
2502
  dz guard check --op teach --text "the fix: export sk-abc..." # → BLOCK (exit 1): looks like a credential
2223
2503
  dz guard log # append-only audit: every verdict + every forced override
2224
2504
  ```
@@ -2253,6 +2533,111 @@ zero specifiers, and then report every real dependency as "not recorded". The ru
2253
2533
  `.dz/guard.json` cannot promote it to HARD, since a parser designed to admit "I may not understand this
2254
2534
  file" must never be able to block a publish.
2255
2535
 
2536
+ **`licence-hold` (HARD, publish)** — the machine side of a licence precondition (ADR-001 of feature
2537
+ `hermes-claude-adaptation`, first holder: `@dzhechkov/cloudru-hub`). A pack that declares
2538
+ `package.json.licenseHold` is parked behind two belts: while it stays `private:true` the npm layer
2539
+ refuses it and this rule stays silent (other packs publish unblocked); the moment `private` is dropped
2540
+ with the hold unsatisfied, publish is HARD-blocked:
2541
+
2542
+ ```
2543
+ [BLOCK] licence-hold: @dzhechkov/cloudru-hub: publishable (private flag removed) but the ADR-001
2544
+ licence hold is UNSATISFIED — LICENSE still carries the PENDING grant placeholder; LICENSE
2545
+ has no "Grant-Confirmation: <url>" line; package.json license "SEE LICENSE IN LICENSE" is
2546
+ not a clean SPDX id
2547
+ ```
2548
+
2549
+ Satisfying the hold — a real LICENSE (no `<!-- PENDING:` placeholder, a `Grant-Confirmation: <url>`
2550
+ line), non-empty `THIRD_PARTY_NOTICES`, and a clean SPDX `license` field — makes the rule pass with the
2551
+ trigger field still in place. Discrimination MEASURED 2026-08-10 (reproducer: flip `private` in
2552
+ `packages/@dzhechkov/cloudru-hub/package.json`, run `dz publish --dry-run` → exit 1 BLOCKED; restore →
2553
+ pass; satisfied scratch copy → pass).
2554
+
2555
+ **`no-stubs` (SOFT, publish)** — a deterministic scan for unfinished-stub markers (`TODO` / `FIXME` /
2556
+ `HACK` / `XXX` / `PLACEHOLDER` / the phrase `implement later`) left in the files of the **current
2557
+ change-set** (backlog 0b403a0106103901, Karpathy-Michaels rule XI). A stub in code you are about to
2558
+ publish means the task shipped incomplete — and until this rule that was caught only by a reviewer's
2559
+ judgment (layer 4 on the cost-of-detection ladder); a grep is layer 1. Scope is the working-tree diff
2560
+ **on purpose**: MEASURED 2026-08-10, a whole-tree case-insensitive scan of this repo yields 32 src/lib
2561
+ hits + 25 test/fixture hits, the clear majority ancient and legitimate — a tree-wide gate would be
2562
+ switched off as noise within a day. The gate catches what *you* left in *this* change. Bare markers are
2563
+ matched **case-sensitively** with hard word boundaries (measured: relaxing case doubles the hits and
2564
+ adds only prose — `todo` keyword lists, "placeholder" in comments), so `hackathon`, `todos`, and a
2565
+ marker inside a hash never fire; in markdown, fenced blocks and backticked spans are treated as QUOTES
2566
+ of a marker, not stubs, so a doc explaining this very gate scans clean.
2567
+
2568
+ ```bash
2569
+ $ printf '// %s: finish the retry path\n' 'TODO' >> src/retry.ts
2570
+ $ dz guard check --op publish
2571
+ dz guard (publish): ⚠ WARN [checked: … no-stubs …]
2572
+ [warn] no-stubs: src/retry.ts:1: stub marker "TODO" in a changed file — finish it, or waive the
2573
+ line with "no-stubs: <reason>" (or .dz/guard.json stubWaivers with a reason)
2574
+ $ echo $? # SOFT means SOFT: the publish path completes
2575
+ 0
2576
+ ```
2577
+
2578
+ A marker a change must legitimately carry (a fixture exercising another scanner, a demo) takes an
2579
+ explicit **waiver-with-reason** — inline on the line (`// … no-stubs: fixture exercises the S10 check`)
2580
+ or path-keyed in `.dz/guard.json` (`"stubWaivers": [{"path": "test/fixtures/x.json", "reason": "…"}]`,
2581
+ the same shape as `dz feature-adr-setup --guards`). A waiver **without** a reason is refused loudly and
2582
+ exempts nothing — an exemption you cannot explain is a silent allowlist:
2583
+
2584
+ ```
2585
+ [warn] no-stubs: src/retry.ts:1: inline waiver ("no-stubs:") carries NO reason — refused; a waiver
2586
+ you cannot explain is a silent allowlist
2587
+ [warn] no-stubs: src/retry.ts:1: stub marker "TODO" in a changed file — …
2588
+ ```
2589
+
2590
+ The rule's own source never trips it **structurally**: every marker in `no-stubs.ts` (and its tests) is
2591
+ assembled from string fragments at load time, so the file contains no literal for the scan to find — a
2592
+ tested property, not a path skip. The same check runs as a Step-8 QE-gate item in the feature-adr
2593
+ pipeline ("any unwaived match over the run's touched files = task incomplete").
2594
+
2595
+ The change-set includes files inside **brand-new untracked directories**: the gatherer runs
2596
+ `git status --porcelain -uall`, so a fresh module directory — the most stub-prone artifact there is —
2597
+ is scanned file-by-file instead of collapsing to one invisible `?? newdir/` line (FN-1, REPRODUCED:
2598
+ before `-uall` a marker in `newdir/stub.ts` published as PASS/0 findings). `.gitignore` semantics are
2599
+ unchanged — git never lists ignored paths, `-uall` or not (tested live).
2600
+
2601
+ **Fail-open, never fail-silent.** A changed scannable file the gatherer does NOT read — deleted,
2602
+ non-regular, over 1 MB, unreadable, or beyond the 400-file cap — still reports no findings (missing
2603
+ evidence is never guessed), but the skip now goes **on the record** as one aggregate note in the
2604
+ verdict, the `--json` output, and the audit log; it is information, never a violation, and cannot
2605
+ change the verdict or the exit code:
2606
+
2607
+ ```
2608
+ dz guard (publish): ✓ PASS [checked: … no-stubs …]
2609
+ [note] no-stubs: 1 changed scannable file(s) not scanned (deleted/oversize/unreadable/beyond the
2610
+ file cap) — the stub scan is fail-open, so this is a coverage gap on the record, not a violation
2611
+ ```
2612
+
2613
+ **Known limits** (conscious trade-offs, named rather than implied away — each is also documented at
2614
+ the top of `no-stubs.ts`):
2615
+
2616
+ - **Whole-line inline waiver (FN-2).** The `no-stubs: <reason>` token exempts the *entire line* it
2617
+ appears on — any line can be silenced by appending the token. The defence is auditability, not
2618
+ prevention: the token is a fixed greppable string, so every silencing is one grep away. That is a
2619
+ layer-4 defence on the cost-of-detection ladder, and it is named as such deliberately.
2620
+ - **Reason quality is not judged (FN-6).** A junk reason (`.`, `x`) satisfies the reason requirement.
2621
+ The design stops *forgetting* a reason, not *faking* one — a deterministic layer cannot judge honesty;
2622
+ that is review-plane work.
2623
+ - **Markdown fence model (FN-4).** Fence tracking is a single boolean toggle, not CommonMark: a
2624
+ mismatched `~~~`/backtick fence pair, an indented fence, an unclosed fence at EOF (everything after it
2625
+ reads as fenced ⇒ skipped), and a marker on the fence info-string line are all mis-scoped.
2626
+ - **Git-quoted paths (FN-3).** Paths git quotes (spaces, non-ASCII under the default
2627
+ `core.quotePath=true`) are not decoded, match no real file, and are not scanned.
2628
+ - **Extension allowlist (FN-8).** `STUB_SCAN_EXTENSIONS` is a TS-monorepo set: extensionless bin
2629
+ scripts, `Dockerfile`, `Makefile`, `.txt/.html/.css/.vue/.svelte/.c/.cpp/.php/.toml/.sql/.ps1/.kt/.swift`
2630
+ are unscanned — MED risk if this rule template ships into a polyglot repo (the list is exported and
2631
+ testable precisely so it can be extended there).
2632
+ - **Staged-but-not-worktree content (FN-5).** The gatherer reads the working tree; content staged in the
2633
+ index but reverted in the worktree scans as the worktree text — design-consistent for the `publish`
2634
+ op, which packs the worktree.
2635
+ - **Config waivers match the exact path string** — no normalization, no globs; a mis-spelled waiver
2636
+ simply does not match and the finding fires (errs toward firing — the safe side).
2637
+ - **A status listing beyond the raised 32 MB `maxBuffer`** makes the porcelain call throw, which drops
2638
+ the whole `change` fact — no-stubs and every promoted template rule go fail-open together for that
2639
+ run (the standing not-a-git-repo contract; the bound makes it a pathological-tree-only event).
2640
+
2256
2641
  #### `dz guard promote` — when a LESSON has earned the right to become a rule
2257
2642
 
2258
2643
  `dz compounding` MEASURED the uncomfortable half of the learning loop (reproducer: `dz compounding
@@ -2387,6 +2772,38 @@ cut hid. That last one is the subtle part: the boost never drops a hit, but prom
2387
2772
  the top N does push the last one out of the printed list, so "nothing was dropped" would be true of
2388
2773
  the ranked list and false of the one you are reading. The note names the difference instead.
2389
2774
 
2775
+ ### Held-out domains — the export does not hand over everything
2776
+
2777
+ `dz recall --all --json` is the portable sharing form, which makes it the realistic path
2778
+ by which a learned store leaves a machine. Some domains should not travel by default:
2779
+ `health-research` (written by `@dzhechkov/health-advisor`) carries lessons drawn from one
2780
+ person's medical investigations.
2781
+
2782
+ ```bash
2783
+ dz recall --all --json
2784
+ # … 178 patterns on stdout, health-research NOT among them
2785
+ # 1 lesson(s) in health-research were HELD BACK from this export — that domain carries
2786
+ # medical research and does not travel by default. To include it deliberately:
2787
+ # --include-domain health-research ← on stderr, so stdout stays parsable
2788
+
2789
+ dz recall --all --json --include-domain health-research # explicit opt-in
2790
+ ```
2791
+
2792
+ **Why a domain tag and not a content scan.** The tag is set by the writer and compared
2793
+ after normalisation (`Health_Research` and `health research` are the same domain), so the
2794
+ check is decidable, language-independent, and provable by a test. `health-advisor` first
2795
+ tried to keep patient data out by INSPECTING the lesson text; seven rounds of
2796
+ cross-model review could not make that correct, because "is this about a person?" is a
2797
+ question about meaning. It then filtered each command that emits lesson text, and review
2798
+ produced five more such commands the moment four were closed. What finally held is
2799
+ neither: health lessons are written to a SEPARATE store and never reach this one. The
2800
+ hold-out remains as a second line — and note what it does NOT claim: it governs the
2801
+ export, not the local store, and it does not make any lesson safe to publish.
2802
+
2803
+ The hold-out always says how much it withheld. A silent one would let you believe you had
2804
+ exported the whole store, and would make a BROKEN hold-out look exactly like an empty
2805
+ domain.
2806
+
2390
2807
  ### Semantic recall (vector tier)
2391
2808
 
2392
2809
  `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.
@@ -2805,7 +3222,7 @@ npx @dzhechkov/p-replicator init
2805
3222
 
2806
3223
  ## Status
2807
3224
 
2808
- `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).
3225
+ `v0.4.2` — published on npm. Also available as [Claude Plugin](#claude-plugin). Part of [DZ Harness Hub](https://github.com/djd1m/dz-harness-hub).
2809
3226
 
2810
3227
  ## Claude Plugin
2811
3228
 
package/dist/bin.js CHANGED
@@ -3,5 +3,15 @@
3
3
  * `dz` executable entry point.
4
4
  */
5
5
  import { runCli } from './cli.js';
6
- process.exit(await runCli(process.argv.slice(2)));
6
+ const code = await runCli(process.argv.slice(2));
7
+ // `process.exit()` here TRUNCATED large output on a pipe, silently and at exactly the
8
+ // pipe buffer size. On a pipe (not a TTY, not a file) Node's stdout is ASYNCHRONOUS, so
9
+ // `process.exit` discards whatever has not flushed yet: `dz recall --all --json > file`
10
+ // wrote 122826 bytes while `dz recall --all --json | jq` got exactly 65536 and a parse
11
+ // error. That is data loss in the documented sharing path, and silent — the exit code
12
+ // was 0 and the JSON simply stopped mid-string.
13
+ //
14
+ // So: set the code and let the process end when the event loop drains, which is what
15
+ // flushes stdout. `process.exitCode` preserves the status without the race.
16
+ process.exitCode = code;
7
17
  //# sourceMappingURL=bin.js.map
package/dist/bin.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA;;GAEG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC,OAAO,CAAC,IAAI,CAAC,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA;;GAEG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AAEjD,sFAAsF;AACtF,wFAAwF;AACxF,wFAAwF;AACxF,uFAAuF;AACvF,sFAAsF;AACtF,gDAAgD;AAChD,EAAE;AACF,qFAAqF;AACrF,4EAA4E;AAC5E,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC"}
package/dist/cli.d.ts CHANGED
@@ -20,6 +20,13 @@ export interface CliIo {
20
20
  * without spawning anything.
21
21
  */
22
22
  readonly releaseRunner?: ReleaseExecRunner;
23
+ /**
24
+ * Test seam for `dz install`: overrides the `npm install` subprocess (production leaves
25
+ * it unset → real `execSync`, stdio piped). A stub runner that pre-stages a fixture
26
+ * package under `node_modules/` makes `cmdInstall`'s layout resolution testable
27
+ * offline, hermetically — mirrors the {@link CliIo.releaseRunner} idiom.
28
+ */
29
+ readonly installRunner?: (command: string, cwd: string) => void;
23
30
  }
24
31
  /** Injected subprocess runner used by `dz release` (see {@link CliIo.releaseRunner}). */
25
32
  export type ReleaseExecRunner = (cmd: string, opts: {
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;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;AA83O9E,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA8J5E"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAkbH,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;AAyyQ9E,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAoK5E"}