@dzhechkov/harness-cli 0.3.259 → 0.3.261

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.259",
3
+ "version": "0.3.261",
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.149",
44
+ "@dzhechkov/harness-core": "^0.3.150",
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": "6b3bcc252253732b9703f1eb43ac1f0e9f0599979b09d0fea3208932fd0f33a8"
28
+ "content": "61c71e355d633a88401a19c247e517d69f7a8d1483ea7549b3505eccb2a8740b"
29
29
  }
30
30
  ]
31
31
  },
@@ -105,7 +105,7 @@
105
105
  "hashes": [
106
106
  {
107
107
  "alg": "SHA-256",
108
- "content": "4c5fa8c51cc350874413efceb0a2e4dce2f0c71f39914ddd8be425336a37ae2d"
108
+ "content": "942d09c054994014e50139b807e1984586e172809b21e309114a5bf2ea92667c"
109
109
  }
110
110
  ]
111
111
  },
@@ -115,7 +115,7 @@
115
115
  "hashes": [
116
116
  {
117
117
  "alg": "SHA-256",
118
- "content": "a75034dfe70f0b57811dfad831366bd5c7995583a57db0f218621b026ac8d854"
118
+ "content": "43b1280af2070d30296162cc33a045a23452201b2da2ce7aec12eea1900451f3"
119
119
  }
120
120
  ]
121
121
  },
@@ -125,7 +125,7 @@
125
125
  "hashes": [
126
126
  {
127
127
  "alg": "SHA-256",
128
- "content": "dbe8fa418aedcb77dead3a374bba7cbf0fb5e305dd9a06002946a19604279684"
128
+ "content": "d0847603ca911ccca9e0838d54dadd1924ac0d9008e103c4edaef16dc4be0678"
129
129
  }
130
130
  ]
131
131
  },
@@ -185,7 +185,7 @@
185
185
  "hashes": [
186
186
  {
187
187
  "alg": "SHA-256",
188
- "content": "72858a1f0199832c7ad781f892b7b117ec980af33ff9598587e1ed1544046a35"
188
+ "content": "582e9b8e1b919650b444b6be90cc3a1ae54e2fd190524fdb3ffa46c8f2058990"
189
189
  }
190
190
  ]
191
191
  },
@@ -205,7 +205,7 @@
205
205
  "hashes": [
206
206
  {
207
207
  "alg": "SHA-256",
208
- "content": "1481238a2fc3a60051f678dcf6b415f8052ce954a9e55ee7577d8c3b2261e062"
208
+ "content": "e6b54081432afb9d3f1ee8a851c0e6b5ec8012dad4b9ae02a0d9036772226044"
209
209
  }
210
210
  ]
211
211
  },
package/src/cli.ts CHANGED
@@ -275,6 +275,10 @@ import {
275
275
  makeBacklogIO,
276
276
  harmonizeBacklog,
277
277
  BACKLOG_BACKENDS,
278
+ applyDomainBoost,
279
+ countDisplacedByCut,
280
+ renderDomainBoostNote,
281
+ renderDomainCutNote,
278
282
  parseReqeDebt,
279
283
  buildReqeBrief,
280
284
  settleReqeDebt,
@@ -333,7 +337,7 @@ Usage:
333
337
  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)
334
338
  dz teach --from-json <file> [--project <dir>] [--no-mirror] (bulk-import a 'dz recall --all --json' export — share a learned store across machines)
335
339
  dz consolidate [--sessions-dir <dir>] [--project <dir>] [--no-mirror] [--prune-noise [--apply]] [--prune-quarantine [--apply]] (both prunes: DRY-RUN by default; --apply snapshots then deletes; prune-quarantine = expired unproven lessons ONLY, never coupled to noise)
336
- 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] | dz recall --promote <dzId>[,<dzId>] [--apply] (forget/promote: dry-run default; forget snapshots before removing; promote lifts lesson-quarantine)
340
+ dz recall "<query>" [--limit <N>] [--domain <name>] [--semantic | --no-semantic] [--books [--book <slug>]] [--project <dir>] | dz recall --all [--json] | dz recall --usage [--json] | dz recall --forget <dzId>[,<dzId>] [--apply] | dz recall --promote <dzId>[,<dzId>] [--apply] (--domain <name> BOOSTS lessons of that domain without dropping foreign ones — a shared store keeps its cross-domain transfers; forget/promote: dry-run default; forget snapshots before removing; promote lifts lesson-quarantine)
337
341
  dz vector status [--project <dir>] [--json] (semantic tier: engine, mirrored vs lexical counts, pending queue)
338
342
  dz vector reindex [--project <dir>] [--json] (snapshot, re-embed learned-pattern vectors, stamp current model)
339
343
  dz vector export <path> [--project <dir>] (portable VECTOR form (.rvf, opt-in RVF engine); patterns ship via recall --all --json)
