@dzhechkov/harness-cli 0.3.195 → 0.3.198

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dzhechkov/harness-cli",
3
- "version": "0.3.195",
3
+ "version": "0.3.198",
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",
@@ -37,7 +37,15 @@
37
37
  "src",
38
38
  "README.md"
39
39
  ],
40
+ "scripts": {
41
+ "build": "tsc -p tsconfig.json",
42
+ "test": "vitest run",
43
+ "test:watch": "vitest",
44
+ "typecheck": "tsc -p tsconfig.json --noEmit",
45
+ "lint": "tsc -p tsconfig.json --noEmit"
46
+ },
40
47
  "dependencies": {
48
+ "@dzhechkov/harness-core": "workspace:*",
41
49
  "@dzhechkov/harness-presets": "^0.5.0",
42
50
  "@dzhechkov/scout": "^0.8.0",
43
51
  "@dzhechkov/skills-devops": "^0.3.0",
@@ -53,8 +61,7 @@
53
61
  "@dzhechkov/skills-idea2prd": "^0.1.0",
54
62
  "@dzhechkov/skills-reverse-engineering": "^0.1.0",
55
63
  "@dzhechkov/skills-presentation-storyteller": "^0.1.0",
56
- "@dzhechkov/skills-website-cloner": "^0.1.0",
57
- "@dzhechkov/harness-core": "0.3.92"
64
+ "@dzhechkov/skills-website-cloner": "^0.1.0"
58
65
  },
59
66
  "devDependencies": {
60
67
  "@types/node": "^25.6.0",
@@ -72,12 +79,5 @@
72
79
  "url": "https://github.com/djd1m/dz-harness-hub.git",
73
80
  "directory": "packages/@dzhechkov/harness-cli"
74
81
  },
