@wrongstack/cli 0.308.0 → 0.308.1

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.
@@ -13,7 +13,7 @@ import {
13
13
  runOAuthLoginKind,
14
14
  runOAuthLoginMenu,
15
15
  validateFamily
16
- } from "./chunk-TOG7OMKJ.js";
16
+ } from "./chunk-7JOMY3HO.js";
17
17
  import {
18
18
  restoreFlags
19
19
  } from "./chunk-CSAPOCBP.js";
@@ -596,4 +596,4 @@ async function runAuthRemove(deps, providerId) {
596
596
  export {
597
597
  authCmd
598
598
  };
599
- //# sourceMappingURL=auth-TVOOHL4Q.js.map
599
+ //# sourceMappingURL=auth-WOKC2RVG.js.map
@@ -795,15 +795,15 @@ var COPILOT_PROVIDER_ID = "github-copilot";
795
795
  function sleep(ms, signal) {
796
796
  return new Promise((resolve, reject) => {
797
797
  if (signal.aborted) return reject(new DOMException("Aborted", "AbortError"));
798
- const t = setTimeout(resolve, ms);
799
- signal.addEventListener(
800
- "abort",
801
- () => {
802
- clearTimeout(t);
803
- reject(new DOMException("Aborted", "AbortError"));
804
- },
805
- { once: true }
806
- );
798
+ const t = setTimeout(() => {
799
+ signal.removeEventListener("abort", onAbort);
800
+ resolve();
801
+ }, ms);
802
+ function onAbort() {
803
+ clearTimeout(t);
804
+ reject(new DOMException("Aborted", "AbortError"));
805
+ }
806
+ signal.addEventListener("abort", onAbort, { once: true });
807
807
  });
808
808
  }
809
809
  async function startDeviceFlow(signal) {
@@ -1374,4 +1374,4 @@ export {
1374
1374
  addCustomProvider,
1375
1375
  addKeyForProvider
1376
1376
  };
1377
- //# sourceMappingURL=chunk-TOG7OMKJ.js.map
1377
+ //# sourceMappingURL=chunk-7JOMY3HO.js.map
@@ -81,7 +81,7 @@ import {
81
81
  runClaudeOAuthLogin,
82
82
  runCopilotOAuthLogin,
83
83
  validateFamily
84
- } from "./chunk-TOG7OMKJ.js";
84
+ } from "./chunk-7JOMY3HO.js";
85
85
  import {
86
86
  LOCAL_LLM_PRESETS,
87
87
  parseSpawnFlags
@@ -5563,7 +5563,7 @@ async function runCliExecution(params) {
5563
5563
  governanceHandle,
5564
5564
  setConfig
5565
5565
  } = params;
5566
- const { execute } = await import("./execution-4S6RSEQI.js");
5566
+ const { execute } = await import("./execution-PHX3NFEK.js");
5567
5567
  return execute(
5568
5568
  toExecuteDeps({
5569
5569
  core: {
@@ -6517,7 +6517,7 @@ import { SageDomainTermExtractor } from "@wrongstack/sage";
6517
6517
  async function refreshDomainTermsMirror(options) {
6518
6518
  try {
6519
6519
  const extractor = new SageDomainTermExtractor();
6520
- return await extractor.writeDomainTermsFile(options.projectRoot, options.memoryStore);
6520
+ return await extractor.writeDomainTermsFile(options.projectRoot, []);
6521
6521
  } catch (err) {
6522
6522
  writeErr2(
6523
6523
  `[wrongstack] domain-terms mirror refresh failed: ${err instanceof Error ? err.message : String(err)}
@@ -6570,7 +6570,7 @@ async function setupCliPromptAndTools(params) {
6570
6570
  pathJoiner: { join: (a, b) => path7.join(a, b) },
6571
6571
  systemPromptBuilderToken: TOKENS8.SystemPromptBuilder
6572
6572
  });
6573
- await refreshDomainTermsMirror({ projectRoot, memoryStore });
6573
+ await refreshDomainTermsMirror({ projectRoot });
6574
6574
  const toolRegistry = new ToolRegistry2();
6575
6575
  registerBuiltinTools({
6576
6576
  toolRegistry,
@@ -18345,6 +18345,47 @@ function formatAudit(rows) {
18345
18345
  function formatLegacyImport(result) {
18346
18346
  return `Legacy import complete: ${result.imported} imported, ${result.skipped} skipped from ${result.files} file(s).`;
18347
18347
  }
18348
+ function formatMemoryDiagnostics(diag) {
18349
+ const lines = ["\u{1FA7A} Memory diagnostics", ""];
18350
+ const byStatus = Object.entries(diag.sageStats.byStatus).map(([k, v]) => `${k}=${v}`).join(" ");
18351
+ const byKind = Object.entries(diag.sageStats.byKind).map(([k, v]) => `${k}=${v}`).join(" ");
18352
+ lines.push("SAGE (lexical):");
18353
+ lines.push(` total entries: ${diag.sageTotal}`);
18354
+ lines.push(` by status: ${byStatus || "\u2205"}`);
18355
+ lines.push(` by kind: ${byKind || "\u2205"}`);
18356
+ if (diag.sageStats.injections !== void 0) {
18357
+ lines.push(` injections: ${diag.sageStats.injections}`);
18358
+ }
18359
+ if (diag.sageStats.uses !== void 0) {
18360
+ lines.push(` uses: ${diag.sageStats.uses}`);
18361
+ }
18362
+ lines.push("");
18363
+ if (!diag.vector) {
18364
+ lines.push("Vector memory: disabled \u2014 running on the SAGE-only surface.");
18365
+ } else {
18366
+ const v = diag.vector;
18367
+ lines.push("Vector (semantic):");
18368
+ lines.push(` entries: ${v.entries}`);
18369
+ lines.push(` with vectors: ${v.vectors}`);
18370
+ lines.push(` providers: ${v.providers.join(", ") || "\u2205"}`);
18371
+ lines.push(` model: ${v.modelId} (${v.dimensions}-dim)`);
18372
+ lines.push(` cache: ${v.cacheEntries} rows \xB7 ${v.cacheProviders} provider(s) \xB7 ${v.totalUseCount} hits`);
18373
+ if (v.oldestLastUsedAt) {
18374
+ lines.push(` cache oldest: ${v.oldestLastUsedAt}`);
18375
+ }
18376
+ lines.push(` store path: ${v.storePath}`);
18377
+ lines.push("");
18378
+ lines.push("Cross-system coverage:");
18379
+ lines.push(` vector \u2194 SAGE: ${v.mirroredInSage} mirrored`);
18380
+ if (v.standalone > 0) {
18381
+ lines.push(` standalone: ${v.standalone} vector-only entries (no SAGE id \u2014 lexical recall misses these)`);
18382
+ }
18383
+ if (v.mirroredInSage === 0 && diag.sageTotal > 0) {
18384
+ lines.push(" \u{1F4A1} Run `wrongstack --vector-sync` to backfill the vector mirror.");
18385
+ }
18386
+ }
18387
+ return lines.join("\n");
18388
+ }
18348
18389
  function formatSageStats(stats, scopedCount, scopedRoles) {
18349
18390
  const lines = [
18350
18391
  "## SAGE Stats",
@@ -18407,6 +18448,60 @@ function formatSageShow(stats, memories) {
18407
18448
  lines.push(`*${memories.length} entr${memories.length === 1 ? "y" : "ies"}*`);
18408
18449
  return lines.join("\n");
18409
18450
  }
18451
+ function formatSearchRace(race) {
18452
+ const lines = [];
18453
+ const pct2 = (value) => `${Math.round(value * 100)}%`;
18454
+ lines.push(`## \u{1F3C1} Search race \u2014 "${race.query}"`);
18455
+ lines.push("");
18456
+ lines.push(
18457
+ `lexical: ${race.metrics.lexicalCount} \xB7 vector: ${race.metrics.vectorCount} \xB7 overlap: ${race.metrics.overlapCount} \xB7 agreement: ${pct2(race.metrics.agreementRatio)}`
18458
+ );
18459
+ if (race.metrics.lexicalOnlyRatio > 0) {
18460
+ lines.push(
18461
+ ` \u21B3 lexical-only: ${pct2(race.metrics.lexicalOnlyRatio)} of lexical recall is invisible to vector.`
18462
+ );
18463
+ }
18464
+ if (race.metrics.vectorOnlyRatio > 0) {
18465
+ lines.push(
18466
+ ` \u21B3 vector-only: ${pct2(race.metrics.vectorOnlyRatio)} of vector recall is invisible to lexical.`
18467
+ );
18468
+ }
18469
+ lines.push("");
18470
+ if (race.overlap.length > 0) {
18471
+ lines.push("### \u2705 Both channels");
18472
+ for (const row of race.overlap) {
18473
+ lines.push(
18474
+ `- \`${row.id.slice(0, 12)}\u2026\` L=${pct2(row.lexicalScore)} V=${pct2(row.vectorScore)} ${row.preview}`
18475
+ );
18476
+ }
18477
+ lines.push("");
18478
+ }
18479
+ if (race.lexicalOnly.length > 0) {
18480
+ lines.push("### \u{1F170}\uFE0F Lexical only (vector missed)");
18481
+ for (const row of race.lexicalOnly) {
18482
+ lines.push(
18483
+ `- \`${row.id.slice(0, 12)}\u2026\` L=${pct2(row.lexicalScore ?? 0)} ${row.preview}`
18484
+ );
18485
+ }
18486
+ lines.push("");
18487
+ }
18488
+ if (race.vectorOnly.length > 0) {
18489
+ lines.push("### \u{1F171}\uFE0F Vector only (lexical missed)");
18490
+ for (const row of race.vectorOnly) {
18491
+ lines.push(
18492
+ `- \`${row.id.slice(0, 12)}\u2026\` V=${pct2(row.vectorScore ?? 0)} ${row.preview}`
18493
+ );
18494
+ }
18495
+ lines.push("");
18496
+ }
18497
+ if (race.overlap.length === 0 && race.lexicalOnly.length === 0 && race.vectorOnly.length === 0) {
18498
+ lines.push("_No memories matched either channel for this query._");
18499
+ }
18500
+ return lines.join("\n");
18501
+ }
18502
+
18503
+ // src/slash-commands/memory.ts
18504
+ import { runSearchRace } from "@wrongstack/vector-memory";
18410
18505
 
18411
18506
  // src/slash-commands/memory-gather.ts
18412
18507
  import { toErrorMessage as toErrorMessage15 } from "@wrongstack/core/utils";
@@ -18935,11 +19030,12 @@ async function fileProposals(Sage, proposals) {
18935
19030
  }
18936
19031
 
18937
19032
  // src/slash-commands/memory.ts
19033
+ var DOMAIN_TERM_TAG = "domain-term";
18938
19034
  function buildMemoryCommand(opts) {
18939
19035
  return {
18940
19036
  name: "memory",
18941
19037
  category: "Inspect",
18942
- description: "Inspect or edit persistent memory: /memory [show|search|file|path|for-file|graph|gather|remember|update|delete|forget|hygiene|verify|candidates|triage|audit|import-legacy|clear|compact|compact-log|stats|audience]",
19038
+ description: "Inspect or edit persistent memory: /memory [show|search|race|file|path|for-file|graph|gather|remember|update|delete|forget|hygiene|verify|candidates|triage|audit|import-legacy|clear|compact|compact-log|stats|audience|diagnostics|purge-domain-terms]",
18943
19039
  async run(args) {
18944
19040
  const store = opts.memoryStore;
18945
19041
  if (!store) return { message: "No memory store configured." };
@@ -19139,6 +19235,32 @@ function buildMemoryCommand(opts) {
19139
19235
  )
19140
19236
  };
19141
19237
  }
19238
+ case "race": {
19239
+ if (!restJoined) return { message: "Usage: /memory race <query>" };
19240
+ if (!Sage) {
19241
+ return {
19242
+ message: "`/memory race` requires the SAGE surface. Run the search with `/memory search <query>` instead."
19243
+ };
19244
+ }
19245
+ const vectorStore = opts.vectorMemoryStore;
19246
+ if (!vectorStore) {
19247
+ const entries = await Sage.searchSage(restJoined, { limit: 20 });
19248
+ return {
19249
+ message: `Vector memory is not wired in this host \u2014 only the lexical channel is available.
19250
+
19251
+ ` + formatSageMemories(entries, `Search: ${restJoined}`)
19252
+ };
19253
+ }
19254
+ try {
19255
+ const lexical = await Sage.searchSage(restJoined, { limit: 20 });
19256
+ const race = await runSearchRace(restJoined, lexical, vectorStore, { limit: 20 });
19257
+ return { message: formatSearchRace(race) };
19258
+ } catch (err) {
19259
+ return {
19260
+ message: `Race failed: ${err instanceof Error ? err.message : String(err)}`
19261
+ };
19262
+ }
19263
+ }
19142
19264
  case "graph": {
19143
19265
  if (!Sage?.graphFor) return requiresSage("graph");
19144
19266
  if (!restJoined) return { message: "Usage: /memory graph <memory-id|path|query>" };
@@ -19218,6 +19340,94 @@ function buildMemoryCommand(opts) {
19218
19340
  await store.clear();
19219
19341
  return { message: "Cleared all non-permanent memory scopes by explicit force request." };
19220
19342
  }
19343
+ case "purge-domain-terms": {
19344
+ if (!Sage) return requiresSage("purge-domain-terms");
19345
+ const force = rest.includes("--force");
19346
+ if (!force) {
19347
+ return {
19348
+ message: `Refusing to run /memory purge-domain-terms without --force.
19349
+
19350
+ This is a one-off migration that permanently removes every SAGE memory tagged \`${DOMAIN_TERM_TAG}\` (and the historical \`glossary\` / \`project-jargon\` companion tags). It cannot be undone. Run /memory purge-domain-terms --force when you are ready.`
19351
+ };
19352
+ }
19353
+ try {
19354
+ const targets = [];
19355
+ const PAGE = 200;
19356
+ let cursor;
19357
+ const listPage = Sage.listSagePage;
19358
+ if (typeof listPage === "function") {
19359
+ while (true) {
19360
+ const page = await listPage({
19361
+ statuses: ["active", "stale"],
19362
+ limit: PAGE,
19363
+ ...cursor !== void 0 ? { cursor } : {}
19364
+ });
19365
+ for (const mem of page.memories) {
19366
+ if (Array.isArray(mem.tags) && mem.tags.some(
19367
+ (t) => t === DOMAIN_TERM_TAG || t === "glossary" || t === "project-jargon"
19368
+ )) {
19369
+ targets.push({ id: mem.id, tags: mem.tags });
19370
+ }
19371
+ }
19372
+ if (!page.nextCursor || page.memories.length === 0) break;
19373
+ cursor = page.nextCursor;
19374
+ }
19375
+ } else {
19376
+ const memories = await Sage.listSage(["active", "stale"]);
19377
+ for (const mem of memories) {
19378
+ if (Array.isArray(mem.tags) && mem.tags.some(
19379
+ (t) => t === DOMAIN_TERM_TAG || t === "glossary" || t === "project-jargon"
19380
+ )) {
19381
+ targets.push({ id: mem.id, tags: mem.tags });
19382
+ }
19383
+ }
19384
+ }
19385
+ if (targets.length === 0) {
19386
+ return {
19387
+ message: `No SAGE memories matched the legacy domain-term tag trio. Nothing to purge.`
19388
+ };
19389
+ }
19390
+ let deleted = 0;
19391
+ const failures = [];
19392
+ for (const t of targets) {
19393
+ try {
19394
+ await Sage.deleteSage(
19395
+ t.id,
19396
+ "domain-term persistence removed; pre-migration cleanup",
19397
+ { force: true }
19398
+ );
19399
+ deleted++;
19400
+ } catch (err) {
19401
+ failures.push(`${t.id}: ${toErrorMessage17(err)}`);
19402
+ }
19403
+ }
19404
+ const tagSummary = /* @__PURE__ */ new Map();
19405
+ for (const t of targets) {
19406
+ for (const tag of t.tags) {
19407
+ if (tag === DOMAIN_TERM_TAG || tag === "glossary" || tag === "project-jargon") {
19408
+ tagSummary.set(tag, (tagSummary.get(tag) ?? 0) + 1);
19409
+ }
19410
+ }
19411
+ }
19412
+ const tagBreakdown = [...tagSummary.entries()].sort((a, b) => b[1] - a[1]).map(([tag, count]) => ` - ${tag}: ${count}`).join("\n");
19413
+ const tail = failures.length > 0 ? `
19414
+
19415
+ Failures (${failures.length}):
19416
+ ${failures.slice(0, 10).map((f) => ` - ${f}`).join("\n")}` : "";
19417
+ return {
19418
+ message: `Purged ${deleted} of ${targets.length} legacy domain-term memories.
19419
+
19420
+ Tag breakdown of matched memories:
19421
+ ${tagBreakdown}
19422
+
19423
+ The \`${DOMAIN_TERM_TAG}\` / \`glossary\` / \`project-jargon\` tags are now absent from the live corpus. Run \`/memory show\` to confirm.${tail}`
19424
+ };
19425
+ } catch (err) {
19426
+ return {
19427
+ message: `purge-domain-terms failed: ${toErrorMessage17(err)}`
19428
+ };
19429
+ }
19430
+ }
19221
19431
  case "compact": {
19222
19432
  return runCompact(opts);
19223
19433
  }
@@ -19282,6 +19492,71 @@ File size reduced. Audit-logged as \`memory.log_compacted\`.`
19282
19492
  if (!Sage) return requiresSage("audience");
19283
19493
  return runAudienceMemory(Sage, rest);
19284
19494
  }
19495
+ case "diagnostics": {
19496
+ if (!Sage) {
19497
+ return {
19498
+ message: "Memory diagnostics require the SAGE surface (this memory store does not expose it)."
19499
+ };
19500
+ }
19501
+ const vectorStore = opts.vectorMemoryStore;
19502
+ let sageStats;
19503
+ let sageTotal = 0;
19504
+ try {
19505
+ [sageStats, sageTotal] = await Promise.all([
19506
+ Sage.stats(),
19507
+ Sage.listSagePage({ limit: 1 }).then(
19508
+ (page) => page.total ?? page.memories.length
19509
+ )
19510
+ ]);
19511
+ } catch (err) {
19512
+ return {
19513
+ message: `Memory diagnostics failed: ${err instanceof Error ? err.message : String(err)}`
19514
+ };
19515
+ }
19516
+ let vectorDiag;
19517
+ if (vectorStore) {
19518
+ try {
19519
+ const stats = vectorStore.stats();
19520
+ const cache = vectorStore.cacheStats();
19521
+ const vectorRows = vectorStore.list({ limit: 5e3 });
19522
+ let mirroredInSage = 0;
19523
+ let standalone = 0;
19524
+ for (const row of vectorRows) {
19525
+ const sageId = row.metadata?.sageId;
19526
+ if (typeof sageId === "string" && sageId.length > 0) {
19527
+ mirroredInSage++;
19528
+ } else {
19529
+ standalone++;
19530
+ }
19531
+ }
19532
+ vectorDiag = {
19533
+ entries: stats.entries,
19534
+ vectors: stats.vectors,
19535
+ providers: stats.providers,
19536
+ modelId: stats.modelId,
19537
+ dimensions: stats.dimensions,
19538
+ cacheEntries: cache.entries,
19539
+ cacheProviders: cache.providers,
19540
+ totalUseCount: cache.totalUseCount,
19541
+ oldestLastUsedAt: cache.oldestLastUsedAt,
19542
+ storePath: vectorStore.directory,
19543
+ mirroredInSage,
19544
+ standalone
19545
+ };
19546
+ } catch (err) {
19547
+ return {
19548
+ message: `Memory diagnostics (vector side) failed: ${err instanceof Error ? err.message : String(err)}`
19549
+ };
19550
+ }
19551
+ }
19552
+ return {
19553
+ message: formatMemoryDiagnostics({
19554
+ sageStats,
19555
+ sageTotal,
19556
+ vector: vectorDiag
19557
+ })
19558
+ };
19559
+ }
19285
19560
  default:
19286
19561
  return {
19287
19562
  message: unknownSubcommand(
@@ -19289,6 +19564,7 @@ File size reduced. Audit-logged as \`memory.log_compacted\`.`
19289
19564
  [
19290
19565
  "show",
19291
19566
  "search",
19567
+ "race",
19292
19568
  "file",
19293
19569
  "path",
19294
19570
  "for-file",
@@ -19307,7 +19583,8 @@ File size reduced. Audit-logged as \`memory.log_compacted\`.`
19307
19583
  "clear",
19308
19584
  "compact",
19309
19585
  "stats",
19310
- "audience"
19586
+ "audience",
19587
+ "purge-domain-terms"
19311
19588
  ],
19312
19589
  "memory"
19313
19590
  )
@@ -23527,7 +23804,8 @@ var THEME_META = {
23527
23804
  desc: "Anthony Fu's minimal palette \u2014 muted, print-like"
23528
23805
  },
23529
23806
  aura: { name: "Aura Dark", desc: "Vivid purple and spring green on near-black violet" },
23530
- "dark-plus": { name: "VS Code Dark+", desc: "VS Code's default \u2014 familiar blue/orange/teal" }
23807
+ "dark-plus": { name: "VS Code Dark+", desc: "VS Code's default \u2014 familiar blue/orange/teal" },
23808
+ monochrome: { name: "Monochrome", desc: "Pure grayscale \u2014 no hue, only luminance" }
23531
23809
  };
23532
23810
  var THEME_OPTIONS = THEME_PRESET_IDS.map((id) => ({ id, ...THEME_META[id] }));
23533
23811
  function presetHelpLines(perLine = 4) {
@@ -29419,6 +29697,7 @@ function setupCliSlashCommands(params) {
29419
29697
  renderer,
29420
29698
  events,
29421
29699
  memoryStore,
29700
+ vectorMemoryStore,
29422
29701
  context,
29423
29702
  cwd,
29424
29703
  projectRoot,
@@ -29495,6 +29774,7 @@ function setupCliSlashCommands(params) {
29495
29774
  renderer,
29496
29775
  events,
29497
29776
  memoryStore,
29777
+ vectorMemoryStore,
29498
29778
  context,
29499
29779
  cwd,
29500
29780
  projectRoot,
@@ -32065,7 +32345,12 @@ function warnUnlessMissing(logger, operation, error) {
32065
32345
  // src/cli-main.ts
32066
32346
  import {
32067
32347
  TransformersEmbeddingProvider,
32068
- VectorMemoryStore
32348
+ VectorMemoryStore,
32349
+ wrapMemoryPortWithVectorRecall
32350
+ } from "@wrongstack/vector-memory";
32351
+ import {
32352
+ startFirstBootSageSync,
32353
+ subscribeVectorMemoryToSage
32069
32354
  } from "@wrongstack/vector-memory";
32070
32355
 
32071
32356
  // src/wiring/replay-governance-setup.ts
@@ -32672,8 +32957,9 @@ async function runInteractive(cliCtx) {
32672
32957
  const modelCapabilitiesRef = {
32673
32958
  current: modelCapabilities
32674
32959
  };
32675
- const memoryStore = container.resolve(TOKENS12.MemoryStore);
32960
+ let memoryStore = container.resolve(TOKENS12.MemoryStore);
32676
32961
  await memoryStore.initialize();
32962
+ const teardownHandlers = [];
32677
32963
  let vectorMemoryStore;
32678
32964
  const vectorMemoryModelCacheDir = path32.join(
32679
32965
  projectRoot,
@@ -32695,6 +32981,34 @@ async function runInteractive(cliCtx) {
32695
32981
  );
32696
32982
  vectorMemoryStore = void 0;
32697
32983
  }
32984
+ if (vectorMemoryStore) {
32985
+ void startFirstBootSageSync({
32986
+ store: vectorMemoryStore,
32987
+ memoryStore,
32988
+ logger
32989
+ });
32990
+ memoryStore = wrapMemoryPortWithVectorRecall(memoryStore, {
32991
+ store: vectorMemoryStore,
32992
+ weight: 0.3
32993
+ });
32994
+ const mirrorHandle = subscribeVectorMemoryToSage({
32995
+ store: vectorMemoryStore,
32996
+ memoryStore,
32997
+ logger
32998
+ });
32999
+ teardownHandlers.push(() => mirrorHandle.dispose());
33000
+ if (flags["vector-sync"] === true) {
33001
+ const forced = await startFirstBootSageSync({
33002
+ store: vectorMemoryStore,
33003
+ memoryStore,
33004
+ logger,
33005
+ force: true
33006
+ });
33007
+ logger.info(
33008
+ forced.synced ? `vector-memory forced re-sync complete (${forced.marker?.indexed ?? 0} new)` : `vector-memory forced re-sync skipped: ${forced.reason}`
33009
+ );
33010
+ }
33011
+ }
32698
33012
  const skillLoader = container.resolve(TOKENS12.SkillLoader);
32699
33013
  const promptLoader = container.resolve(TOKENS12.PromptLoader);
32700
33014
  const sessionRef = {
@@ -32736,7 +33050,6 @@ async function runInteractive(cliCtx) {
32736
33050
  config: { provider: config.provider, model: config.model }
32737
33051
  });
32738
33052
  const tuiOwnsScreen = flags.tui === true && flags["no-tui"] !== true;
32739
- const teardownHandlers = [];
32740
33053
  const evOn = createTeardownEventRegistrar(events, teardownHandlers);
32741
33054
  if (vectorMemoryStore) {
32742
33055
  const store = vectorMemoryStore;
@@ -33166,6 +33479,7 @@ async function runInteractive(cliCtx) {
33166
33479
  renderer,
33167
33480
  events,
33168
33481
  memoryStore,
33482
+ vectorMemoryStore,
33169
33483
  context,
33170
33484
  cwd,
33171
33485
  projectRoot,
@@ -33402,4 +33716,4 @@ export {
33402
33716
  CLI_VERSION,
33403
33717
  runInteractive
33404
33718
  };
33405
- //# sourceMappingURL=cli-main-RJSOWUGD.js.map
33719
+ //# sourceMappingURL=cli-main-5JPPEJ6N.js.map
@@ -207,7 +207,7 @@ async function runWebUIDispatch(ctx) {
207
207
  const isSimpleUi = !isSessionChild && flags["simpleui"] === true;
208
208
  agent.disableInteractiveConfirmation();
209
209
  renderer.setSilent(true);
210
- const { runWebUI } = await import("./webui-server-A3H2SM6J.js");
210
+ const { runWebUI } = await import("./webui-server-CKIKAM2P.js");
211
211
  const flagValue = (names) => {
212
212
  for (const name of names) {
213
213
  if (!Object.hasOwn(flags, name)) continue;
@@ -1420,6 +1420,9 @@ async function resumeSession(ctx, sessionId) {
1420
1420
  }
1421
1421
  tokenCounter.reset();
1422
1422
  tokenCounter.account(resumed.data.usage, targetModel, targetProviderId);
1423
+ if (typeof resumed.data.usage?.input === "number" && resumed.data.usage.input > 0) {
1424
+ agent.ctx.lastRequestTokens = resumed.data.usage.input;
1425
+ }
1423
1426
  const reqTokens = tokenCounter.currentRequestTokens?.();
1424
1427
  const tokens = (typeof reqTokens === "number" ? reqTokens : 0) + (typeof reqTokens?.input === "number" ? reqTokens.input : 0) + (typeof reqTokens?.cacheRead === "number" ? reqTokens.cacheRead : 0) + (typeof reqTokens?.cacheWrite === "number" ? reqTokens.cacheWrite : 0);
1425
1428
  const maxContext = agent.ctx.provider?.capabilities?.maxContext ?? 0;
@@ -6042,4 +6045,4 @@ export {
6042
6045
  execute,
6043
6046
  resolveReviewerFallbackModels
6044
6047
  };
6045
- //# sourceMappingURL=execution-4S6RSEQI.js.map
6048
+ //# sourceMappingURL=execution-PHX3NFEK.js.map
package/dist/index.js CHANGED
@@ -1836,7 +1836,7 @@ var subcommandsWithFocusedHelp = Object.keys(helpTable);
1836
1836
  var loaders = {
1837
1837
  acp: async () => (await import("./acp-5ZLGFHWP.js")).acpCmd,
1838
1838
  init: async () => (await import("./init-E2NDDOHI.js")).initCmd,
1839
- auth: async () => (await import("./auth-TVOOHL4Q.js")).authCmd,
1839
+ auth: async () => (await import("./auth-WOKC2RVG.js")).authCmd,
1840
1840
  update: async () => (await import("./update-VZSOZGYC.js")).updateCmd,
1841
1841
  sessions: async () => (await import("./sessions-config-YX2ZPAFM.js")).sessionsCmd,
1842
1842
  config: async () => (await import("./sessions-config-YX2ZPAFM.js")).configCmd,
@@ -4447,7 +4447,7 @@ async function initializeCli(argv) {
4447
4447
  async function main(argv) {
4448
4448
  const cliCtx = await initializeCli(argv);
4449
4449
  if (typeof cliCtx === "number") return cliCtx;
4450
- const { runInteractive } = await import("./cli-main-RJSOWUGD.js");
4450
+ const { runInteractive } = await import("./cli-main-5JPPEJ6N.js");
4451
4451
  return runInteractive(cliCtx);
4452
4452
  }
4453
4453
 
@@ -3,6 +3,7 @@ import type { EventBus } from '@wrongstack/core/kernel';
3
3
  import type { SlashCommandRegistry, ToolRegistry } from '@wrongstack/core/registry';
4
4
  import type { CompactReport, HealthRegistry, MemoryPort, MetricsRuntimeStatus, MetricsSink, ModeStore, Renderer, SessionStore, SkillLoader, TokenCounter } from '@wrongstack/core/types';
5
5
  import type { WstackPaths } from '@wrongstack/core/utils';
6
+ import type { VectorMemoryStore } from '@wrongstack/vector-memory';
6
7
  /** Host capabilities supplied to command adapters. */
7
8
  export interface SlashCommandContext {
8
9
  registry: SlashCommandRegistry;
@@ -28,6 +29,13 @@ export interface SlashCommandContext {
28
29
  /** App-level EventBus — used by GoalRunner to emit phase/graph events to the TUI. */
29
30
  events: EventBus;
30
31
  memoryStore?: MemoryPort | undefined;
32
+ /**
33
+ * Optional vector memory store. Wired when the host has enabled the
34
+ * dual-channel system; slash commands like `/memory diagnostics`
35
+ * and `/memory race` use it to surface cross-system health and
36
+ * run lexical vs semantic channel comparisons.
37
+ */
38
+ vectorMemoryStore?: VectorMemoryStore | undefined;
31
39
  context?: Context | undefined;
32
40
  /** Working directory for the current session. */
33
41
  cwd: string;
@@ -1,4 +1,5 @@
1
1
  import type { FindMemoriesForFileResponse, LegacyImportResult, MemoryCandidate, MemoryGraphEdge, MemoryVerificationResult, Sage, SageAuditRecord, SageHygieneReport, SageStats } from '@wrongstack/sage';
2
+ import type { SearchRaceResult } from '@wrongstack/vector-memory';
2
3
  export declare function formatForFileResponse(filePath: string, response: FindMemoriesForFileResponse): string;
3
4
  export declare function requiresSage(command: string): {
4
5
  message: string;
@@ -11,6 +12,47 @@ export declare function formatHygiene(report: SageHygieneReport): string;
11
12
  export declare function formatCandidates(candidates: MemoryCandidate[]): string;
12
13
  export declare function formatAudit(rows: SageAuditRecord[]): string;
13
14
  export declare function formatLegacyImport(result: LegacyImportResult): string;
15
+ /** Pre-collected diagnostic data for `formatMemoryDiagnostics`. */
16
+ export interface MemoryDiagnostics {
17
+ sageStats: SageStats;
18
+ /** Total SAGE entries (active + non-active) in the corpus. */
19
+ sageTotal: number;
20
+ vector: {
21
+ entries: number;
22
+ vectors: number;
23
+ providers: string[];
24
+ modelId: string;
25
+ dimensions: number;
26
+ cacheEntries: number;
27
+ cacheProviders: number;
28
+ totalUseCount: number;
29
+ oldestLastUsedAt: string | null;
30
+ storePath: string;
31
+ /** Vector entries whose `metadata.sageId` resolves in SAGE. */
32
+ mirroredInSage: number;
33
+ /** Vector entries without a `metadata.sageId` (standalone). */
34
+ standalone: number;
35
+ } | undefined;
36
+ }
37
+ /**
38
+ * Two-system health snapshot — covers SAGE stats, vector memory
39
+ * stats, and the cross-system coverage (how many SAGE memories have a
40
+ * vector mirror). Surfaces the value of running both stores side by
41
+ * side: the operator sees drift (vector entries without a SAGE id),
42
+ * the embedding cache hit ratio, and the active provider / dimension.
43
+ */
44
+ export declare function formatMemoryDiagnostics(diag: MemoryDiagnostics): string;
14
45
  export declare function formatSageStats(stats: SageStats, scopedCount?: number, scopedRoles?: string): string;
15
46
  export declare function formatSageShow(stats: SageStats, memories: Sage[]): string;
47
+ /**
48
+ * Human-readable rendering of a `SearchRaceResult` — the channel
49
+ * comparison that makes the dual system's value visible. The output
50
+ * groups memories into three buckets: **both** (lexical + vector
51
+ * agreement), **lexical only** (rare-token matches the embedding
52
+ * model would under-rank), and **vector only** (semantic recall the
53
+ * FTS index would have missed). The summary metrics above the lists
54
+ * make the "what would I have missed?" question answerable in one
55
+ * glance.
56
+ */
57
+ export declare function formatSearchRace(race: SearchRaceResult): string;
16
58
  //# sourceMappingURL=memory-formatters.d.ts.map
@@ -8,7 +8,9 @@ export declare function startDeferredHttpListen(args: {
8
8
  host: string;
9
9
  httpPort: number;
10
10
  logger: ListenLogger;
11
- }): Promise<void>;
11
+ /** Fail-fast bind (no auto-advance on EADDRINUSE). Mirrors WEBUI_STRICT_PORT. */
12
+ strictPort?: boolean;
13
+ }): Promise<number>;
12
14
  export declare function startIpv6LoopbackProxy(args: {
13
15
  primary: http.Server;
14
16
  httpPort: number;
@@ -32,6 +32,7 @@ import {
32
32
  createEmbeddedProviderOperations,
33
33
  envFlag,
34
34
  findFreePort,
35
+ isStrictPort,
35
36
  resolveAuthToken,
36
37
  sendSerialized
37
38
  } from "@wrongstack/webui-server";
@@ -649,18 +650,19 @@ import {
649
650
 
650
651
  // src/webui-server/listen-helpers.ts
651
652
  import * as http from "node:http";
653
+ import { listenWithRetry } from "@wrongstack/webui-server";
652
654
  async function startDeferredHttpListen(args) {
653
- const { server, host, httpPort, logger } = args;
654
- await new Promise((resolveListen, rejectListen) => {
655
- server.listen(httpPort, host, () => resolveListen());
656
- server.once("error", rejectListen);
655
+ const { server, host, httpPort, logger, strictPort } = args;
656
+ const boundPort = await listenWithRetry(server, host, httpPort, {
657
+ maxTries: strictPort ? 1 : 10
657
658
  });
658
659
  server.on("error", (err) => {
659
660
  logger.error("http_server_error", {
660
661
  message: err.message,
661
- port: httpPort
662
+ port: boundPort
662
663
  });
663
664
  });
665
+ return boundPort;
664
666
  }
665
667
  async function startIpv6LoopbackProxy(args) {
666
668
  const { primary, httpPort, logger } = args;
@@ -1207,12 +1209,12 @@ async function runWebUI(opts) {
1207
1209
  const surface = opts.surface ?? "webui";
1208
1210
  const surfaceDefaults = surface === "simpleui" ? { http: 3466 } : { http: 3456 };
1209
1211
  const requestedHttpPort = opts.httpPort ?? opts.port ?? surfaceDefaults.http;
1210
- const strictPort = opts.strictPort ?? (process.env["WEBUI_STRICT_PORT"] === "1" || process.env["WEBUI_STRICT_PORT"] === "true");
1212
+ const strictPort = opts.strictPort ?? isStrictPort();
1211
1213
  let httpPort = requestedHttpPort;
1212
1214
  if (!strictPort) {
1213
1215
  httpPort = await findFreePort(host, requestedHttpPort);
1214
1216
  }
1215
- const wsPort = httpPort;
1217
+ let wsPort = httpPort;
1216
1218
  const globalRoot = opts.globalConfigPath ? path7.dirname(opts.globalConfigPath) : wstackGlobalRoot();
1217
1219
  const profileConfigPath = opts.profileConfigPath ?? opts.globalConfigPath ?? path7.join(globalRoot, "config.json");
1218
1220
  const rateLimitMax = Number.parseInt(process.env["WEBUI_RATE_LIMIT"] ?? "600", 10);
@@ -1303,7 +1305,7 @@ async function runWebUI(opts) {
1303
1305
  return void 0;
1304
1306
  }
1305
1307
  }).filter((value) => Boolean(value));
1306
- const accessUrl = buildWebUIAccessUrl({
1308
+ let accessUrl = buildWebUIAccessUrl({
1307
1309
  host,
1308
1310
  port: httpPort,
1309
1311
  token: wsToken,
@@ -1332,17 +1334,24 @@ async function runWebUI(opts) {
1332
1334
  apiToken: wsToken,
1333
1335
  requireToken,
1334
1336
  deferListen: surface === "simpleui",
1337
+ strictPort,
1335
1338
  ...opts.getVectorMemoryStore ? { getVectorMemoryStore: opts.getVectorMemoryStore } : {},
1336
1339
  ...opts.vectorMemoryModelCacheDir ? { vectorMemoryModelCacheDir: opts.vectorMemoryModelCacheDir } : {}
1337
1340
  });
1338
1341
  const wss = httpServer ? new WebSocketServer({ server: httpServer.server, maxPayload: 20 * 1024 * 1024 }) : new WebSocketServer({ port: httpPort, host, maxPayload: 20 * 1024 * 1024 });
1339
- if (httpServer && surface === "simpleui") {
1340
- await startDeferredHttpListen({
1342
+ if (httpServer) {
1343
+ const boundPort = surface === "simpleui" ? await startDeferredHttpListen({
1341
1344
  server: httpServer.server,
1342
1345
  host,
1343
1346
  httpPort,
1344
- logger: consoleLogger
1345
- });
1347
+ logger: consoleLogger,
1348
+ strictPort
1349
+ }) : httpServer.port;
1350
+ if (boundPort !== httpPort) {
1351
+ httpPort = boundPort;
1352
+ wsPort = boundPort;
1353
+ accessUrl = buildWebUIAccessUrl({ host, port: httpPort, token: wsToken, publicUrl });
1354
+ }
1346
1355
  }
1347
1356
  let ipv6LoopbackServer = null;
1348
1357
  if (httpServer && host === "127.0.0.1") {
@@ -1671,4 +1680,4 @@ async function runWebUI(opts) {
1671
1680
  export {
1672
1681
  runWebUI
1673
1682
  };
1674
- //# sourceMappingURL=webui-server-A3H2SM6J.js.map
1683
+ //# sourceMappingURL=webui-server-CKIKAM2P.js.map
@@ -12,6 +12,13 @@ export declare function setupCliSlashCommands(params: {
12
12
  renderer: any;
13
13
  events: any;
14
14
  memoryStore: any;
15
+ /**
16
+ * Vector memory store (semantic recall channel). When supplied, the
17
+ * `/memory diagnostics` and `/memory race` slash commands can
18
+ * surface cross-system health and the lexical vs semantic channel
19
+ * comparison.
20
+ */
21
+ vectorMemoryStore?: any;
15
22
  context: any;
16
23
  cwd: string;
17
24
  projectRoot: string;
@@ -1,49 +1,15 @@
1
- /**
2
- * Refresh the `.wrongstack/domain-terms.md` mirror file from the
3
- * resolved SAGE `MemoryPort` at CLI boot.
4
- *
5
- * Background
6
- * ----------
7
- * The Project Jargon Dictionary feature splits ownership between two
8
- * storage backends (per `packages/sage/docs/direct-icp-usage.md`):
9
- * - SAGE's SQLite store is the authoritative source of truth.
10
- * - `.wrongstack/domain-terms.md` is a *derived* view of the same
11
- * data, kept on disk so humans (and ProjectRoot sandboxes) can
12
- * read the glossary without going through the IPC client.
13
- *
14
- * `SageDomainTermExtractor.writeDomainTermsFile` already knows how to
15
- * regenerate the file from the canonical SAGE state. What it lacks
16
- * is a runtime host caller — the helper exists only in tests. This
17
- * module closes that gap for the CLI host.
18
- *
19
- * Behaviour
20
- * ---------
21
- * - **Fire and forget at boot.** The mirror is a derived artefact;
22
- * a failure to refresh at startup must not abort the CLI. The
23
- * helper logs via `writeErr` and returns `null` on failure.
24
- * - **Idempotent.** Calling this on every boot is safe: the file is
25
- * regenerated atomically by `writeDomainTermsFile`, and the content
26
- * is fully derived from SAGE state.
27
- * - **Honest about scope.** Only the CLI host invokes this. TUI and
28
- * WebUI hosts share the same SAGE port and read the same file; the
29
- * CLI boot path is the canonical place to refresh the mirror.
30
- */
31
- import type { MemoryPort } from '@wrongstack/core/types';
32
1
  export interface RefreshDomainTermsMirrorOptions {
33
2
  /** Absolute project root whose `.wrongstack/domain-terms.md` should be refreshed. */
34
3
  projectRoot: string;
35
- /**
36
- * Resolved SAGE `MemoryPort`. The extractor reads the `domain-term`
37
- * tagged subset from this port.
38
- */
39
- memoryStore: MemoryPort;
40
4
  }
41
5
  /**
42
- * Regenerate the mirror file from the current SAGE state.
6
+ * Regenerate the mirror file with the empty placeholder so the path
7
+ * always exists. The next per-turn or session-end extraction pass
8
+ * overwrites the file with the freshly extracted terms.
43
9
  *
44
10
  * Returns the absolute path of the written file, or `null` when the
45
11
  * refresh failed (the CLI logs the underlying reason to stderr and
46
- * continues — the SAGE corpus remains authoritative).
12
+ * continues).
47
13
  *
48
14
  * Never throws — callers are guaranteed to receive a `string | null`
49
15
  * outcome.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/cli",
3
- "version": "0.308.0",
3
+ "version": "0.308.1",
4
4
  "license": "MIT",
5
5
  "description": "WrongStack CLI — terminal AI coding agent with provider catalog from models.dev. Provides `wrongstack` and `wstack` binaries.",
6
6
  "keywords": [
@@ -42,32 +42,32 @@
42
42
  ],
43
43
  "dependencies": {
44
44
  "ws": "^8.21.3",
45
- "@wrongstack/core": "0.308.0",
46
- "@wrongstack/mcp": "0.308.0",
47
- "@wrongstack/bench": "0.308.0",
48
- "@wrongstack/kanban": "0.308.0",
49
- "@wrongstack/acp": "0.308.0",
50
- "@wrongstack/plugins": "0.308.0",
51
- "@wrongstack/providers": "0.308.0",
52
- "@wrongstack/persistence": "0.308.0",
53
- "@wrongstack/sage": "0.308.0",
54
- "@wrongstack/runtime": "0.308.0",
55
- "@wrongstack/plug-lsp": "0.308.0",
56
- "@wrongstack/techstack": "0.308.0",
57
- "@wrongstack/sdd": "0.308.0",
58
- "@wrongstack/tui": "0.308.0",
59
- "@wrongstack/requirement-intake": "0.308.0",
60
- "@wrongstack/telegram": "0.308.0",
61
- "@wrongstack/simpleui": "0.308.0",
62
- "@wrongstack/vector-memory": "0.308.0",
63
- "@wrongstack/tools": "0.308.0",
64
- "@wrongstack/webui": "0.308.0",
65
- "@wrongstack/security-scanner": "0.308.0",
66
- "@wrongstack/webui-server": "0.308.0",
67
- "@wrongstack/webui-hq": "0.308.0"
45
+ "@wrongstack/bench": "0.308.1",
46
+ "@wrongstack/core": "0.308.1",
47
+ "@wrongstack/acp": "0.308.1",
48
+ "@wrongstack/kanban": "0.308.1",
49
+ "@wrongstack/mcp": "0.308.1",
50
+ "@wrongstack/plugins": "0.308.1",
51
+ "@wrongstack/plug-lsp": "0.308.1",
52
+ "@wrongstack/providers": "0.308.1",
53
+ "@wrongstack/requirement-intake": "0.308.1",
54
+ "@wrongstack/persistence": "0.308.1",
55
+ "@wrongstack/runtime": "0.308.1",
56
+ "@wrongstack/telegram": "0.308.1",
57
+ "@wrongstack/techstack": "0.308.1",
58
+ "@wrongstack/sage": "0.308.1",
59
+ "@wrongstack/security-scanner": "0.308.1",
60
+ "@wrongstack/tools": "0.308.1",
61
+ "@wrongstack/tui": "0.308.1",
62
+ "@wrongstack/simpleui": "0.308.1",
63
+ "@wrongstack/sdd": "0.308.1",
64
+ "@wrongstack/webui-hq": "0.308.1",
65
+ "@wrongstack/webui": "0.308.1",
66
+ "@wrongstack/vector-memory": "0.308.1",
67
+ "@wrongstack/webui-server": "0.308.1"
68
68
  },
69
69
  "optionalDependencies": {
70
- "@wrongstack/desktop": "0.308.0"
70
+ "@wrongstack/desktop": "0.308.1"
71
71
  },
72
72
  "devDependencies": {
73
73
  "@types/node": "^26.2.0",
@@ -81,6 +81,7 @@
81
81
  "scripts": {
82
82
  "build": "node ../../scripts/build.mjs --target @wrongstack/webui-hq --skip-if-workspace-build && node ../../scripts/build-package.mjs",
83
83
  "typecheck": "pnpm --filter @wrongstack/tools run build && pnpm --filter @wrongstack/tui run build && tsc --noEmit",
84
+ "test": "vitest run",
84
85
  "test:hqdash": "vitest run --config vitest.hqdash.config.ts",
85
86
  "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\""
86
87
  }