@@ -2241,13 +2245,31 @@ async function cmdRecall(options: Map<string, string>, flags: Set<string>, cwd:
2241
2245
  : flags.has('no-semantic') || flags.has('lexical')
2242
2246
  ? ('lexical' as const)
2243
2247
  : ('hybrid' as const);
2244
- const result = await recallHybrid(projectRoot, query, { limit, mode });
2248
+ const wantedDomain = options.get('domain');
2249
+ // OVER-FETCH before boosting (Codex QE #5): the boost used to run on hits ALREADY
2250
+ // truncated to `limit`, so an exact-domain lesson sitting at rank limit+1 could
2251
+ // never receive its promised lift — the feature was weakest in exactly the case it
2252
+ // exists for (foreign-domain dilution pushing a relevant lesson just past the cut).
2253
+ // Fetch a bounded surplus, re-rank, then trim to the limit the caller asked for.
2254
+ const fetchLimit = wantedDomain !== undefined ? Math.min(limit * 3, limit + 20) : limit;
2255
+ const result = await recallHybrid(projectRoot, query, { limit: fetchLimit, mode });
2256
+
2245
2257
  if (mode === 'semantic' && result.vectorEngine === 'none') {
2246
2258
  // --semantic is an explicit ask — degrading it silently would be dishonest (FR-3).
2247
2259
  write(`dz recall --semantic: ${result.vectorReason ?? 'no vector engine available — run: dz setup --memory agentdb'}`);
2248
2260
  return 1;
2249
2261
  }
2250
- const hits = result.hits;
2262
+ // Domain-aware re-ranking (health-advisor slice H): `--domain <name>` lifts lessons
2263
+ // tagged with that domain WITHOUT dropping foreign ones — a boost, not a filter, so a
2264
+ // shared store keeps the cross-domain transfers that make it worth more than two stores.
2265
+ const boost = wantedDomain !== undefined ? applyDomainBoost(result.hits, wantedDomain) : null;
2266
+ const hits = (boost ? boost.hits : result.hits).slice(0, limit);
2267
+ // The boost never drops a hit, but the CUT still can: promoting a match into the top
2268
+ // `limit` pushes the last one out, so a lesson visible WITHOUT --domain can vanish
2269
+ // WITH it. Cross-model review called this out as a lie by omission — the note said
2270
+ // "foreign-domain lessons kept" about the pre-cut list while the printed list was
2271
+ // missing one. Count it and say so; the reader can act on it (raise --limit).
2272
+ const displaced = boost !== null ? countDisplacedByCut(result.hits, boost.hits, limit) : 0;
2251
2273
  if (asJson) {
2252
2274
  // Portable contract UNCHANGED (I-7/AC-6): a plain PatternRecord[] — round-trips through
2253
2275
  // `dz teach --from-json` regardless of which backend ranked each hit.
@@ -2258,6 +2280,10 @@ async function cmdRecall(options: Map<string, string>, flags: Set<string>, cwd:
2258
2280
  if (hits.length === 0) {
2259
2281
  write(`dz recall "${query}"`);
2260
2282
  write(` No matching patterns (teach some with \`dz teach\`, or consolidate sessions).`);
2283
+ // The domain note must print here too (Codex QE #10): a --domain run with zero hits
2284
+ // silently said nothing about the domain, so the reader could not tell whether the
2285
+ // boost had been applied and found nothing, or had not run at all.
2286
+ if (boost !== null && wantedDomain !== undefined) write(renderDomainBoostNote(boost, wantedDomain));
2261
2287
  return 0;
2262
2288
  }
2263
2289
  const vectorOn = result.vectorEngine !== 'none' && result.vectorError === undefined && mode !== 'lexical';
@@ -2269,12 +2295,30 @@ async function cmdRecall(options: Map<string, string>, flags: Set<string>, cwd:
2269
2295
  const backendTag = vectorOn ? ` ⟨${h.backend}⟩` : '';
2270
2296
  const qTag = h.quarantined === true ? ' ⚠q' : '';
2271
2297
  if (h.quarantined === true) sawQuarantined = true;
2272
- write(` [${h.pattern.reward.toFixed(2)}] (${h.pattern.domain})${backendTag}${qTag} ${h.pattern.pattern.slice(0, 80)}`);
2298
+ // ONE line per hit, always. A lesson may contain newlines, and printing them raw
2299
+ // let stored CONTENT forge lines that look like the tool's own output — including
2300
+ // the domain-boost note that learning_bridge.py reads as a capability probe. Any
2301
+ // consumer that parses this output line-wise has the same exposure, so the fix
2302
+ // belongs at the point of rendering rather than in each reader.
2303
+ // Sanitise EVERY stored field that reaches the line, and every character a reader
2304
+ // might treat as a break — not just CR/LF. Round 4 forged the domain-boost note
2305
+ // twice over: once through `domain`, which was rendered raw, and once through a
2306
+ // U+2028 that Python's splitlines() honours and this replace did not.
2307
+ const oneLine = (v: string): string => v.replace(/[\r\n\u2028\u2029\u0085\v\f]+/g, ' ⏎ ');
2308
+ const shown = oneLine(h.pattern.pattern).slice(0, 80);
2309
+ write(` [${h.pattern.reward.toFixed(2)}] (${oneLine(h.pattern.domain)})${backendTag}${qTag} ${shown}`);
2273
2310
  }
2274
2311
  if (sawQuarantined) {
2275
2312
  // The loop stays VISIBLE (ADR D2): a quarantined hit is shown, marked, and explained.
2276
2313
  write(' ⚠q = quarantined (unproven hypothesis, rank damped) — confirm with dz teach --reinforce, or dz recall --promote <dzId> --apply');
2277
2314
  }
2315
+ if (boost !== null && wantedDomain !== undefined) {
2316
+ // Say what the boost did — INCLUDING when it did nothing. A silent reorder would
2317
+ // let the reader believe the ranking was domain-aware when it had no match to work with.
2318
+ write(renderDomainBoostNote(boost, wantedDomain));
2319
+ const cutNote = renderDomainCutNote(displaced, limit);
2320
+ if (cutNote !== '') write(cutNote);
2321
+ }
2278
2322
  if (result.vectorError !== undefined && mode !== 'lexical') {
2279
2323
  // Engine present but the semantic leg failed/timed out — one honest line, exit 0 (05 §2.3).
2280
2324
  write(` ℹ vector search degraded: ${result.vectorError} — showing lexical ranking`);