@dzhechkov/harness-cli 0.7.7 → 0.7.9
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 +7 -7
- package/README.md +64 -9
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +66 -14
- package/dist/cli.js.map +1 -1
- package/package.json +6 -6
- package/sbom.json +6 -6
- package/src/cli.ts +68 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dzhechkov/harness-cli",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.9",
|
|
4
4
|
"description": "The dz CLI — install AI skills for Claude Code, Codex, OpenCode, Hermes, OpenClaude, GitHub Copilot. 67 commands, 14 presets, 10 platform targets.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -41,22 +41,22 @@
|
|
|
41
41
|
"sbom.json"
|
|
42
42
|
],
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@dzhechkov/harness-core": "^0.7.
|
|
44
|
+
"@dzhechkov/harness-core": "^0.7.12",
|
|
45
45
|
"@dzhechkov/harness-presets": "^0.5.14",
|
|
46
46
|
"@dzhechkov/scout": "^0.8.7",
|
|
47
47
|
"@dzhechkov/skills-devops": "^0.3.13",
|
|
48
48
|
"@dzhechkov/skills-mcp": "^0.3.6",
|
|
49
49
|
"@dzhechkov/skills-web3": "^0.2.5",
|
|
50
50
|
"@dzhechkov/skills-qe": "^0.3.10",
|
|
51
|
-
"@dzhechkov/skills-academic": "^0.3.3",
|
|
52
51
|
"@dzhechkov/skills-meta": "^0.9.51",
|
|
52
|
+
"@dzhechkov/skills-academic": "^0.3.3",
|
|
53
53
|
"@dzhechkov/skills-ecc": "^0.1.8",
|
|
54
|
-
"@dzhechkov/skills-
|
|
54
|
+
"@dzhechkov/skills-news": "^0.1.12",
|
|
55
55
|
"@dzhechkov/skills-taste": "^0.1.1",
|
|
56
|
+
"@dzhechkov/skills-pm": "^0.1.1",
|
|
56
57
|
"@dzhechkov/skills-idea2prd": "^0.1.11",
|
|
57
|
-
"@dzhechkov/skills-reverse-engineering": "^0.1.9",
|
|
58
58
|
"@dzhechkov/skills-presentation-storyteller": "^0.1.10",
|
|
59
|
-
"@dzhechkov/skills-
|
|
59
|
+
"@dzhechkov/skills-reverse-engineering": "^0.1.9",
|
|
60
60
|
"@dzhechkov/skills-website-cloner": "^0.1.1"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
package/sbom.json
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"hashes": [
|
|
26
26
|
{
|
|
27
27
|
"alg": "SHA-256",
|
|
28
|
-
"content": "
|
|
28
|
+
"content": "07e1f395043324fe72c1170406340df3ebdb1dbabde64d80dc8124269a0cb5a6"
|
|
29
29
|
}
|
|
30
30
|
]
|
|
31
31
|
},
|
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
"hashes": [
|
|
126
126
|
{
|
|
127
127
|
"alg": "SHA-256",
|
|
128
|
-
"content": "
|
|
128
|
+
"content": "947a13984fe9e992098c4d7b63303b6048f30d7ff0f509b666022e1c8c67cd54"
|
|
129
129
|
}
|
|
130
130
|
]
|
|
131
131
|
},
|
|
@@ -135,7 +135,7 @@
|
|
|
135
135
|
"hashes": [
|
|
136
136
|
{
|
|
137
137
|
"alg": "SHA-256",
|
|
138
|
-
"content": "
|
|
138
|
+
"content": "d539a0084dced536df3edadd79bcce94d1cb7205d1e534922fd8a1ca37c7fc7d"
|
|
139
139
|
}
|
|
140
140
|
]
|
|
141
141
|
},
|
|
@@ -145,7 +145,7 @@
|
|
|
145
145
|
"hashes": [
|
|
146
146
|
{
|
|
147
147
|
"alg": "SHA-256",
|
|
148
|
-
"content": "
|
|
148
|
+
"content": "b3b37c4d27ea28e00da2f6d1bc438e22ebb48da8dec365e9f4128071647c3dd0"
|
|
149
149
|
}
|
|
150
150
|
]
|
|
151
151
|
},
|
|
@@ -335,7 +335,7 @@
|
|
|
335
335
|
"hashes": [
|
|
336
336
|
{
|
|
337
337
|
"alg": "SHA-256",
|
|
338
|
-
"content": "
|
|
338
|
+
"content": "98583e52265eb3951c2596f652f5046cc2044e5a4c12353ea1a380a037819778"
|
|
339
339
|
}
|
|
340
340
|
]
|
|
341
341
|
},
|
|
@@ -365,7 +365,7 @@
|
|
|
365
365
|
"hashes": [
|
|
366
366
|
{
|
|
367
367
|
"alg": "SHA-256",
|
|
368
|
-
"content": "
|
|
368
|
+
"content": "e13c80580e477748ab072078f3b90e9301ad60b01106e228501f16bc753f994a"
|
|
369
369
|
}
|
|
370
370
|
]
|
|
371
371
|
},
|
package/src/cli.ts
CHANGED
|
@@ -302,6 +302,9 @@ import {
|
|
|
302
302
|
renderContentProbe,
|
|
303
303
|
findNonRegistrableSkillDirs,
|
|
304
304
|
assembleCompoundingReport,
|
|
305
|
+
banditStats,
|
|
306
|
+
narrowBanditReport,
|
|
307
|
+
renderBanditHealth,
|
|
305
308
|
// Cold-vs-warm EPOCH RUNNER (feature epoch-replay) — orchestrates + scores, never calls a model.
|
|
306
309
|
replayableInstances,
|
|
307
310
|
buildWorkOrder,
|
|
@@ -518,6 +521,11 @@ Usage:
|
|
|
518
521
|
dz tg-post --draft <file.html> [--manifest <sources.json>] [--channel <@name|id>] [--send --yes] [--night] [--preview] [--json] (the sender for an APPROVED channel post, per the accepted genai-tweets-channel ADRs: HTML mode only — never MarkdownV2; link preview OFF by default (x.com previews in Telegram are broken); the 00:00-06:00 MSK quiet window refuses without an explicit --night. DEFAULT IS A DRY-RUN: it validates the draft (tag balance, allowed tags, bare &/<, the 4096 visible-character limit with the overshoot counted) and runs the provenance gate over --manifest IN-PROCESS — a draft with no manifest is refused as unchecked, and anything but ALLOWED refuses. A real send needs --send --yes, stating ADR-004's manual-publishing decision out loud each time. The token comes from TELEGRAM_BOT_TOKEN or telegram.tokenFile in .dz/config.json and is never printed. exit 0 sent or clean dry-run / 1 refused or Telegram error / 2 usage)
|
|
519
522
|
dz name-check [--command <n>] [--module <basename>] [--export <a,b>] [--project <dir>] [--json] (is this name free, BEFORE a line of code? Scans workspace SOURCE — never dist, because a stale build answers 'free' confidently. Checks a dz command name against the dispatcher AND the help block, a module basename against every package's src/, and exported identifiers against every declaration in the workspace. exit 0 all free / 1 at least one taken, naming where / 2 nothing asked or the scan did not run — an empty sweep is never a clean bill. Honest limit, printed on the passing path: it reads declarations, so a re-export under a different name stays the build's job)
|
|
520
523
|
dz provenance-check --manifest <sources.json> [--project <dir>] [--json] (nothing goes out citing a source that may not leave this machine. Checks PROVENANCE, not words: every claim names its source, and only a KNOWN kind that resolves safely is cleared. Repo paths go through 'git -C <root> check-ignore' over the RESOLVED path — a symlink into an ignored directory is REFUSED (git classifies the string and never dereferences, MEASURED), and the verdict does not change with your working directory. Store records must be named in the git-TRACKED provenance-public.json, so declaring one public is a reviewable commit rather than a field inside an ignored store. An undeclared kind is refused, never inferred from the path's shape. exit 0 allowed / 1 blocked / 3 NOT ESTABLISHED — an empty manifest, an unreadable one, or an oracle that did not run is never a pass. It proves what was CITED: it cannot see a paraphrase with no citation, nor confidential text pasted by hand into an allowed file)
|
|
524
|
+
dz project-skills [--project <dir>] [--json] [--stages-json] (polymorphic feature-adr: resolve architecture/project-skills.json — fixed roles product-vision/critic/brand/impl-bar plus an open extra[] — into per-stage guidance. READ-ONLY. --project names the root explicitly, so it works from any cwd; without it the manifest is read from the current repo. No manifest ⇒ a byte-identical generic run)
|
|
525
|
+
dz discrimination-check --slug <slug> [--base <ref>] [--json] (does the ADR's named test actually DISCRIMINATE? Re-runs it on a worktree at the pre-feature commit, where it MUST go red. A test that passes with the feature removed proves nothing; dz amendment-check proves the test exists, this proves it bites)
|
|
526
|
+
dz guard [check|promote|init] [--json] [--force] (HARD/SOFT repo rules — readme-first, lockfile-in-sync, claim tagging — run automatically as a pre-flight inside dz publish. HARD blocks, SOFT warns)
|
|
527
|
+
dz architecture [--check --slug <s> --desc <text>] [--project <dir>] [--revise] (the live product map + vision: --check is the soft Step-0 сверка of a new feature against them, reporting {signal,confidence} rather than blocking)
|
|
528
|
+
dz sbom [--pack <name>] [--out <file>] (CycloneDX software bill of materials for the workspace, or for one pack with --pack)
|
|
521
529
|
dz amendment-check --slug <slug> | --feature-dir <dir> | --all [--json] (the deterministic Step-8 amendment gate: every AM-N row must resolve to a test found INSIDE the file the row names; the PLAN is authoritative when it carries rows, and an ideation amendment the plan drops is a failure. exit 0 pass/skip, 1 fail, 3 NOT-ESTABLISHED — a section that parsed ZERO rows is never a pass. --all is a CENSUS and always exits 0. Does NOT prove non-vacuity — that is dz discrimination-check)
|
|
522
530
|
dz feature-adr-record --kind ledger|training-pair --stage <s> [--slug <s>] [--row|--pair <json>] [--mark <n>] [--once] [--json] (the witnessed writer for the run-cost ledger and training pairs: the payload arrives as an ARGUMENT, never as shell; a malformed or wrong-kind payload is REFUSED before any write; the timestamp is stamped before serialising; the append is verified by re-reading the tail. exit 0 written|duplicate|skipped, 2 refused, 3 not-verified — a record failure is never blocking)
|
|
523
531
|
dz feature-adr-checkpoint (--slug <feature> | --feature-dir <abs>) --stage <s> --input-hash <h> --result <json> [--artifact a,b] [--json] (record a pipeline stage ONLY after measuring its artifacts on disk; refuses a null result, an absent artifact, or a stage that declares none — the subagent runs a COMMAND instead of hand-writing durable state)
|
|
@@ -3572,8 +3580,8 @@ async function cmdRecall(options: Map<string, string>, flags: Set<string>, cwd:
|
|
|
3572
3580
|
// second occurrence. Foreign stdout is routed to stderr for the duration of the engine call — our
|
|
3573
3581
|
// own output is written after it returns.
|
|
3574
3582
|
const result = asJson
|
|
3575
|
-
? await withForeignStdoutOnStderr(() => recallHybrid(projectRoot, query, { limit: fetchLimit, mode }))
|
|
3576
|
-
: await recallHybrid(projectRoot, query, { limit: fetchLimit, mode });
|
|
3583
|
+
? await withForeignStdoutOnStderr(() => recallHybrid(projectRoot, query, { limit: fetchLimit, mode, deferExposures: true, ...(wantedDomain !== undefined ? { domain: wantedDomain } : {}) }))
|
|
3584
|
+
: await recallHybrid(projectRoot, query, { limit: fetchLimit, mode, deferExposures: true, ...(wantedDomain !== undefined ? { domain: wantedDomain } : {}) });
|
|
3577
3585
|
|
|
3578
3586
|
if (mode === 'semantic' && result.vectorEngine === 'none') {
|
|
3579
3587
|
// --semantic is an explicit ask — degrading it silently would be dishonest (FR-3).
|
|
@@ -3605,6 +3613,23 @@ async function cmdRecall(options: Map<string, string>, flags: Set<string>, cwd:
|
|
|
3605
3613
|
// "foreign-domain lessons kept" about the pre-cut list while the printed list was
|
|
3606
3614
|
// missing one. Count it and say so; the reader can act on it (raise --limit).
|
|
3607
3615
|
const displaced = boost !== null ? countDisplacedByCut(result.hits, boost.hits, limit) : 0;
|
|
3616
|
+
// lesson-bandit-rerank FR-8/AC-11: the bandit block must describe the POST-cut list the reader
|
|
3617
|
+
// actually sees. `dz recall` over-fetches under --domain and truncates AGAIN here, so reporting
|
|
3618
|
+
// the core's own (already post-merge-cut) count would still describe a list one cut too early.
|
|
3619
|
+
// ABSENT while disarmed — its presence is what tells a reader the feature ran.
|
|
3620
|
+
const shownDzIds = hits.map((h) => patternRecordId(h.pattern));
|
|
3621
|
+
const banditBlock = result.bandit === undefined
|
|
3622
|
+
? undefined
|
|
3623
|
+
: narrowBanditReport(result.bandit, shownDzIds);
|
|
3624
|
+
// Exposures were DEFERRED (`deferExposures: true`) precisely so this cut happens first: `dz recall`
|
|
3625
|
+
// over-fetches under --domain and truncates again here, and counting an over-fetched hit as "seen"
|
|
3626
|
+
// both inflates the health metrics and mislabels a candidate the reader never laid eyes on.
|
|
3627
|
+
result.commitExposures?.(shownDzIds);
|
|
3628
|
+
const renderBanditNote = (b: NonNullable<typeof banditBlock>): string =>
|
|
3629
|
+
` ℹ bandit payoff: ${b.moved} of ${b.armsConsidered} shown hit(s) moved (ctx ${b.contextKey}` +
|
|
3630
|
+
`, ${b.unknownArms} with no measured payoff yet, ${b.quarantinedExcluded} quarantined excluded` +
|
|
3631
|
+
`${b.exploration ? `, exploration ON — ${b.explored} trial impression(s)` : ''}` +
|
|
3632
|
+
`${b.reason !== null ? `, state ${b.reason}` : ''})`;
|
|
3608
3633
|
if (asJson) {
|
|
3609
3634
|
// Portable contract UNCHANGED (I-7/AC-6): a plain PatternRecord[] — round-trips through
|
|
3610
3635
|
// `dz teach --from-json` regardless of which backend ranked each hit.
|
|
@@ -3635,6 +3660,10 @@ async function cmdRecall(options: Map<string, string>, flags: Set<string>, cwd:
|
|
|
3635
3660
|
const cutNoteJson = renderDomainCutNote(displaced, limit);
|
|
3636
3661
|
if (cutNoteJson !== '') process.stderr.write(`${cutNoteJson}\n`);
|
|
3637
3662
|
}
|
|
3663
|
+
// STDERR, not stdout: `--json`'s stdout contract is a plain PatternRecord[] that round-trips
|
|
3664
|
+
// through `dz teach --from-json`, and wrapping it in an object to make room for one report
|
|
3665
|
+
// would break every existing consumer. The block travels beside the other honesty notes.
|
|
3666
|
+
if (banditBlock !== undefined) process.stderr.write(`${JSON.stringify({ bandit: banditBlock })}\n`);
|
|
3638
3667
|
return semanticUnserved ? 1 : 0;
|
|
3639
3668
|
}
|
|
3640
3669
|
|
|
@@ -3733,6 +3762,12 @@ async function cmdRecall(options: Map<string, string>, flags: Set<string>, cwd:
|
|
|
3733
3762
|
// The loop stays VISIBLE (ADR D2): a quarantined hit is shown, marked, and explained.
|
|
3734
3763
|
write(' ⚠q = quarantined (unproven hypothesis, rank damped) — confirm with dz teach --reinforce, or dz recall --promote <dzId> --apply');
|
|
3735
3764
|
}
|
|
3765
|
+
if (banditBlock !== undefined) {
|
|
3766
|
+
// Say what the payoff term did — INCLUDING when it did nothing. `moved: 0` over many queries
|
|
3767
|
+
// means the feature is armed and inert, which is precisely the outcome nobody would notice
|
|
3768
|
+
// without this line.
|
|
3769
|
+
write(renderBanditNote(banditBlock));
|
|
3770
|
+
}
|
|
3736
3771
|
if (boost !== null && shownDomain !== undefined) {
|
|
3737
3772
|
// Say what the boost did — INCLUDING when it did nothing. A silent reorder would
|
|
3738
3773
|
// let the reader believe the ranking was domain-aware when it had no match to work with.
|
|
@@ -8041,11 +8076,16 @@ async function cmdDownloads(cwd: string, write: Write): Promise<number> {
|
|
|
8041
8076
|
* The pure engine lives in harness-core; this is the thin I/O shell (scan disk + git, render).
|
|
8042
8077
|
*/
|
|
8043
8078
|
function cmdArchitecture(options: Map<string, string>, flags: Set<string>, cwd: string, write: Write): number {
|
|
8044
|
-
// Resolve repo root:
|
|
8045
|
-
|
|
8079
|
+
// Resolve repo root: an explicit --project wins, then git toplevel, then cwd (matches cmdRoam /
|
|
8080
|
+
// cmdStats duality). The --project leg is the same fix as cmdProjectSkills got for field report
|
|
8081
|
+
// doc-25b, applied to its neighbour before it costs a second report: this command also reads an
|
|
8082
|
+
// `architecture/` tree, and on a feature-adr run against a target checkout the Step-0 product-vision
|
|
8083
|
+
// сверка silently no-ops for exactly that reason.
|
|
8084
|
+
const explicitRoot = (options.get('project') ?? '').trim();
|
|
8085
|
+
let repoRoot = explicitRoot !== '' ? resolve(cwd, explicitRoot) : cwd;
|
|
8046
8086
|
try {
|
|
8047
|
-
repoRoot = execSync('git rev-parse --show-toplevel', { cwd, encoding: 'utf-8' }).trim() ||
|
|
8048
|
-
} catch { /* not a git repo — use
|
|
8087
|
+
repoRoot = execSync('git rev-parse --show-toplevel', { cwd: repoRoot, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim() || repoRoot;
|
|
8088
|
+
} catch { /* not a git repo — use the root as given */ }
|
|
8049
8089
|
|
|
8050
8090
|
const manifest = loadSubsystemManifest(repoRoot);
|
|
8051
8091
|
if (!manifest) {
|
|
@@ -8091,11 +8131,21 @@ function cmdArchitecture(options: Map<string, string>, flags: Set<string>, cwd:
|
|
|
8091
8131
|
* --stages-json { hasManifest, design, code, qe, report } — ready to thread into prompts
|
|
8092
8132
|
* No manifest ⇒ empty guidance strings ⇒ byte-identical run (FR-7).
|
|
8093
8133
|
*/
|
|
8094
|
-
function cmdProjectSkills(flags: Set<string>, cwd: string, write: Write): number {
|
|
8095
|
-
|
|
8134
|
+
function cmdProjectSkills(options: Map<string, string>, flags: Set<string>, cwd: string, write: Write): number {
|
|
8135
|
+
// `--project <dir>` names the root EXPLICITLY. Until 2026-08-25 this command took only `cwd`, so
|
|
8136
|
+
// the manifest was reachable exclusively from the workspace you happened to stand in — and because
|
|
8137
|
+
// the known-flag list is deliberately FLAT, `--project` passed validation on a command that never
|
|
8138
|
+
// read it: exit 0, no warning, no manifest. MEASURED that day: a feature-adr run against a target
|
|
8139
|
+
// repo probed with `cd REPO`, found nothing, and fell open to a generic run — the honest
|
|
8140
|
+
// `polymorphism:null` was recorded and not one project lens reached any stage.
|
|
8141
|
+
const explicitRoot = (options.get('project') ?? '').trim();
|
|
8142
|
+
let repoRoot = explicitRoot !== '' ? resolve(cwd, explicitRoot) : cwd;
|
|
8096
8143
|
try {
|
|
8097
|
-
|
|
8098
|
-
|
|
8144
|
+
// stderr is SWALLOWED: a non-repo directory is an ordinary case here, and letting git print
|
|
8145
|
+
// "fatal: not a git repository" onto the operator's terminal made a working command look broken.
|
|
8146
|
+
// The sibling cmdFeatureAdrSetup already redirects; this one did not.
|
|
8147
|
+
repoRoot = execSync('git rev-parse --show-toplevel', { cwd: repoRoot, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim() || repoRoot;
|
|
8148
|
+
} catch { /* not a git repo — use the root as given */ }
|
|
8099
8149
|
|
|
8100
8150
|
const plan = planProjectSkills(repoRoot);
|
|
8101
8151
|
const hasManifest = plan.injections.length > 0 || plan.skipped.length > 0;
|
|
@@ -12036,8 +12086,13 @@ function cmdCompounding(options: Map<string, string>, flags: Set<string>, cwd: s
|
|
|
12036
12086
|
}
|
|
12037
12087
|
|
|
12038
12088
|
const report = assembleCompoundingReport({ lessons, usage, guard, nowTs: new Date().toISOString(), evidenceLogs });
|
|
12039
|
-
|
|
12040
|
-
|
|
12089
|
+
// lesson-bandit-rerank §11: the payoff axis joins THIS report rather than growing a private
|
|
12090
|
+
// dashboard — the `rewardEvents : exposureEvents` row asks exactly the question this command
|
|
12091
|
+
// already asks of the reinforcement loop (is the apply leg alive, or is it a write-only log?).
|
|
12092
|
+
// Read-only, and INSUFFICIENT_DATA on an absent state file — never a fake verdict.
|
|
12093
|
+
const bandit = banditStats(root);
|
|
12094
|
+
if (json) write(JSON.stringify({ ...report, bandit, exitCode: 0 }, null, 2));
|
|
12095
|
+
else write(`${renderCompoundingReport(report)}\n\n${renderBanditHealth(bandit)}`);
|
|
12041
12096
|
return 0;
|
|
12042
12097
|
}
|
|
12043
12098
|
|
|
@@ -13674,7 +13729,7 @@ export async function runCli(argv: string[], io: CliIo = {}): Promise<number> {
|
|
|
13674
13729
|
case 'architecture':
|
|
13675
13730
|
return cmdArchitecture(options, flags, cwd, write);
|
|
13676
13731
|
case 'project-skills':
|
|
13677
|
-
return cmdProjectSkills(flags, cwd, write);
|
|
13732
|
+
return cmdProjectSkills(options, flags, cwd, write);
|
|
13678
13733
|
case 'mr-rakes':
|
|
13679
13734
|
return await cmdMrRakes(options, flags, cwd, write);
|
|
13680
13735
|
case 'retro':
|