@dzhechkov/harness-cli 0.3.205 → 0.3.207
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 +53 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +157 -7
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
- package/src/cli.ts +163 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dzhechkov/harness-cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.207",
|
|
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",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"@dzhechkov/skills-reverse-engineering": "^0.1.0",
|
|
55
55
|
"@dzhechkov/skills-presentation-storyteller": "^0.1.0",
|
|
56
56
|
"@dzhechkov/skills-website-cloner": "^0.1.0",
|
|
57
|
-
"@dzhechkov/harness-core": "0.3.
|
|
57
|
+
"@dzhechkov/harness-core": "0.3.106"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@types/node": "^25.6.0",
|
package/src/cli.ts
CHANGED
|
@@ -55,6 +55,8 @@ import {
|
|
|
55
55
|
storeStats,
|
|
56
56
|
consolidateSessions,
|
|
57
57
|
pruneNoisePatterns,
|
|
58
|
+
removePatternsByIds,
|
|
59
|
+
snapshotStore,
|
|
58
60
|
recallHybrid,
|
|
59
61
|
teachGuard,
|
|
60
62
|
mirrorPatternsToVector,
|
|
@@ -74,6 +76,9 @@ import {
|
|
|
74
76
|
summarize,
|
|
75
77
|
queryBookKnowledge,
|
|
76
78
|
loadStorePatternsSync,
|
|
79
|
+
patternRecordId,
|
|
80
|
+
loadStoreRecords,
|
|
81
|
+
recordToPattern,
|
|
77
82
|
bundleSkills,
|
|
78
83
|
brainHome,
|
|
79
84
|
listBrain,
|
|
@@ -87,8 +92,12 @@ import {
|
|
|
87
92
|
exportBrainSlice,
|
|
88
93
|
importBrainSlice,
|
|
89
94
|
registerKusToBrain,
|
|
95
|
+
RECALL_USAGE_LOG_RELATIVE,
|
|
96
|
+
RECALL_USAGE_LOG_MAX_BYTES,
|
|
97
|
+
parseRecallUsageLog,
|
|
98
|
+
buildRecallUsageReport,
|
|
90
99
|
} from '@dzhechkov/harness-core';
|
|
91
|
-
import type { ClaudeUsageModel, PatternRecord, TargetName, BookKU, HarmonizeReport, UsageCalibrationPlan, ClaimFinding } from '@dzhechkov/harness-core';
|
|
100
|
+
import type { ClaudeUsageModel, PatternRecord, TargetName, BookKU, HarmonizeReport, UsageCalibrationPlan, ClaimFinding, RecallUsagePatternRow } from '@dzhechkov/harness-core';
|
|
92
101
|
import { getPreset, PRESET_NAMES } from '@dzhechkov/harness-presets';
|
|
93
102
|
import { scanGitHub, analyzeRepo, generateReport, deepAnalyze, scanAllSources, ScoutMemory } from '@dzhechkov/scout';
|
|
94
103
|
|
|
@@ -112,8 +121,8 @@ Usage:
|
|
|
112
121
|
dz setup --target <name> [--preset <name>] [--select id,id,...] [--skills-dir <dir>] [--project <dir>] [--memory agentdb] [--no-memory] [--no-hooks] [--install-driver] [--force] [--enrich]
|
|
113
122
|
dz teach "<pattern>" [--reward <0-1>] [--domain <name>] [--type rule|success-pattern|lesson-learned] [--project <dir>] [--no-mirror] (--project pins the learned store to <dir>/.dz, not the cwd — pin to a canonical brain)
|
|
114
123
|
dz teach --from-json <file> [--project <dir>] [--no-mirror] (bulk-import a 'dz recall --all --json' export — share a learned store across machines)
|
|
115
|
-
dz consolidate [--sessions-dir <dir>] [--project <dir>] [--no-mirror] [--prune-noise]
|
|
116
|
-
dz recall "<query>" [--limit <N>] [--semantic | --no-semantic] [--books [--book <slug>]] [--project <dir>] | dz recall --all [--json]
|
|
124
|
+
dz consolidate [--sessions-dir <dir>] [--project <dir>] [--no-mirror] [--prune-noise [--apply]] (prune-noise: DRY-RUN by default; --apply snapshots then deletes)
|
|
125
|
+
dz recall "<query>" [--limit <N>] [--semantic | --no-semantic] [--books [--book <slug>]] [--project <dir>] | dz recall --all [--json] | dz recall --usage [--json] | dz recall --forget <dzId>[,<dzId>] [--apply] (forget: dry-run default; snapshots before removing)
|
|
117
126
|
dz vector status [--project <dir>] [--json] (semantic tier: engine, mirrored vs lexical counts, pending queue)
|
|
118
127
|
dz vector reindex [--project <dir>] [--json] (snapshot, re-embed learned-pattern vectors, stamp current model)
|
|
119
128
|
dz vector export <path> [--project <dir>] (portable VECTOR form (.rvf, opt-in RVF engine); patterns ship via recall --all --json)
|
|
@@ -1102,7 +1111,7 @@ function cmdStatusline(
|
|
|
1102
1111
|
return 0;
|
|
1103
1112
|
}
|
|
1104
1113
|
|
|
1105
|
-
let line = `🎓 dz: ${data.patterns} patterns · 🧠 ${data.brainSources} sources`;
|
|
1114
|
+
let line = `🎓 dz: ${data.patterns} patterns${data.usedPatterns !== undefined ? ` · ${data.usedPatterns} used` : ''} · 🧠 ${data.brainSources} sources`;
|
|
1106
1115
|
const branch = statuslineGitBranch(projectRoot);
|
|
1107
1116
|
if (branch !== undefined) line += ` · ⎇ ${branch}`;
|
|
1108
1117
|
if (data.consolidatedAgeH !== undefined) line += ` · ⟳ ${data.consolidatedAgeH}h`;
|
|
@@ -1554,8 +1563,24 @@ async function cmdConsolidate(options: Map<string, string>, flags: Set<string>,
|
|
|
1554
1563
|
// --prune-noise: RETRO-PRUNE legacy noise (tool telemetry + system-wrapper "responses") from
|
|
1555
1564
|
// the lexical store AND the agentdb vector mirror BEFORE harvesting, so this run's watermark
|
|
1556
1565
|
// never re-learns from junk. Best-effort — a prune error is reported, never fatal.
|
|
1566
|
+
//
|
|
1567
|
+
// DRY-RUN BY DEFAULT. The learned store lives in a git-ignored `.dz/` and has no history: a
|
|
1568
|
+
// wrong sweep is unrecoverable. `--prune-noise` therefore only PREVIEWS; `--apply` performs the
|
|
1569
|
+
// deletion, and snapshots the store to a restorable JSON export first. (Behaviour change: before
|
|
1570
|
+
// this, `--prune-noise` deleted immediately and silently.)
|
|
1571
|
+
const applyPrune = flags.has('apply');
|
|
1557
1572
|
let pruned: ReturnType<typeof pruneNoisePatterns> | undefined;
|
|
1558
|
-
|
|
1573
|
+
let pruneSnapshot: string | undefined;
|
|
1574
|
+
if (pruneNoise) {
|
|
1575
|
+
if (applyPrune) {
|
|
1576
|
+
const dest = join(projectRoot, '.dz', `patterns-pre-prune-${Date.now()}.json`);
|
|
1577
|
+
const snap = snapshotStore(projectRoot, dest);
|
|
1578
|
+
if (snap.error === undefined) pruneSnapshot = snap.path;
|
|
1579
|
+
else write(` ⚠ snapshot failed (${snap.error}) — prune SKIPPED, the store is not versioned`);
|
|
1580
|
+
if (snap.error !== undefined) return 1; // never delete what we could not back up
|
|
1581
|
+
}
|
|
1582
|
+
pruned = pruneNoisePatterns(projectRoot, { dryRun: !applyPrune });
|
|
1583
|
+
}
|
|
1559
1584
|
// Throw-safety (QE P3): this command also runs DETACHED from the SessionEnd hook (stdio
|
|
1560
1585
|
// ignored) — an uncaught store/watermark error would be fully invisible there. Catch, leave
|
|
1561
1586
|
// an observable note next to the session markers, and report on stdout for manual runs.
|
|
@@ -1585,7 +1610,17 @@ async function cmdConsolidate(options: Map<string, string>, flags: Set<string>,
|
|
|
1585
1610
|
// Echo active flags in the header (learned pattern: flags must be visible in command output).
|
|
1586
1611
|
write(`dz consolidate${pruneNoise ? ' (--prune-noise)' : ''}`);
|
|
1587
1612
|
if (pruned !== undefined) {
|
|
1588
|
-
|
|
1613
|
+
const verb = pruned.dryRun === true ? 'would prune' : 'pruned';
|
|
1614
|
+
write(` ${verb}: ${pruned.lexicalRemoved} lexical, ${pruned.vectorRemoved} vector`);
|
|
1615
|
+
for (const c of (pruned.candidates ?? []).slice(0, 10)) {
|
|
1616
|
+
write(` ${c.id} ${c.text.replace(/\s+/g, ' ').slice(0, 72)}`);
|
|
1617
|
+
}
|
|
1618
|
+
const extra = (pruned.candidates?.length ?? 0) - 10;
|
|
1619
|
+
if (extra > 0) write(` … and ${extra} more`);
|
|
1620
|
+
if (pruned.dryRun === true && pruned.lexicalRemoved + pruned.vectorRemoved > 0) {
|
|
1621
|
+
write(' DRY RUN — nothing deleted. Re-run with --apply to remove (snapshots first).');
|
|
1622
|
+
}
|
|
1623
|
+
if (pruneSnapshot !== undefined) write(` snapshot: ${pruneSnapshot}`);
|
|
1589
1624
|
if (pruned.error !== undefined) write(` Prune: ${pruned.error}`);
|
|
1590
1625
|
}
|
|
1591
1626
|
write(` Sessions dir: ${result.sessionsDir} (${sourceLabel[result.source]})`);
|
|
@@ -1606,10 +1641,132 @@ async function cmdConsolidate(options: Map<string, string>, flags: Set<string>,
|
|
|
1606
1641
|
return 0;
|
|
1607
1642
|
}
|
|
1608
1643
|
|
|
1644
|
+
function recallUsagePatternRefs(projectRoot: string): { dzId: string; pattern: string; domain: string; reward: number }[] {
|
|
1645
|
+
return loadStoreRecords(projectRoot).map((record) => {
|
|
1646
|
+
const pattern = recordToPattern(record);
|
|
1647
|
+
return { dzId: record.id, pattern: pattern.pattern, domain: pattern.domain, reward: pattern.reward };
|
|
1648
|
+
});
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
function readRecallUsageReport(projectRoot: string) {
|
|
1652
|
+
let text = '';
|
|
1653
|
+
const logPath = join(projectRoot, RECALL_USAGE_LOG_RELATIVE);
|
|
1654
|
+
try {
|
|
1655
|
+
if (existsSync(logPath)) text = readFileSync(logPath, 'utf-8');
|
|
1656
|
+
} catch {
|
|
1657
|
+
text = '';
|
|
1658
|
+
}
|
|
1659
|
+
return buildRecallUsageReport(recallUsagePatternRefs(projectRoot), parseRecallUsageLog(text));
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
function fmtUsageRow(row: RecallUsagePatternRow): string {
|
|
1663
|
+
const last = row.lastReadAt !== undefined ? ` last=${row.lastReadAt}` : '';
|
|
1664
|
+
const score = row.avgScore !== undefined ? ` avg=${row.avgScore.toFixed(3)}` : '';
|
|
1665
|
+
const domain = row.domain !== undefined ? ` (${row.domain})` : '';
|
|
1666
|
+
return `${row.reads}×${last}${score} ${row.dzId}${domain} ${row.pattern.slice(0, 100)}`;
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
function cmdRecallUsage(options: Map<string, string>, flags: Set<string>, projectRoot: string, write: Write): number {
|
|
1670
|
+
const report = readRecallUsageReport(projectRoot);
|
|
1671
|
+
if (flags.has('json')) {
|
|
1672
|
+
write(JSON.stringify({
|
|
1673
|
+
...report,
|
|
1674
|
+
log: join(projectRoot, RECALL_USAGE_LOG_RELATIVE),
|
|
1675
|
+
retention: { maxBytes: RECALL_USAGE_LOG_MAX_BYTES },
|
|
1676
|
+
}));
|
|
1677
|
+
return 0;
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
const displayLimit = Math.max(1, parseInt(options.get('limit') ?? '20', 10) || 20);
|
|
1681
|
+
write(`dz recall --usage — ${report.usedPatterns}/${report.totalPatterns} pattern(s) read, ${report.totalReads} read event(s)`);
|
|
1682
|
+
write(` log: ${RECALL_USAGE_LOG_RELATIVE} (bounded at ${RECALL_USAGE_LOG_MAX_BYTES} bytes)`);
|
|
1683
|
+
if (report.invalidLines > 0) write(` skipped invalid/torn line(s): ${report.invalidLines}`);
|
|
1684
|
+
if (report.unknownReadPatterns > 0) write(` usage rows for missing pattern id(s): ${report.unknownReadPatterns}`);
|
|
1685
|
+
|
|
1686
|
+
write(' most read:');
|
|
1687
|
+
if (report.top.length === 0) {
|
|
1688
|
+
write(' none yet');
|
|
1689
|
+
} else {
|
|
1690
|
+
for (const row of report.top.slice(0, displayLimit)) write(` ${fmtUsageRow(row)}`);
|
|
1691
|
+
if (report.top.length > displayLimit) write(` ... ${report.top.length - displayLimit} more (use --json for the full list)`);
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
write(' never read:');
|
|
1695
|
+
if (report.neverRead.length === 0) {
|
|
1696
|
+
write(' none');
|
|
1697
|
+
} else {
|
|
1698
|
+
for (const row of report.neverRead.slice(0, displayLimit)) write(` ${row.dzId}${row.domain !== undefined ? ` (${row.domain})` : ''} ${row.pattern.slice(0, 100)}`);
|
|
1699
|
+
if (report.neverRead.length > displayLimit) write(` ... ${report.neverRead.length - displayLimit} more (use --json for the full list)`);
|
|
1700
|
+
}
|
|
1701
|
+
return 0;
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
/**
|
|
1705
|
+
* `dz recall --forget <dzId>[,<dzId>…] [--apply]` — remove NAMED records from the learned store.
|
|
1706
|
+
*
|
|
1707
|
+
* `--prune-noise` only removes what `isNoiseInsight` recognises. It cannot reach the other junk
|
|
1708
|
+
* class: records that are structurally fine but were never lessons. This repo has two of them —
|
|
1709
|
+
* `mismatch probe A` / `match probe B`, written into the LIVE store on 2026-07-07 by the
|
|
1710
|
+
* `dz teach --project <relative>` path bug fixed in `83b1bac` (the relative root resolved to the
|
|
1711
|
+
* cwd store instead of the temp project). The bug is gone; its residue is not, and no predicate
|
|
1712
|
+
* will ever classify it.
|
|
1713
|
+
*
|
|
1714
|
+
* DRY-RUN BY DEFAULT, and it snapshots before deleting: `.dz/` is git-ignored and unversioned, so a
|
|
1715
|
+
* mistaken id is unrecoverable. Use `dz recall --all --json` to find ids.
|
|
1716
|
+
*/
|
|
1717
|
+
async function cmdRecallForget(
|
|
1718
|
+
options: Map<string, string>,
|
|
1719
|
+
flags: Set<string>,
|
|
1720
|
+
projectRoot: string,
|
|
1721
|
+
write: Write,
|
|
1722
|
+
): Promise<number> {
|
|
1723
|
+
const raw = options.get('forget') ?? '';
|
|
1724
|
+
const ids = new Set(raw.split(',').map((s) => s.trim()).filter((s) => s !== ''));
|
|
1725
|
+
if (ids.size === 0) {
|
|
1726
|
+
write('dz recall --forget: no ids given (comma-separated dzIds; find them with `dz recall --all --json`)');
|
|
1727
|
+
return 1;
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
// Use the CANONICAL store ids, not `patternRecordId(pattern)`. `removePatternsByIds` matches on
|
|
1731
|
+
// `MemoryRecord.id`, and the two coincide only for records dz itself wrote (`dz teach` stores the
|
|
1732
|
+
// hash as the id). A record seeded or migrated with a different id would be reported as "found",
|
|
1733
|
+
// then silently NOT removed — the command would claim success while deleting nothing.
|
|
1734
|
+
const known = new Map(loadStoreRecords(projectRoot).map((r) => [r.id, r.text]));
|
|
1735
|
+
const found = [...ids].filter((id) => known.has(id));
|
|
1736
|
+
const missing = [...ids].filter((id) => !known.has(id));
|
|
1737
|
+
|
|
1738
|
+
for (const id of found) write(` ${id} ${(known.get(id) ?? '').replace(/\s+/g, ' ').slice(0, 72)}`);
|
|
1739
|
+
for (const id of missing) write(` ${id} (not in the store — nothing to forget)`);
|
|
1740
|
+
|
|
1741
|
+
if (found.length === 0) {
|
|
1742
|
+
write('dz recall --forget: nothing matched.');
|
|
1743
|
+
return 1;
|
|
1744
|
+
}
|
|
1745
|
+
if (!flags.has('apply')) {
|
|
1746
|
+
write(`dz recall --forget: DRY RUN — ${found.length} record(s) would be removed. Re-run with --apply.`);
|
|
1747
|
+
return 0;
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
const dest = join(projectRoot, '.dz', `patterns-pre-forget-${Date.now()}.json`);
|
|
1751
|
+
const snap = snapshotStore(projectRoot, dest);
|
|
1752
|
+
if (snap.error !== undefined) {
|
|
1753
|
+
write(`dz recall --forget: snapshot failed (${snap.error}) — nothing removed; the store is not versioned`);
|
|
1754
|
+
return 1;
|
|
1755
|
+
}
|
|
1756
|
+
const result = removePatternsByIds(projectRoot, new Set(found));
|
|
1757
|
+
write(`dz recall --forget: removed ${result.removed} record(s)`);
|
|
1758
|
+
write(` snapshot: ${snap.path} (${snap.count} record(s))`);
|
|
1759
|
+
if (result.error !== undefined) write(` ⚠ ${result.error}`);
|
|
1760
|
+
write(' the vector mirror still holds them — run `dz vector reindex` to resync');
|
|
1761
|
+
return 0;
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1609
1764
|
async function cmdRecall(options: Map<string, string>, flags: Set<string>, cwd: string, write: Write): Promise<number> {
|
|
1610
1765
|
const projectRoot = resolve(cwd, options.get('project') ?? '.');
|
|
1611
1766
|
const asJson = flags.has('json');
|
|
1612
1767
|
const all = flags.has('all');
|
|
1768
|
+
if (flags.has('usage')) return cmdRecallUsage(options, flags, projectRoot, write);
|
|
1769
|
+
if (options.has('forget')) return cmdRecallForget(options, flags, projectRoot, write);
|
|
1613
1770
|
|
|
1614
1771
|
// --all: dump the entire learned store (backend-agnostic, via loadStorePatternsSync).
|
|
1615
1772
|
// With --json this is the portable export the agentdb-memory MCP bridge consumes.
|