75
- "homepage": "https://github.com/djd1m/dz-harness-hub/tree/main/packages/@dzhechkov/harness-cli#readme",
76
- "scripts": {
77
- "build": "tsc -p tsconfig.json",
78
- "test": "vitest run",
79
- "test:watch": "vitest",
80
- "typecheck": "tsc -p tsconfig.json --noEmit",
81
- "lint": "tsc -p tsconfig.json --noEmit"
82
- }
83
- }
82
+ "homepage": "https://github.com/djd1m/dz-harness-hub/tree/main/packages/@dzhechkov/harness-cli#readme"
83
+ }
package/src/cli.ts CHANGED
@@ -51,9 +51,12 @@ import {
51
51
  WORKFLOW_NAMES,
52
52
  importEcc,
53
53
  recordPattern,
54
+ resolveLearningBackend,
55
+ storeStats,
54
56
  consolidateSessions,
55
57
  pruneNoisePatterns,
56
58
  recallHybrid,
59
+ teachGuard,
57
60
  mirrorPatternsToVector,
58
61
  vectorMirrorEnabled,
59
62
  vectorTierStatus,
@@ -63,6 +66,8 @@ import {
63
66
  importRvfCheckpoint,
64
67
  statuslineData,
65
68
  writeFeatureAdrState,
69
+ computeUsage,
70
+ readUsageLimits,
66
71
  queryBookKnowledge,
67
72
  loadStorePatternsSync,
68
73
  bundleSkills,
@@ -122,6 +127,7 @@ Usage:
122
127
  dz brain init [--project <dir>] [--k <N>] (wire the grounding hook into .claude/settings.json — opt-in)
123
128
  dz statusline [--json] [--install] [--project <dir>] (live self-learning panel for Claude Code's status bar; reads the CC JSON payload from STDIN)
124
129
  dz statusline --fa-record --slug <s> --step "<label>" [--recalled <n>] [--stored <n>] [--mode <m>] (feature-adr: record live per-run learning state → 📐 panel segment)
130
+ dz usage [--json] [--project <dir>] (ESTIMATE Claude session (5h-block) + weekly (7d) token usage from local transcripts; exit 0 ALWAYS; pct=null when memory.usage.{sessionTokenLimit,weeklyTokenLimit} unconfigured in .dz/config.json)
125
131
  dz pretrain [--project <dir>]
126
132
  dz recommend "<task description>"
127
133
  dz compose <preset1+preset2+...> [--target <name>]
@@ -1037,11 +1043,14 @@ function cmdStatuslineFaRecord(options: Map<string, string>, cwd: string, write:
1037
1043
  if (recalled === undefined) return 1;
1038
1044
  const stored = parseCount('stored');
1039
1045
  if (stored === undefined) return 1;
1046
+ const reinforced = parseCount('reinforced');
1047
+ if (reinforced === undefined) return 1;
1040
1048
 
1041
1049
  const mode = options.get('mode');
1042
1050
  const projectRoot = resolve(cwd, options.get('project') ?? '.');
1043
1051
  const state = writeFeatureAdrState(projectRoot, {
1044
1052
  slug, step, recalled, stored,
1053
+ ...(reinforced > 0 ? { reinforced } : {}),
1045
1054
  ...(mode !== undefined && mode.trim() !== '' ? { mode: mode.trim() } : {}),
1046
1055
  });
1047
1056
 
@@ -1049,7 +1058,7 @@ function cmdStatuslineFaRecord(options: Map<string, string>, cwd: string, write:
1049
1058
  write(`dz statusline --fa-record: could not write learning state under ${projectRoot}/.dz/feature-adr/`);
1050
1059
  return 1;
1051
1060
  }
1052
- write(`dz statusline: recorded /feature-adr learning state for "${slug}" (${step}) — 🎓 ${state.pool} pool · ↑${state.recalled} used · +${state.stored} new`);
1061
+ write(`dz statusline: recorded /feature-adr learning state for "${slug}" (${step}) — 🎓 ${state.pool} pool · ↑${state.recalled} used · +${state.stored} new · ↻${state.reinforced ?? 0} reinforced`);
1053
1062
  return 0;
1054
1063
  }
1055
1064
 
@@ -1091,7 +1100,7 @@ function cmdStatusline(
1091
1100
  // Live /feature-adr run in flight → PREPEND the pipeline learning segment to the base dz line.
1092
1101
  const fa = data.featureAdr;
1093
1102
  if (fa !== undefined) {
1094
- line = `📐 feature-adr ${fa.step} · 🎓 ${fa.pool} pool · ↑${fa.recalled} used · +${fa.stored} new · ${line}`;
1103
+ line = `📐 feature-adr ${fa.step} · 🎓 ${fa.pool} pool · ↑${fa.recalled} used · +${fa.stored} new · ↻${fa.reinforced ?? 0} reinforced · ${line}`;
1095
1104
  }
1096
1105
 
1097
1106
  write(line);
@@ -1103,6 +1112,73 @@ function cmdStatusline(
1103
1112
  }
1104
1113
  }
1105
1114
 
1115
+ /**
1116
+ * `dz usage` — print an ESTIMATE of Claude session (active 5h-block) + weekly (rolling 7d) token
1117
+ * usage, aggregated READONLY from the local transcript store (see {@link computeUsage}). `--json`
1118
+ * emits the single-line contract the feature-adr usage-probe agent parses; the human path prints a
1119
+ * compact `session ~74% (resets 19:00) · week ~52% (resets Sat)` or an unconfigured hint.
1120
+ *
1121
+ * **Exit code is 0 ALWAYS** — including on internal error the whole body is guarded and prints the
1122
+ * all-null JSON, so a probe can NEVER distinguish "usage unknown" from "command failed" via a
1123
+ * non-zero exit (unknown is a first-class value, INV-3). `--project <dir>` scopes ONLY the
1124
+ * `.dz/config.json` limits read; the measurement is account-wide (all projects, FR-1.6).
1125
+ */
1126
+ function cmdUsage(options: Map<string, string>, flags: Set<string>, cwd: string, write: Write): number {
1127
+ const projectRoot = resolve(cwd, options.get('project') ?? '.');
1128
+ const nullContract = () =>
1129
+ JSON.stringify({
1130
+ sessionPct: null,
1131
+ weeklyPct: null,
1132
+ sessionTokens: 0,
1133
+ weeklyTokens: 0,
1134
+ resetsAt: { session: null, weekly: null },
1135
+ limits: { session: null, weekly: null },
1136
+ estimated: true,
1137
+ });
1138
+ try {
1139
+ const u = computeUsage(projectRoot);
1140
+ const lim = readUsageLimits(projectRoot);
1141
+ if (flags.has('json')) {
1142
+ write(
1143
+ JSON.stringify({
1144
+ sessionPct: u.sessionPct,
1145
+ weeklyPct: u.weeklyPct,
1146
+ sessionTokens: u.sessionTokens,
1147
+ weeklyTokens: u.weeklyTokens,
1148
+ resetsAt: { session: u.sessionResetsAt, weekly: u.weeklyResetsAt },
1149
+ limits: { session: lim.sessionTokenLimit ?? null, weekly: lim.weeklyTokenLimit ?? null },
1150
+ estimated: true,
1151
+ }),
1152
+ );
1153
+ return 0;
1154
+ }
1155
+ if (u.sessionPct === null && u.weeklyPct === null) {
1156
+ write(
1157
+ 'usage: unconfigured — set memory.usage.sessionTokenLimit / weeklyTokenLimit in .dz/config.json (percentages are ESTIMATES calibrated from observed exhaustion)',
1158
+ );
1159
+ return 0;
1160
+ }
1161
+ // Compact human line — a short HH:MM / weekday hint on the resets, best-effort.
1162
+ const clock = (iso: string | null): string => {
1163
+ if (!iso) return '?';
1164
+ try {
1165
+ return new Date(iso).toISOString().slice(11, 16);
1166
+ } catch {
1167
+ return '?';
1168
+ }
1169
+ };
1170
+ const s = u.sessionPct === null ? 'n/a' : '~' + u.sessionPct + '%';
1171
+ const w = u.weeklyPct === null ? 'n/a' : '~' + u.weeklyPct + '%';
1172
+ write('usage: session ' + s + ' (resets ' + clock(u.sessionResetsAt) + ') · week ' + w + ' (resets ' + clock(u.weeklyResetsAt) + ') · estimated');
1173
+ return 0;
1174
+ } catch {
1175
+ // never let a probe see a non-zero exit — print the all-null contract and exit 0.
1176
+ if (flags.has('json')) write(nullContract());
1177
+ else write('usage: unconfigured — set memory.usage.sessionTokenLimit / weeklyTokenLimit in .dz/config.json');
1178
+ return 0;
1179
+ }
1180
+ }
1181
+
1106
1182
  async function cmdTeach(options: Map<string, string>, flags: Set<string>, cwd: string, write: Write): Promise<number> {
1107
1183
  const projectRoot = options.get('project') ?? cwd;
1108
1184
 
@@ -1176,9 +1252,37 @@ async function cmdTeach(options: Map<string, string>, flags: Set<string>, cwd: s
1176
1252
  write(` Backend: memory (@dzhechkov/memory) Total now: ${loadStorePatternsSync(projectRoot).length}`);
1177
1253
  // ONE batched mirror call through the same seam as single-teach (QR-6 — no bespoke path).
1178
1254
  await emitMirror(projectRoot, importedRecs, 'dz-teach-import');
1255
+ if (imported > 0) {
1256
+ const report = await harmonizeVectorStore(projectRoot, {});
1257
+ write(` ℹ ${imported} imported — ${report.clusters.length} near-duplicate cluster(s): review with dz vector harmonize (dry-run); merge with dz vector harmonize --apply after backup`);
1258
+ }
1179
1259
  return 0;
1180
1260
  }
1181
1261
 
1262
+ const reinforce = options.get('reinforce');
1263
+ if (reinforce !== undefined && reinforce.trim() !== '') {
1264
+ const backend = resolveLearningBackend(projectRoot);
1265
+ const sampleReward = options.has('reward') ? parseFloat(options.get('reward') ?? '0.8') : undefined;
1266
+ backend.addSample({
1267
+ dzId: reinforce,
1268
+ kind: 'reinforce',
1269
+ ts: new Date().toISOString(),
1270
+ ...(sampleReward !== undefined ? { reward: sampleReward } : {}),
1271
+ });
1272
+ const trained = await backend.train();
1273
+ if (trained.flushed > 0) {
1274
+ write(`↳ reinforced ${reinforce}`);
1275
+ return 0;
1276
+ }
1277
+ // HIGH-fix: a no-match must NOT auto-teach the raw argument — callers pass dzIds or truncated
1278
+ // text, so auto-teach minted garbage lessons (observed live). Fail with an honest advisory:
1279
+ // if the lesson is genuinely new, the caller teaches it EXPLICITLY with the full text.
1280
+ write(`dz teach --reinforce: no existing pattern matched ${JSON.stringify(reinforce)} — nothing reinforced`);
1281
+ write(' If this is a genuinely NEW lesson, teach it explicitly: dz teach "<full lesson text>" --reward <0-1> --domain <area>');
1282
+ write(' To find the exact pattern to reinforce: dz recall "<terms>" (match by its full text)');
1283
+ return 1;
1284
+ }
1285
+
1182
1286
  const pattern = options.get('_positional_0');
1183
1287
  if (!pattern) {
1184
1288
  write('dz teach: pattern description required');
@@ -1191,6 +1295,23 @@ async function cmdTeach(options: Map<string, string>, flags: Set<string>, cwd: s
1191
1295
  const reward = parseFloat(options.get('reward') ?? '0.8');
1192
1296
  const domain = options.get('domain') ?? 'general';
1193
1297
 
1298
+ if (flags.has('guard')) {
1299
+ const verdict = await teachGuard(projectRoot, pattern, { reward: Math.max(0, Math.min(1, reward)) });
1300
+ if (verdict.action === 'reinforce') {
1301
+ const backend = resolveLearningBackend(projectRoot);
1302
+ backend.addSample({ dzId: verdict.dzId, kind: 'reinforce', reward: Math.max(0, Math.min(1, reward)), ts: new Date().toISOString() });
1303
+ const trained = await backend.train();
1304
+ // HIGH-fix: only claim success when the reinforce actually FLUSHED. With backend 'off'
1305
+ // (NoopLearningBackend) or a flush failure, flushed === 0 — falling through to the plain
1306
+ // teach below so the lesson is NEVER silently discarded (the exact silent-drop the ADR forbids).
1307
+ if (trained.flushed > 0) {
1308
+ write(`↳ reinforced existing pattern ${verdict.dzId} (cos=${verdict.cosine.toFixed(2)}) — not re-added`);
1309
+ return 0;
1310
+ }
1311
+ write(`dz teach --guard: reinforce of ${verdict.dzId} did not flush (backend off or write failure) — teaching the lesson normally instead`);
1312
+ }
1313
+ }
1314
+
1194
1315
  // Distill pattern into actionable rule (claude-smart inspired)
1195
1316
  // Convert "what happened" into "what to do next time"
1196
1317
  const isRule = pattern.toLowerCase().startsWith('always') || pattern.toLowerCase().startsWith('never') ||
@@ -1293,6 +1414,23 @@ async function cmdRecall(options: Map<string, string>, flags: Set<string>, cwd:
1293
1414
  // With --json this is the portable export the agentdb-memory MCP bridge consumes.
1294
1415
  if (all) {
1295
1416
  const patterns = loadStorePatternsSync(projectRoot);
1417
+ if (flags.has('stats')) {
1418
+ const stats = storeStats(projectRoot);
1419
+ const backendStats = resolveLearningBackend(projectRoot).getStats();
1420
+ if (asJson) {
1421
+ write(JSON.stringify({ patterns, stats, learning: backendStats }));
1422
+ return 0;
1423
+ }
1424
+ write(`dz recall --all --stats — ${patterns.length} learned pattern(s)`);
1425
+ write(` backend: ${backendStats.backend}${backendStats.advisory !== undefined ? ` (${backendStats.advisory})` : ''}`);
1426
+ write(` domains: ${Object.entries(stats.perDomain).map(([k, v]) => `${k}=${v}`).join(', ') || 'none'}`);
1427
+ write(` exact-dup groups: ${stats.exactDupGroups}`);
1428
+ write(` re-teach trend: ${stats.teachEvents} teach event(s), ${stats.reinforceEvents} reinforce event(s)`);
1429
+ write(' top uses:');
1430
+ for (const row of stats.topUses) write(` ${row.uses}× [${row.reward.toFixed(2)}] (${row.domain}) ${row.pattern.slice(0, 80)}`);
1431
+ write(' near-dup density: run dz vector harmonize (dry-run)');
1432
+ return 0;
1433
+ }
1296
1434
  if (asJson) {
1297
1435
  write(JSON.stringify(patterns));
1298
1436
  } else {
@@ -1767,12 +1905,11 @@ async function cmdBrain(
1767
1905
  // Default recall = pure FTS order (nothing regresses). `--rerank` opts into the deterministic
1768
1906
  // lexical reranker (over-fetch → field-weighted reorder → trim) for a more on-point top-K.
1769
1907
  const rerank = flags.has('rerank');
1770
- // Default MATCH = all (AND) — precise. `--any` (OR) is recall-friendly for morphologically-varied
1771
- // queries: nominative query forms (репликация) don't prefix stored genitive forms (репликации),
1772
- // so AND can return 0 while a great KU exists (grounding already uses OR internally). Pairs well
1773
- // with --rerank: over-fetch broadly (OR), then reorder on-point first.
1908
+ // Default MATCH = all (AND) — precise when it hits. If it returns zero hits, queryBrain retries
1909
+ // once with OR and labels the result as broadened. Explicit `--any` still starts broad and is not
1910
+ // labeled as a fallback. Pairs well with --rerank: over-fetch broadly, then reorder on-point first.
1774
1911
  const anyMatch = flags.has('any');
1775
- const { hits, error } = await queryBrain({
1912
+ const queryResult = await queryBrain({
1776
1913
  query,
1777
1914
  limit,
1778
1915
  depsRoot: resolveDepsRoot(cwd),
@@ -1780,8 +1917,13 @@ async function cmdBrain(
1780
1917
  ...(rerank ? { rerank: true } : {}),
1781
1918
  ...(anyMatch ? { match: 'any' as const } : {}),
1782
1919
  });
1783
- if (asJson) { write(JSON.stringify(hits)); return 0; }
1920
+ const { hits, error, broadened } = queryResult;
1921
+ if (asJson) {
1922
+ write(JSON.stringify(broadened === true ? { hits, broadened: true } : hits));
1923
+ return 0;
1924
+ }
1784
1925
  write(`dz brain query "${query}"${source !== undefined ? ` --source ${source}` : ''}${anyMatch ? ' --any' : ''}${rerank ? ' --rerank' : ''} — ${hits.length} KU hit(s)`);
1926
+ if (broadened === true) write(` broadened to any-term match: ${hits.length} hit(s)`);
1785
1927
  if (error !== undefined) write(` (${error})`);
1786
1928
  for (const h of hits) {
1787
1929
  const loc = h.chapter !== undefined
@@ -3365,6 +3507,8 @@ export async function runCli(argv: string[], io: CliIo = {}): Promise<number> {
3365
3507
  return await cmdBrain(options, flags, cwd, write, readStdin);
3366
3508
  case 'statusline':
3367
3509
  return cmdStatusline(options, flags, cwd, write, readStdin);
3510
+ case 'usage':
3511
+ return cmdUsage(options, flags, cwd, write);
3368
3512
  case 'setup':
3369
3513
  return await cmdSetup(options, flags, cwd, write);
3370
3514
  case 'pretrain':
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 dzhechko
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.