@dzhechkov/harness-cli 0.3.256 → 0.3.258
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dz-manifest.json +8 -8
- package/README.md +132 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +109 -4
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
- package/sbom.json +7 -7
- package/src/cli.ts +119 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dzhechkov/harness-cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.258",
|
|
4
4
|
"description": "The dz CLI — install AI skills for Claude Code, Codex, OpenCode, Hermes, OpenClaude, GitHub Copilot. 35 commands, 13 presets, 6 platform targets.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"sbom.json"
|
|
42
42
|
],
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@dzhechkov/harness-core": "^0.3.
|
|
44
|
+
"@dzhechkov/harness-core": "^0.3.147",
|
|
45
45
|
"@dzhechkov/harness-presets": "^0.5.0",
|
|
46
46
|
"@dzhechkov/scout": "^0.8.0",
|
|
47
47
|
"@dzhechkov/skills-devops": "^0.3.0",
|
package/sbom.json
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"hashes": [
|
|
26
26
|
{
|
|
27
27
|
"alg": "SHA-256",
|
|
28
|
-
"content": "
|
|
28
|
+
"content": "a38e5ef6a741aec138af0de7bd3616d4356d613ba1616bd8b5417194c1ee5f03"
|
|
29
29
|
}
|
|
30
30
|
]
|
|
31
31
|
},
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
"hashes": [
|
|
106
106
|
{
|
|
107
107
|
"alg": "SHA-256",
|
|
108
|
-
"content": "
|
|
108
|
+
"content": "f954d1e2e3d9f5941481a2b2ccbf553bab892dfa9ba1e7aefb4bf7d1800e9203"
|
|
109
109
|
}
|
|
110
110
|
]
|
|
111
111
|
},
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
"hashes": [
|
|
116
116
|
{
|
|
117
117
|
"alg": "SHA-256",
|
|
118
|
-
"content": "
|
|
118
|
+
"content": "5fb105be335591fb20401a3b23bac9829777e7ba96c2a2480c29970f4ffa2774"
|
|
119
119
|
}
|
|
120
120
|
]
|
|
121
121
|
},
|
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
"hashes": [
|
|
126
126
|
{
|
|
127
127
|
"alg": "SHA-256",
|
|
128
|
-
"content": "
|
|
128
|
+
"content": "a4be3aac6dadec03c1fc85fc5354d9a286ac0a21b4983349c4e511cae49dec79"
|
|
129
129
|
}
|
|
130
130
|
]
|
|
131
131
|
},
|
|
@@ -185,7 +185,7 @@
|
|
|
185
185
|
"hashes": [
|
|
186
186
|
{
|
|
187
187
|
"alg": "SHA-256",
|
|
188
|
-
"content": "
|
|
188
|
+
"content": "b502ba368d29e25413ce35e8853bc5650c06f686202e51f997f67db0fac03224"
|
|
189
189
|
}
|
|
190
190
|
]
|
|
191
191
|
},
|
|
@@ -205,7 +205,7 @@
|
|
|
205
205
|
"hashes": [
|
|
206
206
|
{
|
|
207
207
|
"alg": "SHA-256",
|
|
208
|
-
"content": "
|
|
208
|
+
"content": "a369561502f08d9dd4993a294adcb3a38349f543959fa8de31cd1580778062b0"
|
|
209
209
|
}
|
|
210
210
|
]
|
|
211
211
|
},
|
|
@@ -225,7 +225,7 @@
|
|
|
225
225
|
"hashes": [
|
|
226
226
|
{
|
|
227
227
|
"alg": "SHA-256",
|
|
228
|
-
"content": "
|
|
228
|
+
"content": "555aed692218a73f23a343df2f2b38ce028e694dd9d4aff37e727096f88eb50e"
|
|
229
229
|
}
|
|
230
230
|
]
|
|
231
231
|
},
|
package/src/cli.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @packageDocumentation
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { chmodSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmdirSync, rmSync, statSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { chmodSync, closeSync, existsSync, fstatSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, readlinkSync, realpathSync, renameSync, rmdirSync, rmSync, statSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
8
8
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
9
9
|
import { fileURLToPath } from 'node:url';
|
|
10
10
|
import { execFileSync, execSync, spawn } from 'node:child_process';
|
|
@@ -80,6 +80,11 @@ import {
|
|
|
80
80
|
statuslineData,
|
|
81
81
|
writeFeatureAdrState,
|
|
82
82
|
computeUsage,
|
|
83
|
+
deriveCostLedger,
|
|
84
|
+
renderCostLedger,
|
|
85
|
+
verifyCostLedgerReport,
|
|
86
|
+
writeCostLedgerJsonl,
|
|
87
|
+
COST_LEDGER_SCOPE,
|
|
83
88
|
deriveUsageCalibration,
|
|
84
89
|
normalizeClaudeUsageModelKey,
|
|
85
90
|
readUsageLimits,
|
|
@@ -107,6 +112,11 @@ import {
|
|
|
107
112
|
RECALL_USAGE_LOG_MAX_BYTES,
|
|
108
113
|
parseRecallUsageLog,
|
|
109
114
|
buildRecallUsageReport,
|
|
115
|
+
EVENT_CHAIN_TAIL_BYTES,
|
|
116
|
+
EMPTY_LOG_TAIL,
|
|
117
|
+
readTailInfo,
|
|
118
|
+
appendChainedLines,
|
|
119
|
+
verifyEventChainText,
|
|
110
120
|
buildManifest,
|
|
111
121
|
buildSbom,
|
|
112
122
|
resolveTrustRoot,
|
|
@@ -269,6 +279,7 @@ import {
|
|
|
269
279
|
import type { IdeaRecord, IdeaStatus } from '@dzhechkov/harness-core';
|
|
270
280
|
import type { Family, ModelRung, Candidate as BtoCandidate, DimScores } from '@dzhechkov/harness-core';
|
|
271
281
|
import type { SetupSpec } from '@dzhechkov/harness-core';
|
|
282
|
+
import type { LogTail } from '@dzhechkov/harness-core';
|
|
272
283
|
import type { ProvenanceMode, PackVerdict, ClaudeUsageModel, PatternRecord, TargetName, BookKU, HarmonizeReport, UsageCalibrationPlan, ClaimFinding, RecallUsagePatternRow, GateExecution, GateStep } from '@dzhechkov/harness-core';
|
|
273
284
|
import { getPreset, PRESET_NAMES } from '@dzhechkov/harness-presets';
|
|
274
285
|
import { scanGitHub, analyzeRepo, generateReport, deepAnalyze, scanAllSources, ScoutMemory } from '@dzhechkov/scout';
|
|
@@ -334,6 +345,7 @@ Usage:
|
|
|
334
345
|
dz statusline [--json] [--install] [--project <dir>] (live self-learning panel for Claude Code's status bar; reads the CC JSON payload from STDIN)
|
|
335
346
|
dz statusline --fa-record --slug <s> --step "<label>" [--recalled <n>] [--stored <n>] [--mode <m>] (feature-adr: record live per-run learning state → 📐 panel segment)
|
|
336
347
|
dz usage [--json] [--project <dir>] | dz usage --calibrate --session <pct> --weekly <pct> [--model fable=<pct>] [--project <dir>] (ESTIMATE Claude usage from fixed reset windows; optional per-model weekly binding; exit 0 ALWAYS; pct=null when limits unconfigured)
|
|
348
|
+
dz usage --by-stage [--run <runId> | --slug <slug>] [--epsilon <0..1>] [--write <file.jsonl>] [--json] (per-stage cost ledger for ONE feature-adr run + the reconciliation invariant: accounted + unaccounted = run total; verdict BALANCED | DEFECT | INSUFFICIENT_DATA; local transcript ESTIMATES — catches ATTRIBUTION errors, not pricing errors)
|
|
337
349
|
dz claim-check [paths...] [--json] [--fail-on high|medium|none] [--project <dir>] (enforce the Integrity Rule: flag untagged/overstated accuracy claims; default scan = root README.md + every discovered package's README.md + features/*/08_qe_report.md + docs/**/*.md (historical feature artifacts are NOT scanned — pass paths explicitly); exit 1 only at/above --fail-on, default high)
|
|
338
350
|
dz pretrain [--project <dir>]
|
|
339
351
|
dz recommend "<task description>"
|
|
@@ -1503,6 +1515,64 @@ function cmdUsageCalibrate(
|
|
|
1503
1515
|
}
|
|
1504
1516
|
}
|
|
1505
1517
|
|
|
1518
|
+
/**
|
|
1519
|
+
* `dz usage --by-stage` — the per-stage cost ledger for one feature-adr run (feature `cost-ledger`).
|
|
1520
|
+
*
|
|
1521
|
+
* A run reports ONE number; this turns it into an itemized receipt keyed by the workflow's OWN
|
|
1522
|
+
* `stageLabel()` strings, plus the reconciliation line that guards the bookkeeping itself. Derived
|
|
1523
|
+
* post-hoc from the transcripts already on disk — no workflow edit, killed runs included.
|
|
1524
|
+
*
|
|
1525
|
+
* Exit code is 0 ALWAYS, matching the rest of `dz usage`; the VERDICT (`BALANCED` / `DEFECT` /
|
|
1526
|
+
* `INSUFFICIENT_DATA`) is the signal, and `INSUFFICIENT_DATA` is not success.
|
|
1527
|
+
*/
|
|
1528
|
+
function cmdUsageByStage(
|
|
1529
|
+
options: Map<string, string>,
|
|
1530
|
+
flags: Set<string>,
|
|
1531
|
+
write: Write,
|
|
1532
|
+
): number {
|
|
1533
|
+
const runId = options.get('run');
|
|
1534
|
+
const slug = options.get('slug');
|
|
1535
|
+
const epsilonRaw = options.get('epsilon');
|
|
1536
|
+
const epsilon = epsilonRaw === undefined ? undefined : Number(epsilonRaw);
|
|
1537
|
+
const report = deriveCostLedger({
|
|
1538
|
+
...(runId !== undefined ? { runId } : {}),
|
|
1539
|
+
...(slug !== undefined ? { slug } : {}),
|
|
1540
|
+
...(epsilon !== undefined && Number.isFinite(epsilon) ? { epsilon } : {}),
|
|
1541
|
+
});
|
|
1542
|
+
|
|
1543
|
+
if (report === null) {
|
|
1544
|
+
// An ABSENT run is never a balanced empty report (ADR-003).
|
|
1545
|
+
const detail = runId !== undefined ? `run ${runId}` : slug !== undefined ? `slug ${slug}` : 'any workflow run';
|
|
1546
|
+
if (flags.has('json')) {
|
|
1547
|
+
write(JSON.stringify({ verdict: 'INSUFFICIENT_DATA', reason: `no workflow run record found for ${detail}`, rows: [], estimated: true, scope: COST_LEDGER_SCOPE }));
|
|
1548
|
+
} else {
|
|
1549
|
+
write(`usage --by-stage: INSUFFICIENT_DATA — no workflow run record found for ${detail}`);
|
|
1550
|
+
write(`usage --by-stage: scope: ${COST_LEDGER_SCOPE}`);
|
|
1551
|
+
}
|
|
1552
|
+
return 0;
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
// The verifier re-derives the identities from the EMITTED report — a builder bug must surface as
|
|
1556
|
+
// a finding, not as a plausible table.
|
|
1557
|
+
const verifyDefects = verifyCostLedgerReport(report);
|
|
1558
|
+
|
|
1559
|
+
const outPath = options.get('write');
|
|
1560
|
+
let wrote: boolean | null = null;
|
|
1561
|
+
if (outPath !== undefined && outPath.length > 0) wrote = writeCostLedgerJsonl(resolve(outPath), report);
|
|
1562
|
+
|
|
1563
|
+
if (flags.has('json')) {
|
|
1564
|
+
write(JSON.stringify({ ...report, verifyDefects, ...(wrote === null ? {} : { wrote, writePath: resolve(outPath ?? '') }) }));
|
|
1565
|
+
return 0;
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
write(renderCostLedger(report));
|
|
1569
|
+
for (const d of verifyDefects) write(` verifier: ${d.kind}: ${d.detail}`);
|
|
1570
|
+
if (wrote !== null) {
|
|
1571
|
+
write(` ${wrote ? 'wrote' : 'FAILED to write'} ${resolve(outPath ?? '')} (derived report — regenerable, never read back)`);
|
|
1572
|
+
}
|
|
1573
|
+
return 0;
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1506
1576
|
/**
|
|
1507
1577
|
* `dz usage` — print an ESTIMATE of Claude session + weekly usage from fixed reset windows,
|
|
1508
1578
|
* aggregated READONLY from the local transcript store (see {@link computeUsage}). `--json` emits
|
|
@@ -1534,6 +1604,7 @@ function cmdUsage(
|
|
|
1534
1604
|
});
|
|
1535
1605
|
try {
|
|
1536
1606
|
if (flags.has('calibrate')) return cmdUsageCalibrate(options, optionLists, cwd, write);
|
|
1607
|
+
if (flags.has('by-stage')) return cmdUsageByStage(options, flags, write);
|
|
1537
1608
|
|
|
1538
1609
|
const u = computeUsage(projectRoot);
|
|
1539
1610
|
const lim = readUsageLimits(projectRoot);
|
|
@@ -5045,11 +5116,44 @@ function runGuardEvaluation(root: string, op: string, text: string | undefined,
|
|
|
5045
5116
|
try {
|
|
5046
5117
|
const rec = auditRecord(result, new Date().toISOString(), overrideReason !== undefined ? { reason: overrideReason } : undefined);
|
|
5047
5118
|
mkdirSync(join(root, '.dz'), { recursive: true });
|
|
5048
|
-
|
|
5119
|
+
const auditPath = join(root, '.dz', 'guard-audit.jsonl');
|
|
5120
|
+
// event-chain (ADR-001): seq + prevHash derived from the LAST LINE ONLY — this file is the
|
|
5121
|
+
// evidence base `dz guard promote` decides on, and a rewrite that loses or duplicates a record
|
|
5122
|
+
// must not be able to look intact. A tail that cannot be read starts a MARKED segment rather
|
|
5123
|
+
// than blocking the audit: the verdict is never held hostage to a broken log.
|
|
5124
|
+
writeFileSync(auditPath, appendChainedLines([rec], readLogTail(auditPath)), { flag: 'a' });
|
|
5049
5125
|
} catch { /* audit is best-effort, never blocks the verdict */ }
|
|
5050
5126
|
return result;
|
|
5051
5127
|
}
|
|
5052
5128
|
|
|
5129
|
+
/**
|
|
5130
|
+
* The tail facts of an append-only log, read from its END — O(1) in the file size, which is what
|
|
5131
|
+
* lets the chain be extended on every append without a full-file scan (FR-2). Anything unreadable
|
|
5132
|
+
* yields {@link EMPTY_LOG_TAIL}; the caller then starts a marked segment rather than blocking.
|
|
5133
|
+
*/
|
|
5134
|
+
function readLogTail(path: string): LogTail {
|
|
5135
|
+
let fd: number | undefined;
|
|
5136
|
+
try {
|
|
5137
|
+
if (!existsSync(path)) return EMPTY_LOG_TAIL;
|
|
5138
|
+
fd = openSync(path, 'r');
|
|
5139
|
+
const size = fstatSync(fd).size;
|
|
5140
|
+
if (!Number.isFinite(size) || size <= 0) return EMPTY_LOG_TAIL;
|
|
5141
|
+
const want = Math.min(size, EVENT_CHAIN_TAIL_BYTES);
|
|
5142
|
+
const buf = Buffer.alloc(want);
|
|
5143
|
+
readSync(fd, buf, 0, want, size - want);
|
|
5144
|
+
return readTailInfo(buf.toString('utf-8'), { partial: want < size });
|
|
5145
|
+
} catch {
|
|
5146
|
+
// A read FAILURE is not an empty file (Codex re-QE LOW): EMPTY_LOG_TAIL means "there is
|
|
5147
|
+
// nothing", which lets the writer open an UNMARKED genesis on a file we merely failed to
|
|
5148
|
+
// read. An unreadable tail must force a marked reset, per the AM-6 contract.
|
|
5149
|
+
return { ...EMPTY_LOG_TAIL, unreadable: true };
|
|
5150
|
+
} finally {
|
|
5151
|
+
if (fd !== undefined) {
|
|
5152
|
+
try { closeSync(fd); } catch { /* nothing to do */ }
|
|
5153
|
+
}
|
|
5154
|
+
}
|
|
5155
|
+
}
|
|
5156
|
+
|
|
5053
5157
|
// ── `dz guard promote` (feature guard-promotion, scout idea #1) ─────────────────────────────────
|
|
5054
5158
|
|
|
5055
5159
|
const PROMOTIONS_DIR = join('features', 'guard-promotion', 'promotions');
|
|
@@ -6290,7 +6394,19 @@ function cmdCompounding(options: Map<string, string>, flags: Set<string>, cwd: s
|
|
|
6290
6394
|
/* no audit yet */
|
|
6291
6395
|
}
|
|
6292
6396
|
|
|
6293
|
-
|
|
6397
|
+
// The evidence logs themselves, verbatim: the report verifies their hash chains (feature
|
|
6398
|
+
// event-chain). Handing over the TEXT rather than a pre-computed verdict keeps one definition of
|
|
6399
|
+
// "the chain is intact" — a second copy here is how a gate and its report start disagreeing.
|
|
6400
|
+
const evidenceLogs: { log: string; text: string }[] = [];
|
|
6401
|
+
for (const rel of ['.dz/recall-usage.jsonl', '.dz/guard-audit.jsonl']) {
|
|
6402
|
+
try {
|
|
6403
|
+
evidenceLogs.push({ log: rel, text: readFileSync(join(root, ...rel.split('/')), 'utf-8') });
|
|
6404
|
+
} catch {
|
|
6405
|
+
/* absent log — reported by its own gate above, not invented here */
|
|
6406
|
+
}
|
|
6407
|
+
}
|
|
6408
|
+
|
|
6409
|
+
const report = assembleCompoundingReport({ lessons, usage, guard, nowTs: new Date().toISOString(), evidenceLogs });
|
|
6294
6410
|
if (json) write(JSON.stringify({ ...report, exitCode: 0 }, null, 2));
|
|
6295
6411
|
else write(renderCompoundingReport(report));
|
|
6296
6412
|
return 0;
|