@vibgrate/cli 2026.817.2 → 2026.818.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.
Files changed (143) hide show
  1. package/dist/activity-MF2GNYAI.js +5 -0
  2. package/dist/activity-MF2GNYAI.js.map +1 -0
  3. package/dist/agent-CUK24AWF.js +30 -0
  4. package/dist/{agent-UQC7QN77.js.map → agent-CUK24AWF.js.map} +1 -1
  5. package/dist/{approvals-ASPJQUZK.js → approvals-YFOU6GVB.js} +5 -5
  6. package/dist/{approvals-ASPJQUZK.js.map → approvals-YFOU6GVB.js.map} +1 -1
  7. package/dist/attach-6S3XS7VO.js +9 -0
  8. package/dist/attach-6S3XS7VO.js.map +1 -0
  9. package/dist/baseline-OBZJRCCP.js +9 -0
  10. package/dist/{baseline-FIV6PR2M.js.map → baseline-OBZJRCCP.js.map} +1 -1
  11. package/dist/{chunk-E2FAQREZ.js → chunk-43ESHUM5.js} +174 -401
  12. package/dist/chunk-43ESHUM5.js.map +1 -0
  13. package/dist/{chunk-K6DXETMC.js → chunk-4QAMUBAC.js} +13 -12
  14. package/dist/chunk-4QAMUBAC.js.map +1 -0
  15. package/dist/{chunk-6K3XK7C5.js → chunk-6ABMV74F.js} +6 -3
  16. package/dist/chunk-6ABMV74F.js.map +1 -0
  17. package/dist/chunk-6BEBJCK4.js +467 -0
  18. package/dist/chunk-6BEBJCK4.js.map +1 -0
  19. package/dist/{chunk-KJT2R6WW.js → chunk-6LY3JH6S.js} +3 -3
  20. package/dist/{chunk-KJT2R6WW.js.map → chunk-6LY3JH6S.js.map} +1 -1
  21. package/dist/chunk-6WRG7VDO.js +118 -0
  22. package/dist/chunk-6WRG7VDO.js.map +1 -0
  23. package/dist/chunk-DBX3DL27.js +26 -0
  24. package/dist/chunk-DBX3DL27.js.map +1 -0
  25. package/dist/{chunk-Q3M6K3HY.js → chunk-DH6EDSQP.js} +3 -3
  26. package/dist/{chunk-Q3M6K3HY.js.map → chunk-DH6EDSQP.js.map} +1 -1
  27. package/dist/{chunk-5OL2TSHK.js → chunk-DM6MJ7IF.js} +4 -4
  28. package/dist/{chunk-5OL2TSHK.js.map → chunk-DM6MJ7IF.js.map} +1 -1
  29. package/dist/{chunk-Q4G2BWE3.js → chunk-DUCEKH7Q.js} +12 -6
  30. package/dist/chunk-DUCEKH7Q.js.map +1 -0
  31. package/dist/{chunk-EXV4QSAD.js → chunk-E7RVA2ZI.js} +3 -3
  32. package/dist/{chunk-EXV4QSAD.js.map → chunk-E7RVA2ZI.js.map} +1 -1
  33. package/dist/{chunk-C3LMWZKF.js → chunk-FFYO7G3T.js} +4 -4
  34. package/dist/{chunk-C3LMWZKF.js.map → chunk-FFYO7G3T.js.map} +1 -1
  35. package/dist/chunk-G7ZFSH5J.js +132 -0
  36. package/dist/chunk-G7ZFSH5J.js.map +1 -0
  37. package/dist/{chunk-M7IO3DXH.js → chunk-GKZU6MII.js} +165 -31
  38. package/dist/chunk-GKZU6MII.js.map +1 -0
  39. package/dist/chunk-H5Q4JUY4.js +47 -0
  40. package/dist/chunk-H5Q4JUY4.js.map +1 -0
  41. package/dist/{chunk-LHKQGVRF.js → chunk-H6RV2OAX.js} +31 -10
  42. package/dist/chunk-H6RV2OAX.js.map +1 -0
  43. package/dist/{chunk-2FEB5KJS.js → chunk-I3PKYYRP.js} +5 -4
  44. package/dist/chunk-I3PKYYRP.js.map +1 -0
  45. package/dist/{chunk-XLXJC7H3.js → chunk-J3A3O4IA.js} +5 -118
  46. package/dist/chunk-J3A3O4IA.js.map +1 -0
  47. package/dist/{chunk-O2DL2Q55.js → chunk-J7CVFTLG.js} +5 -5
  48. package/dist/{chunk-O2DL2Q55.js.map → chunk-J7CVFTLG.js.map} +1 -1
  49. package/dist/{chunk-3NJUOIZQ.js → chunk-LHQYYBCH.js} +5 -5
  50. package/dist/{chunk-3NJUOIZQ.js.map → chunk-LHQYYBCH.js.map} +1 -1
  51. package/dist/{chunk-GOGMS3ST.js → chunk-MHJBIGWV.js} +752 -51
  52. package/dist/chunk-MHJBIGWV.js.map +1 -0
  53. package/dist/chunk-NDNNOAS6.js +16 -0
  54. package/dist/chunk-NDNNOAS6.js.map +1 -0
  55. package/dist/{chunk-RBIRXQR6.js → chunk-NO4BMW7M.js} +4 -14
  56. package/dist/chunk-NO4BMW7M.js.map +1 -0
  57. package/dist/{chunk-DVW3SVCW.js → chunk-OS4YFOFJ.js} +29 -9
  58. package/dist/chunk-OS4YFOFJ.js.map +1 -0
  59. package/dist/{chunk-3H3SVJUY.js → chunk-OVUZWR67.js} +107 -542
  60. package/dist/chunk-OVUZWR67.js.map +1 -0
  61. package/dist/chunk-POOL355F.js +237 -0
  62. package/dist/chunk-POOL355F.js.map +1 -0
  63. package/dist/{chunk-WVMOGIPC.js → chunk-Q7TWHJJ6.js} +3 -3
  64. package/dist/{chunk-WVMOGIPC.js.map → chunk-Q7TWHJJ6.js.map} +1 -1
  65. package/dist/{chunk-3FMRFECC.js → chunk-SBHK4ECM.js} +3 -3
  66. package/dist/{chunk-3FMRFECC.js.map → chunk-SBHK4ECM.js.map} +1 -1
  67. package/dist/chunk-SUD54UGN.js +6 -0
  68. package/dist/{chunk-PRG2ULPK.js.map → chunk-SUD54UGN.js.map} +1 -1
  69. package/dist/{chunk-WAKP7SK7.js → chunk-V2UGRUTL.js} +225 -48
  70. package/dist/chunk-V2UGRUTL.js.map +1 -0
  71. package/dist/{chunk-AXZZ5CCG.js → chunk-WVTGF2MP.js} +38 -19
  72. package/dist/chunk-WVTGF2MP.js.map +1 -0
  73. package/dist/{chunk-TVDMNIUT.js → chunk-X4DECFUG.js} +4 -4
  74. package/dist/{chunk-TVDMNIUT.js.map → chunk-X4DECFUG.js.map} +1 -1
  75. package/dist/{chunk-OZ3IFXRD.js → chunk-YENM3HT7.js} +5 -4
  76. package/dist/chunk-YENM3HT7.js.map +1 -0
  77. package/dist/{chunk-KNGAZDGF.js → chunk-ZWFMJUXC.js} +151 -42
  78. package/dist/chunk-ZWFMJUXC.js.map +1 -0
  79. package/dist/cli.d.ts +6 -0
  80. package/dist/cli.js +576 -454
  81. package/dist/cli.js.map +1 -1
  82. package/dist/daemon-4Q5QBBAF.js +31 -0
  83. package/dist/{daemon-FPTRHO5N.js.map → daemon-4Q5QBBAF.js.map} +1 -1
  84. package/dist/embed-worker-main.js +3 -3
  85. package/dist/ensure-map-B2PS5ALS.js +40 -0
  86. package/dist/{ensure-map-342G2IV3.js.map → ensure-map-B2PS5ALS.js.map} +1 -1
  87. package/dist/graph-backend-EEXMD6XY.js +16 -0
  88. package/dist/{graph-backend-RNWQBT3P.js.map → graph-backend-EEXMD6XY.js.map} +1 -1
  89. package/dist/index.d.ts +606 -4
  90. package/dist/index.js +24 -17
  91. package/dist/index.js.map +1 -1
  92. package/dist/{interactive-SR7TMF6M.js → interactive-KEPGE5HD.js} +40 -33
  93. package/dist/interactive-KEPGE5HD.js.map +1 -0
  94. package/dist/load-H5HAME25.js +11 -0
  95. package/dist/{load-AYFECIQB.js.map → load-H5HAME25.js.map} +1 -1
  96. package/dist/parse-worker.js +3 -2
  97. package/dist/parse-worker.js.map +1 -1
  98. package/dist/{run-outcome-YQQJG3LW.js → run-outcome-V3L4GAN4.js} +5 -5
  99. package/dist/{run-outcome-YQQJG3LW.js.map → run-outcome-V3L4GAN4.js.map} +1 -1
  100. package/dist/runtime-session-M2FRGTDS.js +27 -0
  101. package/dist/{runtime-session-CNJ4DAV5.js.map → runtime-session-M2FRGTDS.js.map} +1 -1
  102. package/dist/{session-H34HUV2Y.js → session-77YXFUC7.js} +8 -8
  103. package/dist/{session-H34HUV2Y.js.map → session-77YXFUC7.js.map} +1 -1
  104. package/dist/slot-subscription-25GOARW5.js +95 -0
  105. package/dist/slot-subscription-25GOARW5.js.map +1 -0
  106. package/dist/{stream-json-55YDNAFX.js → stream-json-S6E3W2US.js} +18 -15
  107. package/dist/stream-json-S6E3W2US.js.map +1 -0
  108. package/dist/version-VRZT5G4T.js +4 -0
  109. package/dist/{version-WSE3QOPK.js.map → version-VRZT5G4T.js.map} +1 -1
  110. package/dist/{vg-mcp-bridge-2CNLHNEL.js → vg-mcp-bridge-HCPJWJKJ.js} +12 -11
  111. package/dist/vg-mcp-bridge-HCPJWJKJ.js.map +1 -0
  112. package/dist/{vgd-URUZQORN.js → vgd-R2PNIDEP.js} +15 -10
  113. package/dist/vgd-R2PNIDEP.js.map +1 -0
  114. package/package.json +1 -1
  115. package/dist/agent-UQC7QN77.js +0 -27
  116. package/dist/baseline-FIV6PR2M.js +0 -9
  117. package/dist/chunk-2FEB5KJS.js.map +0 -1
  118. package/dist/chunk-3H3SVJUY.js.map +0 -1
  119. package/dist/chunk-6K3XK7C5.js.map +0 -1
  120. package/dist/chunk-AXZZ5CCG.js.map +0 -1
  121. package/dist/chunk-DVW3SVCW.js.map +0 -1
  122. package/dist/chunk-E2FAQREZ.js.map +0 -1
  123. package/dist/chunk-GOGMS3ST.js.map +0 -1
  124. package/dist/chunk-K6DXETMC.js.map +0 -1
  125. package/dist/chunk-KNGAZDGF.js.map +0 -1
  126. package/dist/chunk-LHKQGVRF.js.map +0 -1
  127. package/dist/chunk-M7IO3DXH.js.map +0 -1
  128. package/dist/chunk-OZ3IFXRD.js.map +0 -1
  129. package/dist/chunk-PRG2ULPK.js +0 -6
  130. package/dist/chunk-Q4G2BWE3.js.map +0 -1
  131. package/dist/chunk-RBIRXQR6.js.map +0 -1
  132. package/dist/chunk-WAKP7SK7.js.map +0 -1
  133. package/dist/chunk-XLXJC7H3.js.map +0 -1
  134. package/dist/daemon-FPTRHO5N.js +0 -26
  135. package/dist/ensure-map-342G2IV3.js +0 -33
  136. package/dist/graph-backend-RNWQBT3P.js +0 -15
  137. package/dist/interactive-SR7TMF6M.js.map +0 -1
  138. package/dist/load-AYFECIQB.js +0 -11
  139. package/dist/runtime-session-CNJ4DAV5.js +0 -22
  140. package/dist/stream-json-55YDNAFX.js.map +0 -1
  141. package/dist/version-WSE3QOPK.js +0 -4
  142. package/dist/vg-mcp-bridge-2CNLHNEL.js.map +0 -1
  143. package/dist/vgd-URUZQORN.js.map +0 -1
@@ -1,13 +1,20 @@
1
1
  import { listHostSessions, hostSessionPoolSize, acquireHostSession, evictIdleSessions, clearHostSessionPool, dropHostSession, generateOnSession } from './chunk-3LOWJSAF.js';
2
- import { vgdSocketPath, vgdPidPath } from './chunk-RBIRXQR6.js';
2
+ import { isRelevantChange } from './chunk-DBX3DL27.js';
3
+ import { isSkippedDirName, SKIP_FILES } from './chunk-POOL355F.js';
4
+ import { inventory } from './chunk-6BEBJCK4.js';
3
5
  import { resolveOne } from './chunk-J7LKEB2R.js';
4
- import { impactOf, queryGraph } from './chunk-Q4G2BWE3.js';
5
- import { loadGraph } from './chunk-3FMRFECC.js';
6
- import { VERSION } from './chunk-PRG2ULPK.js';
6
+ import { impactOf, queryGraph, queryGraphSemantic } from './chunk-DUCEKH7Q.js';
7
+ import { loadGraph } from './chunk-SBHK4ECM.js';
8
+ import { vgdSocketPath, vgdPidPath } from './chunk-NDNNOAS6.js';
9
+ import { embedTargets, readCachedVectors, cosine } from './chunk-WVTGF2MP.js';
10
+ import { cacheDir } from './chunk-MVPRPSXU.js';
11
+ import { stableStringify } from './chunk-O3LABX6I.js';
12
+ import { VERSION } from './chunk-SUD54UGN.js';
7
13
  import { repositoryIdFromRoot, globalGraphPathForRef, globalGraphPath } from './chunk-BWPBB45M.js';
8
- import { activeGraphSlotKey, detectGitRef } from './chunk-BPF3YX2U.js';
9
- import * as path from 'path';
10
- import * as fs2 from 'fs';
14
+ import { hashString, canonicalize, hashBytes } from './chunk-JFGYT6BI.js';
15
+ import { activeGraphSlotKey, detectGitRef, clearDetectGitRefCache } from './chunk-BPF3YX2U.js';
16
+ import * as path3 from 'path';
17
+ import * as fs4 from 'fs';
11
18
  import * as net from 'net';
12
19
  import { spawn } from 'child_process';
13
20
  import { fileURLToPath } from 'url';
@@ -81,6 +88,37 @@ function parseRequest(line) {
81
88
  gitRef: typeof gitRef === "string" && gitRef.trim() ? gitRef.trim() : void 0
82
89
  };
83
90
  }
91
+ if (op === "embed-rank") {
92
+ const repositoryId = raw.repositoryId;
93
+ const gitRef = raw.gitRef;
94
+ const text = raw.text;
95
+ const limit = raw.limit;
96
+ if (typeof repositoryId !== "string" || !repositoryId.trim()) return { error: "embed-rank requires repositoryId" };
97
+ if (typeof text !== "string" || !text.trim()) return { error: "embed-rank requires text" };
98
+ return {
99
+ op: "embed-rank",
100
+ repositoryId: repositoryId.trim(),
101
+ gitRef: typeof gitRef === "string" && gitRef.trim() ? gitRef.trim() : void 0,
102
+ // Same bound as embed-query: untrusted socket input, one worker.
103
+ text: text.trim().slice(0, 8e3),
104
+ // Only the ORDER of this list reaches the caller's rank fusion, and a
105
+ // candidate past a few hundred can never surface in a 12-row answer —
106
+ // so a generous cap costs nothing and a huge one buys nothing.
107
+ limit: typeof limit === "number" && limit > 0 ? Math.min(Math.floor(limit), 1e3) : void 0
108
+ };
109
+ }
110
+ if (op === "watch-slots") {
111
+ const repositoryId = raw.repositoryId;
112
+ return {
113
+ op: "watch-slots",
114
+ repositoryId: typeof repositoryId === "string" && repositoryId.trim() ? repositoryId.trim() : void 0
115
+ };
116
+ }
117
+ if (op === "dep-context") {
118
+ const repositoryId = raw.repositoryId;
119
+ if (typeof repositoryId !== "string" || !repositoryId.trim()) return { error: "dep-context requires repositoryId" };
120
+ return { op: "dep-context", repositoryId: repositoryId.trim() };
121
+ }
84
122
  if (op === "query-graph") {
85
123
  const repositoryId = raw.repositoryId;
86
124
  const query = raw.query;
@@ -93,7 +131,13 @@ function parseRequest(line) {
93
131
  repositoryId: repositoryId.trim(),
94
132
  query: query.trim(),
95
133
  limit: typeof limit === "number" && limit > 0 ? Math.min(Math.floor(limit), 50) : void 0,
96
- gitRef: typeof gitRef === "string" && gitRef.trim() ? gitRef.trim() : void 0
134
+ gitRef: typeof gitRef === "string" && gitRef.trim() ? gitRef.trim() : void 0,
135
+ // Opt-in, and left off the parsed request entirely when not asked: a
136
+ // client that asks for semantic gets the fused ranking when the slot's
137
+ // index is ready, and the lexical answer when it is not. Ranking and
138
+ // fusion both happen here, so the caller needs neither the graph nor the
139
+ // embedder — which is the whole reason a panel can afford to ask.
140
+ semantic: raw.semantic === true ? true : void 0
97
141
  };
98
142
  }
99
143
  if (op === "impact-of") {
@@ -386,6 +430,8 @@ var WorkspaceRegistry = class {
386
430
  * index can never describe a ref its graph no longer holds.
387
431
  */
388
432
  slotListener;
433
+ /** Told after a graph is stored, so the daemon can push to subscribers. */
434
+ publishListener;
389
435
  /** Multi-branch in-memory graphs (shared across registered workspaces). */
390
436
  graphs = new ActiveGraphCache({
391
437
  onEvict: (slot) => this.slotListener?.onGraphEvict(slot.repositoryId, slot.gitRef)
@@ -394,8 +440,17 @@ var WorkspaceRegistry = class {
394
440
  setSlotListener(listener) {
395
441
  this.slotListener = listener;
396
442
  }
443
+ /**
444
+ * Attach the push channel. Separate from {@link GraphSlotListener} on
445
+ * purpose: that one keeps *slot-scoped resources* in step and must run
446
+ * before anyone can query, while this one notifies *other processes* and
447
+ * must run after the store is complete.
448
+ */
449
+ setPublishListener(listener) {
450
+ this.publishListener = listener;
451
+ }
397
452
  register(root, now = () => /* @__PURE__ */ new Date(), meta = {}) {
398
- const resolved = path.resolve(root);
453
+ const resolved = path3.resolve(root);
399
454
  const id = repositoryIdFromRoot(resolved);
400
455
  const git = meta.gitRef ? { ref: meta.gitRef, kind: "branch" } : detectGitRef(resolved);
401
456
  const graphPath = git.kind !== "none" && git.ref ? globalGraphPathForRef(resolved, git.ref) : globalGraphPath(resolved);
@@ -409,7 +464,7 @@ var WorkspaceRegistry = class {
409
464
  gitRef: git.ref || void 0
410
465
  };
411
466
  this.byId.set(id, record);
412
- if (git.ref) this.graphs.select(id, git.ref);
467
+ if (git.ref) this.selectGitRef(id, git.ref);
413
468
  return record;
414
469
  }
415
470
  /**
@@ -421,6 +476,7 @@ var WorkspaceRegistry = class {
421
476
  this.graphs.select(repositoryId, gitRef);
422
477
  this.slotListener?.onGraphPut(repositoryId, gitRef, graph);
423
478
  this.slotListener?.onGraphSelect(repositoryId, gitRef);
479
+ this.publishListener?.(repositoryId, gitRef, graph);
424
480
  }
425
481
  /** Switch the current branch slot for a repository (no load). */
426
482
  selectGitRef(repositoryId, gitRef) {
@@ -432,14 +488,14 @@ var WorkspaceRegistry = class {
432
488
  return members.map((m) => this.register(m.root, now, { label: m.label, role: m.role }));
433
489
  }
434
490
  unregister(root) {
435
- const id = repositoryIdFromRoot(path.resolve(root));
491
+ const id = repositoryIdFromRoot(path3.resolve(root));
436
492
  return this.byId.delete(id);
437
493
  }
438
494
  list() {
439
495
  return [...this.byId.values()].sort((a, b) => a.root.localeCompare(b.root));
440
496
  }
441
497
  get(root) {
442
- return this.byId.get(repositoryIdFromRoot(path.resolve(root)));
498
+ return this.byId.get(repositoryIdFromRoot(path3.resolve(root)));
443
499
  }
444
500
  /** Lookup by stable repository id. */
445
501
  getById(repositoryId) {
@@ -482,8 +538,8 @@ var VgdHostBroker = class {
482
538
  }
483
539
  status() {
484
540
  const leases = {};
485
- for (const [path4, clients] of this.leases) {
486
- leases[path4] = clients.size;
541
+ for (const [path6, clients] of this.leases) {
542
+ leases[path6] = clients.size;
487
543
  }
488
544
  return {
489
545
  poolSize: hostSessionPoolSize(),
@@ -582,10 +638,6 @@ function getVgdHostBroker() {
582
638
  if (!defaultBroker) defaultBroker = new VgdHostBroker();
583
639
  return defaultBroker;
584
640
  }
585
- function nodeText(node) {
586
- const parts = [node.qualifiedName || node.name, node.kind, node.file, node.doc].filter((p) => typeof p === "string" && p.length > 0);
587
- return parts.join(" \xB7 ").slice(0, 512);
588
- }
589
641
  var EmbedBroker = class {
590
642
  child = null;
591
643
  starting = false;
@@ -602,6 +654,7 @@ var EmbedBroker = class {
602
654
  /** The slot most recently made current — logged on change. */
603
655
  currentKey;
604
656
  graphProvider;
657
+ rootProvider;
605
658
  log;
606
659
  spawnWorker;
607
660
  maxCrashes;
@@ -637,6 +690,7 @@ var EmbedBroker = class {
637
690
  state: idx.state,
638
691
  vectors: idx.vectors.size,
639
692
  nodeCount: idx.nodeCount,
693
+ pending: idx.pending,
640
694
  builtAt: idx.builtAt,
641
695
  buildMs: idx.buildMs,
642
696
  error: idx.error
@@ -679,6 +733,15 @@ var EmbedBroker = class {
679
733
  setGraphProvider(provider) {
680
734
  this.graphProvider = provider;
681
735
  }
736
+ /**
737
+ * Supplies the absolute repository root for a slot, so the broker can read
738
+ * that repo's on-disk vector cache. It cannot come from the graph:
739
+ * `meta.root` is deliberately relative (`.`) so snapshots stay portable
740
+ * across machines. The registry holds the real path.
741
+ */
742
+ setRootProvider(provider) {
743
+ this.rootProvider = provider;
744
+ }
682
745
  /**
683
746
  * A slot became current — a branch switch, a new repo, a fresh publish.
684
747
  * The matching index is swapped in, and warmed in the background if it is
@@ -733,17 +796,35 @@ var EmbedBroker = class {
733
796
  if (this.building.has(key)) return idx;
734
797
  this.building.add(key);
735
798
  idx.state = "building";
736
- const nodes = (graph.nodes ?? []).filter((n) => !!n?.id);
737
799
  const started = this.now();
738
- this.log(`semantic: building index for ${repositoryId}@${gitRef} (${nodes.length} nodes)\u2026`);
800
+ const seeded = this.seedFromDisk(repositoryId, graph);
801
+ const targets = seeded?.pending ?? embedTargets(graph);
802
+ const vectors = seeded ? seeded.vectors : /* @__PURE__ */ new Map();
803
+ const nodeCount = seeded ? seeded.vectors.size + seeded.pending.length : targets.length;
804
+ idx.nodeCount = nodeCount;
805
+ if (seeded && seeded.vectors.size) {
806
+ this.log(
807
+ `semantic: seeded ${repositoryId}@${gitRef} from the on-disk index \u2014 ${seeded.vectors.size} vectors, ${seeded.pending.length} pending`
808
+ );
809
+ }
810
+ if (seeded?.writerBusy && targets.length) {
811
+ idx.vectors = vectors;
812
+ idx.pending = targets.length;
813
+ idx.state = vectors.size ? "stale" : "absent";
814
+ this.building.delete(key);
815
+ this.log(
816
+ `semantic: ${repositoryId}@${gitRef} left at ${targets.length} pending \u2014 another process is writing the on-disk index; re-warming when it reports in`
817
+ );
818
+ return idx;
819
+ }
820
+ this.log(`semantic: building index for ${repositoryId}@${gitRef} (${targets.length} of ${nodeCount} nodes)\u2026`);
739
821
  try {
740
- const vectors = /* @__PURE__ */ new Map();
741
- for (let i = 0; i < nodes.length; i += this.batchSize) {
742
- const batch = nodes.slice(i, i + this.batchSize);
743
- const out = await this.embed(batch.map((n) => nodeText(n)));
744
- batch.forEach((n, j) => {
822
+ for (let i = 0; i < targets.length; i += this.batchSize) {
823
+ const batch = targets.slice(i, i + this.batchSize);
824
+ const out = await this.embed(batch.map((t) => t.text));
825
+ batch.forEach((t, j) => {
745
826
  const v = out[j];
746
- if (v) vectors.set(n.id, v);
827
+ if (v) vectors.set(t.id, v);
747
828
  });
748
829
  const live = this.indexes.get(key);
749
830
  if (!live || live !== idx) {
@@ -752,13 +833,15 @@ var EmbedBroker = class {
752
833
  }
753
834
  }
754
835
  idx.vectors = vectors;
755
- idx.nodeCount = nodes.length;
836
+ idx.pending = void 0;
756
837
  idx.state = "ready";
757
838
  idx.builtAt = this.now();
758
839
  idx.buildMs = idx.builtAt - started;
759
840
  idx.error = void 0;
760
841
  this.log(`semantic: index ready for ${repositoryId}@${gitRef} \u2014 ${vectors.size} vectors in ${idx.buildMs}ms`);
761
842
  } catch (err) {
843
+ idx.vectors = vectors;
844
+ idx.pending = targets.length;
762
845
  idx.state = "failed";
763
846
  idx.error = err instanceof Error ? err.message : String(err);
764
847
  this.log(`semantic: index build failed for ${repositoryId}@${gitRef} \u2014 ${idx.error}; callers fall back to lexical`);
@@ -767,6 +850,51 @@ var EmbedBroker = class {
767
850
  }
768
851
  return idx;
769
852
  }
853
+ /**
854
+ * Read this slot's vectors out of the repo's on-disk cache. Needs the repo
855
+ * root from the registry; a repository the daemon has no root for (a
856
+ * synthetic publish in a test) simply builds from scratch.
857
+ */
858
+ seedFromDisk(repositoryId, graph) {
859
+ const root = this.rootProvider?.(repositoryId);
860
+ if (!root) return null;
861
+ try {
862
+ const read = readCachedVectors(graph, root);
863
+ if (!this.model) this.model = read.model;
864
+ return { vectors: read.vectors, pending: read.pending, writerBusy: read.writerBusy };
865
+ } catch (err) {
866
+ this.log(`semantic: could not read the on-disk index \u2014 ${err instanceof Error ? err.message : String(err)}; embedding from scratch`);
867
+ return null;
868
+ }
869
+ }
870
+ /**
871
+ * Rank a slot's nodes against one question, inside the daemon.
872
+ *
873
+ * This is the op that makes the resident index worth holding. The
874
+ * alternative — shipping the vectors to the caller so it can rank locally —
875
+ * moves tens of megabytes over a socket per ask; ranking here moves one
876
+ * question in and a few hundred (id, score) pairs out.
877
+ *
878
+ * Only the *order* of this list reaches the caller's rank fusion, so
879
+ * truncating at `limit` is not an approximation of the answer: a candidate
880
+ * ranked past a few hundred cannot reach a 12-row result under RRF.
881
+ *
882
+ * Returns null whenever semantic is unavailable — a caller that gets null
883
+ * runs its own in-process pass, exactly as it does with no daemon at all.
884
+ */
885
+ async rank(repositoryId, gitRef, text, limit = 200) {
886
+ const idx = this.slot(repositoryId, gitRef);
887
+ if (!idx || idx.vectors.size === 0) return null;
888
+ const queryVec = await this.embedQuery(text);
889
+ if (!queryVec) return null;
890
+ const scored = [];
891
+ for (const [id, vec] of idx.vectors) {
892
+ const score = cosine(queryVec, vec);
893
+ if (score > 0) scored.push({ id, score });
894
+ }
895
+ scored.sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
896
+ return { ranked: scored.slice(0, limit), state: idx.state, vectors: idx.vectors.size };
897
+ }
770
898
  /** Embed one query string. Returns null when semantic is not available. */
771
899
  async embedQuery(text) {
772
900
  if (this.gaveUp) return null;
@@ -880,13 +1008,13 @@ var EmbedBroker = class {
880
1008
  return Promise.reject(error);
881
1009
  }
882
1010
  const id = this.nextId++;
883
- return new Promise((resolve2, reject) => {
1011
+ return new Promise((resolve3, reject) => {
884
1012
  const timer = setTimeout(() => {
885
1013
  this.pending.delete(id);
886
1014
  reject(new Error(`embedding request timed out after ${this.requestTimeoutMs}ms`));
887
1015
  }, this.requestTimeoutMs);
888
1016
  timer.unref?.();
889
- this.pending.set(id, { resolve: resolve2, reject, timer });
1017
+ this.pending.set(id, { resolve: resolve3, reject, timer });
890
1018
  try {
891
1019
  child.stdin?.write(JSON.stringify({ id, op: "embed", texts, query }) + "\n");
892
1020
  } catch (err) {
@@ -902,9 +1030,9 @@ var EmbedBroker = class {
902
1030
  }
903
1031
  };
904
1032
  function defaultSpawnWorker() {
905
- const here = path.dirname(fileURLToPath(import.meta.url));
906
- const entry = path.join(here, "embed-worker-main.js");
907
- if (!fs2.existsSync(entry)) {
1033
+ const here = path3.dirname(fileURLToPath(import.meta.url));
1034
+ const entry = path3.join(here, "embed-worker-main.js");
1035
+ if (!fs4.existsSync(entry)) {
908
1036
  throw new Error(`embedding worker not found at ${entry} \u2014 semantic runs lexically`);
909
1037
  }
910
1038
  return spawn(process.execPath, [entry], {
@@ -915,6 +1043,446 @@ function defaultSpawnWorker() {
915
1043
  });
916
1044
  }
917
1045
 
1046
+ // src/lsp/manifest-positions.ts
1047
+ function manifestKind(filePath) {
1048
+ const base = (filePath.split(/[\\/]/).pop() ?? "").toLowerCase();
1049
+ if (base === "package.json") return "package.json";
1050
+ if (base === "go.mod") return "go.mod";
1051
+ if (base === "requirements.txt" || /^requirements.*\.txt$/.test(base)) return "requirements.txt";
1052
+ if (base === "pyproject.toml") return "pyproject.toml";
1053
+ if (base === "cargo.toml") return "Cargo.toml";
1054
+ if (base === "pom.xml") return "pom.xml";
1055
+ if (base === "build.gradle" || base === "build.gradle.kts") return "build.gradle";
1056
+ if (base === "gemfile") return "Gemfile";
1057
+ if (base === "composer.json") return "composer.json";
1058
+ if (base === "pubspec.yaml") return "pubspec.yaml";
1059
+ if (base === "mix.exs") return "mix.exs";
1060
+ if (base.endsWith(".csproj") || base.endsWith(".fsproj") || base.endsWith(".vbproj")) return "csproj";
1061
+ if (base === "dockerfile" || base.startsWith("dockerfile.")) return "Dockerfile";
1062
+ return "unknown";
1063
+ }
1064
+ function isManifest(filePath) {
1065
+ return manifestKind(filePath) !== "unknown";
1066
+ }
1067
+ function esc(s) {
1068
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1069
+ }
1070
+ function findPackageLine(text, pkg, kind) {
1071
+ const lines = text.split(/\r?\n/);
1072
+ const p = esc(pkg);
1073
+ const patterns = (() => {
1074
+ switch (kind) {
1075
+ case "package.json":
1076
+ case "composer.json":
1077
+ return [new RegExp(`^\\s*"${p}"\\s*:`)];
1078
+ case "go.mod":
1079
+ return [new RegExp(`^\\s*(?:require\\s+)?${p}\\s+v?\\d`)];
1080
+ case "requirements.txt":
1081
+ return [new RegExp(`^\\s*${p}\\s*(?:\\[[^\\]]*\\])?\\s*(?:[=<>!~]|$)`, "i")];
1082
+ case "pyproject.toml":
1083
+ case "Cargo.toml":
1084
+ return [
1085
+ new RegExp(`^\\s*${p}\\s*=`),
1086
+ new RegExp(`^\\s*"${p}\\s*(?:[=<>!~\\[]|")`)
1087
+ ];
1088
+ case "pom.xml":
1089
+ return [new RegExp(`<artifactId>\\s*${p}\\s*</artifactId>`)];
1090
+ case "build.gradle":
1091
+ return [new RegExp(`["']${p}(?:["':])`)];
1092
+ case "Gemfile":
1093
+ return [new RegExp(`^\\s*gem\\s+["']${p}["']`)];
1094
+ case "pubspec.yaml":
1095
+ return [new RegExp(`^\\s*${p}\\s*:`)];
1096
+ case "mix.exs":
1097
+ return [new RegExp(`\\{\\s*:${p}\\s*,`)];
1098
+ case "csproj":
1099
+ return [new RegExp(`Include\\s*=\\s*"${p}"`, "i")];
1100
+ case "Dockerfile":
1101
+ return [new RegExp(`^\\s*FROM\\s+.*${p}`, "i")];
1102
+ default:
1103
+ return [];
1104
+ }
1105
+ })();
1106
+ for (const re of patterns) {
1107
+ for (let i = 0; i < lines.length; i++) {
1108
+ if (re.test(lines[i])) return i;
1109
+ }
1110
+ }
1111
+ return -1;
1112
+ }
1113
+ function findRuntimeLine(text, kind) {
1114
+ const lines = text.split(/\r?\n/);
1115
+ const patterns = (() => {
1116
+ switch (kind) {
1117
+ case "package.json":
1118
+ return [/^\s*"node"\s*:/, /^\s*"engines"\s*:/];
1119
+ case "go.mod":
1120
+ return [/^\s*go\s+\d/];
1121
+ case "pyproject.toml":
1122
+ return [/^\s*(?:requires-python|python)\s*=/];
1123
+ case "pom.xml":
1124
+ return [/<(?:java\.version|maven\.compiler\.(?:source|target|release))>/];
1125
+ case "build.gradle":
1126
+ return [/(?:sourceCompatibility|targetCompatibility|JavaVersion)/];
1127
+ case "csproj":
1128
+ return [/<TargetFrameworks?>/i];
1129
+ case "Dockerfile":
1130
+ return [/^\s*FROM\s+/i];
1131
+ default:
1132
+ return [];
1133
+ }
1134
+ })();
1135
+ for (const re of patterns) {
1136
+ for (let i = 0; i < lines.length; i++) {
1137
+ if (re.test(lines[i])) return i;
1138
+ }
1139
+ }
1140
+ return -1;
1141
+ }
1142
+ function endOfLine(text, line) {
1143
+ const lines = text.split(/\r?\n/);
1144
+ return (lines[line] ?? "").length;
1145
+ }
1146
+
1147
+ // src/lsp/scan-cache.ts
1148
+ var CACHE_VERSION = "vg-lsp-scan-cache/2";
1149
+ function cachePath(root) {
1150
+ return path3.join(cacheDir(root), "lsp-scan.json");
1151
+ }
1152
+ function manifestHash(root) {
1153
+ const files = [];
1154
+ const walk = (dir) => {
1155
+ let entries;
1156
+ try {
1157
+ entries = fs4.readdirSync(dir, { withFileTypes: true });
1158
+ } catch {
1159
+ return;
1160
+ }
1161
+ for (const entry of entries) {
1162
+ const abs = path3.join(dir, entry.name);
1163
+ if (entry.isDirectory()) {
1164
+ if (isSkippedDirName(entry.name)) continue;
1165
+ walk(abs);
1166
+ continue;
1167
+ }
1168
+ if (!entry.isFile()) continue;
1169
+ const base = entry.name.toLowerCase();
1170
+ if (!isManifest(abs) && !SKIP_FILES.has(base)) continue;
1171
+ try {
1172
+ const rel = path3.relative(root, abs).split(path3.sep).join("/");
1173
+ files.push({ rel, hash: hashBytes(fs4.readFileSync(abs)) });
1174
+ } catch {
1175
+ }
1176
+ }
1177
+ };
1178
+ walk(root);
1179
+ files.sort((a, b) => a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0);
1180
+ return hashString(canonicalize(files));
1181
+ }
1182
+ function isDependencyFile(root, filePath) {
1183
+ const rel = path3.relative(path3.resolve(root), path3.resolve(filePath));
1184
+ if (rel === "" || rel.startsWith("..") || path3.isAbsolute(rel)) return false;
1185
+ const parts = rel.split(path3.sep);
1186
+ if (parts.slice(0, -1).some((dir) => isSkippedDirName(dir))) return false;
1187
+ const base = parts[parts.length - 1].toLowerCase();
1188
+ return isManifest(filePath) || SKIP_FILES.has(base);
1189
+ }
1190
+ function loadScanCache(root, key) {
1191
+ try {
1192
+ const raw = JSON.parse(fs4.readFileSync(cachePath(root), "utf8"));
1193
+ if (raw.version === CACHE_VERSION && raw.manifestHash === key.manifestHash && raw.toolVersion === key.toolVersion && raw.offline === key.offline && raw.artifact) {
1194
+ return { manifestHash: raw.manifestHash, artifact: raw.artifact };
1195
+ }
1196
+ } catch {
1197
+ }
1198
+ return null;
1199
+ }
1200
+ function writeScanCache(root, key, artifact) {
1201
+ try {
1202
+ fs4.mkdirSync(cacheDir(root), { recursive: true });
1203
+ fs4.writeFileSync(
1204
+ cachePath(root),
1205
+ stableStringify(
1206
+ { version: CACHE_VERSION, manifestHash: key.manifestHash, toolVersion: key.toolVersion, offline: key.offline, artifact },
1207
+ 0
1208
+ )
1209
+ );
1210
+ } catch {
1211
+ }
1212
+ }
1213
+
1214
+ // src/runtime/vgd/freshness.ts
1215
+ var DEFAULT_SETTLE_MS = 600;
1216
+ var DEFAULT_MIN_REBUILD_GAP_MS = 5e3;
1217
+ var DEFAULT_REBUILD_TIMEOUT_MS = 10 * 6e4;
1218
+ var FreshnessSupervisor = class {
1219
+ watched = /* @__PURE__ */ new Map();
1220
+ /** Told when a manifest or lockfile is written — the shared dependency cache. */
1221
+ manifestListener;
1222
+ log;
1223
+ reload;
1224
+ select;
1225
+ settleMs;
1226
+ minRebuildGapMs;
1227
+ rebuildTimeoutMs;
1228
+ watchImpl;
1229
+ rebuildImpl;
1230
+ detectRef;
1231
+ now;
1232
+ constructor(options) {
1233
+ this.log = options.log ?? (() => {
1234
+ });
1235
+ this.reload = options.reload;
1236
+ this.select = options.select;
1237
+ this.settleMs = options.settleMs ?? DEFAULT_SETTLE_MS;
1238
+ this.minRebuildGapMs = options.minRebuildGapMs ?? DEFAULT_MIN_REBUILD_GAP_MS;
1239
+ this.rebuildTimeoutMs = options.rebuildTimeoutMs ?? DEFAULT_REBUILD_TIMEOUT_MS;
1240
+ this.watchImpl = options.watch ?? defaultWatch;
1241
+ this.rebuildImpl = options.rebuild ?? ((root) => spawnRebuild(root, this.rebuildTimeoutMs));
1242
+ this.detectRef = options.detectRef ?? defaultDetectRef;
1243
+ this.now = options.now ?? (() => Date.now());
1244
+ }
1245
+ /**
1246
+ * Attach something that must forget derived state when a manifest or
1247
+ * lockfile changes. The watcher is already looking at exactly these files,
1248
+ * so a cache hung off this has no staleness window — unlike a TTL.
1249
+ */
1250
+ setManifestListener(listener) {
1251
+ this.manifestListener = listener;
1252
+ }
1253
+ list() {
1254
+ return [...this.watched.values()].map((w) => ({
1255
+ repositoryId: w.repositoryId,
1256
+ root: w.root,
1257
+ gitRef: w.gitRef,
1258
+ watching: !!w.source,
1259
+ pending: w.pending.size,
1260
+ building: w.building,
1261
+ lastRebuildAt: w.lastRebuildAt || void 0
1262
+ })).sort((a, b) => a.repositoryId.localeCompare(b.repositoryId));
1263
+ }
1264
+ /**
1265
+ * Start watching a repository. Idempotent: re-registering the same root only
1266
+ * updates the ref it believes is current.
1267
+ */
1268
+ start(repositoryId, root, gitRef) {
1269
+ const existing = this.watched.get(repositoryId);
1270
+ if (existing) {
1271
+ existing.gitRef = gitRef;
1272
+ return;
1273
+ }
1274
+ const entry = {
1275
+ repositoryId,
1276
+ root,
1277
+ gitRef,
1278
+ source: null,
1279
+ head: null,
1280
+ timer: null,
1281
+ pending: /* @__PURE__ */ new Set(),
1282
+ building: false,
1283
+ again: false,
1284
+ lastRebuildAt: 0
1285
+ };
1286
+ this.watched.set(repositoryId, entry);
1287
+ entry.source = this.watchImpl(root, (filename) => {
1288
+ if (!isRelevantChange(filename)) return;
1289
+ entry.pending.add(filename);
1290
+ const manifest = isDependencyFile(root, path3.join(root, filename));
1291
+ if (manifest) this.manifestListener?.(entry.repositoryId);
1292
+ this.schedule(entry, manifest ? "manifest" : "source");
1293
+ });
1294
+ const gitDir = path3.join(root, ".git");
1295
+ if (fs4.existsSync(gitDir)) {
1296
+ entry.head = this.watchImpl(gitDir, (filename) => {
1297
+ if (path3.basename(filename) !== "HEAD") return;
1298
+ this.onHeadChanged(entry);
1299
+ });
1300
+ }
1301
+ this.log(
1302
+ `freshness: watching ${repositoryId} @${gitRef} (${entry.source ? "source" : "no source watcher"}${entry.head ? " + HEAD" : ""})`
1303
+ );
1304
+ }
1305
+ /** Stop watching one repository — its slot is gone or was unregistered. */
1306
+ stop(repositoryId) {
1307
+ const entry = this.watched.get(repositoryId);
1308
+ if (!entry) return;
1309
+ this.watched.delete(repositoryId);
1310
+ if (entry.timer) clearTimeout(entry.timer);
1311
+ entry.source?.close();
1312
+ entry.head?.close();
1313
+ this.log(`freshness: stopped watching ${repositoryId}`);
1314
+ }
1315
+ /** Daemon shutdown. */
1316
+ stopAll() {
1317
+ for (const id of [...this.watched.keys()]) this.stop(id);
1318
+ }
1319
+ /**
1320
+ * A branch switch: re-select first, and rebuild only when that ref has no
1321
+ * usable snapshot. Checking out a branch you were on ten minutes ago should
1322
+ * be instant, not a full rebuild.
1323
+ */
1324
+ onHeadChanged(entry) {
1325
+ const ref = this.detectRef(entry.root);
1326
+ if (!ref || ref === entry.gitRef) return;
1327
+ const from = entry.gitRef;
1328
+ entry.gitRef = ref;
1329
+ entry.pending.clear();
1330
+ this.log(`freshness: ${entry.repositoryId} branch ${from} \u2192 ${ref}`);
1331
+ this.select(entry.repositoryId, ref);
1332
+ void this.reload(entry.repositoryId, entry.root, ref).then((nodes) => {
1333
+ if (nodes != null) {
1334
+ this.log(`freshness: ${entry.repositoryId}@${ref} slot warm from disk \u2014 ${nodes} nodes`);
1335
+ } else {
1336
+ this.log(`freshness: ${entry.repositoryId}@${ref} has no snapshot yet \u2014 rebuilding`);
1337
+ this.schedule(entry, "branch");
1338
+ }
1339
+ });
1340
+ }
1341
+ schedule(entry, reason) {
1342
+ if (entry.building) {
1343
+ entry.again = true;
1344
+ return;
1345
+ }
1346
+ if (entry.timer) clearTimeout(entry.timer);
1347
+ const sinceLast = this.now() - entry.lastRebuildAt;
1348
+ const wait = Math.max(this.settleMs, entry.lastRebuildAt ? this.minRebuildGapMs - sinceLast : 0);
1349
+ entry.timer = setTimeout(() => {
1350
+ entry.timer = null;
1351
+ void this.rebuild(entry, reason);
1352
+ }, wait);
1353
+ entry.timer.unref?.();
1354
+ }
1355
+ async rebuild(entry, reason) {
1356
+ if (entry.building) {
1357
+ entry.again = true;
1358
+ return;
1359
+ }
1360
+ entry.building = true;
1361
+ const changed = entry.pending.size;
1362
+ const names = [...entry.pending].sort().slice(0, 3).join(", ");
1363
+ entry.pending.clear();
1364
+ const started = this.now();
1365
+ this.log(
1366
+ `freshness: rebuilding ${entry.repositoryId}@${entry.gitRef} (${reason}${changed ? `, ${changed} file(s): ${names}` : ""})\u2026`
1367
+ );
1368
+ try {
1369
+ const result = await this.rebuildImpl(entry.root);
1370
+ entry.lastRebuildAt = this.now();
1371
+ if (!result.ok) {
1372
+ this.log(`freshness: rebuild failed for ${entry.repositoryId} \u2014 ${result.error ?? "unknown"}; slot keeps its previous map`);
1373
+ return;
1374
+ }
1375
+ const nodes = await this.reload(entry.repositoryId, entry.root, entry.gitRef);
1376
+ this.log(
1377
+ nodes != null ? `freshness: ${entry.repositoryId}@${entry.gitRef} reloaded \u2014 ${nodes} nodes in ${this.now() - started}ms` : `freshness: ${entry.repositoryId}@${entry.gitRef} rebuilt but no snapshot to reload`
1378
+ );
1379
+ } catch (err) {
1380
+ this.log(`freshness: rebuild errored for ${entry.repositoryId} \u2014 ${err instanceof Error ? err.message : String(err)}`);
1381
+ } finally {
1382
+ entry.building = false;
1383
+ if (entry.again) {
1384
+ entry.again = false;
1385
+ this.schedule(entry, "source");
1386
+ }
1387
+ }
1388
+ }
1389
+ };
1390
+ function defaultDetectRef(root) {
1391
+ clearDetectGitRefCache(root);
1392
+ return detectGitRef(root).ref;
1393
+ }
1394
+ function defaultWatch(dir, onChange) {
1395
+ try {
1396
+ const w = fs4.watch(dir, { recursive: true }, (_event, filename) => {
1397
+ if (filename) onChange(String(filename));
1398
+ });
1399
+ w.unref?.();
1400
+ return w;
1401
+ } catch {
1402
+ return null;
1403
+ }
1404
+ }
1405
+ function spawnRebuild(root, timeoutMs) {
1406
+ return new Promise((resolve3) => {
1407
+ let child;
1408
+ try {
1409
+ child = spawn(process.execPath, [process.argv[1] ?? "", "build", "-C", root, "--json", "--no-daemon", "--no-warm"], {
1410
+ stdio: "ignore",
1411
+ env: { ...process.env, NODE_OPTIONS: "" }
1412
+ });
1413
+ } catch (e) {
1414
+ resolve3({ ok: false, error: e instanceof Error ? e.message : String(e) });
1415
+ return;
1416
+ }
1417
+ const timer = setTimeout(() => {
1418
+ try {
1419
+ child.kill();
1420
+ } catch {
1421
+ }
1422
+ resolve3({ ok: false, error: `rebuild exceeded ${Math.round(timeoutMs / 1e3)}s` });
1423
+ }, timeoutMs);
1424
+ timer.unref?.();
1425
+ child.on("error", (err) => {
1426
+ clearTimeout(timer);
1427
+ resolve3({ ok: false, error: err.message });
1428
+ });
1429
+ child.on("exit", (code) => {
1430
+ clearTimeout(timer);
1431
+ resolve3(code === 0 ? { ok: true } : { ok: false, error: `vg build exited ${code ?? "null"}` });
1432
+ });
1433
+ });
1434
+ }
1435
+
1436
+ // src/runtime/vgd/dep-cache.ts
1437
+ var DepContextCache = class {
1438
+ entries = /* @__PURE__ */ new Map();
1439
+ log;
1440
+ now;
1441
+ computeHash;
1442
+ computeInventory;
1443
+ constructor(options = {}) {
1444
+ this.log = options.log ?? (() => {
1445
+ });
1446
+ this.now = options.now ?? (() => Date.now());
1447
+ this.computeHash = options.computeHash ?? manifestHash;
1448
+ this.computeInventory = options.computeInventory ?? inventory;
1449
+ }
1450
+ /** Compute or reuse the context for a repository root. Never throws. */
1451
+ get(repositoryId, root) {
1452
+ const hit = this.entries.get(repositoryId);
1453
+ if (hit) return hit;
1454
+ const started = this.now();
1455
+ try {
1456
+ const context = {
1457
+ manifestHash: this.computeHash(root),
1458
+ inventory: this.computeInventory(root),
1459
+ builtAt: this.now()
1460
+ };
1461
+ this.entries.set(repositoryId, context);
1462
+ this.log(
1463
+ `deps: context for ${repositoryId} built in ${context.builtAt - started}ms \u2014 ${context.inventory.records.length} dependency(ies)`
1464
+ );
1465
+ return context;
1466
+ } catch (err) {
1467
+ this.log(`deps: could not build context for ${repositoryId} \u2014 ${err instanceof Error ? err.message : String(err)}`);
1468
+ return null;
1469
+ }
1470
+ }
1471
+ /**
1472
+ * Forget a repository's context. Called from the freshness watcher when a
1473
+ * manifest or lockfile is written, so the next reader recomputes.
1474
+ */
1475
+ invalidate(repositoryId) {
1476
+ if (this.entries.delete(repositoryId)) this.log(`deps: context for ${repositoryId} invalidated by a manifest change`);
1477
+ }
1478
+ clear() {
1479
+ this.entries.clear();
1480
+ }
1481
+ size() {
1482
+ return this.entries.size;
1483
+ }
1484
+ };
1485
+
918
1486
  // src/runtime/vgd/server.ts
919
1487
  async function startVgdServer(options = {}) {
920
1488
  const socketPath = options.socketPath ?? vgdSocketPath();
@@ -925,23 +1493,60 @@ async function startVgdServer(options = {}) {
925
1493
  });
926
1494
  const embedBroker = options.embedBroker ?? new EmbedBroker({ log });
927
1495
  embedBroker.setGraphProvider((repositoryId, gitRef) => registry.graphs.get(repositoryId, gitRef)?.graph);
1496
+ const freshness = options.freshness ?? new FreshnessSupervisor({
1497
+ log,
1498
+ reload: (repositoryId, root, gitRef) => {
1499
+ const graph = loadGraph(root);
1500
+ if (!graph) return Promise.resolve(null);
1501
+ registry.putGraph(repositoryId, gitRef, graph);
1502
+ return Promise.resolve(graph.nodes?.length ?? 0);
1503
+ },
1504
+ select: (repositoryId, gitRef) => registry.selectGitRef(repositoryId, gitRef)
1505
+ });
1506
+ const watchEnabled = options.watch !== false;
1507
+ const depCache = options.depCache ?? new DepContextCache({ log });
1508
+ freshness.setManifestListener((repositoryId) => depCache.invalidate(repositoryId));
1509
+ embedBroker.setRootProvider((repositoryId) => registry.getById(repositoryId)?.root);
928
1510
  registry.setSlotListener(embedBroker);
929
1511
  const onShutdownRequest = options.onShutdownRequest;
930
1512
  const now = options.now ?? (() => /* @__PURE__ */ new Date());
931
1513
  const pid = options.pid ?? process.pid;
932
1514
  const startedAt = Date.now();
933
1515
  if (!socketPath.startsWith("\\\\.\\pipe")) {
934
- fs2.mkdirSync(path.dirname(socketPath), { recursive: true });
1516
+ fs4.mkdirSync(path3.dirname(socketPath), { recursive: true });
935
1517
  try {
936
- fs2.unlinkSync(socketPath);
1518
+ fs4.unlinkSync(socketPath);
937
1519
  } catch {
938
1520
  }
939
1521
  } else {
940
- fs2.mkdirSync(path.dirname(pidPath), { recursive: true });
941
- }
1522
+ fs4.mkdirSync(path3.dirname(pidPath), { recursive: true });
1523
+ }
1524
+ const subscribers = /* @__PURE__ */ new Map();
1525
+ const broadcastSlot = (repositoryId, gitRef, graph) => {
1526
+ if (!subscribers.size) return;
1527
+ const frame = JSON.stringify({
1528
+ ok: true,
1529
+ event: "slot-changed",
1530
+ repositoryId,
1531
+ gitRef,
1532
+ nodeCount: graph.nodes?.length ?? 0,
1533
+ corpusHash: graph.provenance?.corpusHash ?? null
1534
+ }) + "\n";
1535
+ for (const [socket, filter] of subscribers) {
1536
+ if (filter.repositoryId && filter.repositoryId !== repositoryId) continue;
1537
+ try {
1538
+ socket.write(frame);
1539
+ } catch {
1540
+ subscribers.delete(socket);
1541
+ }
1542
+ }
1543
+ };
1544
+ registry.setPublishListener(broadcastSlot);
942
1545
  const server = net.createServer((socket) => {
943
1546
  let buffer = "";
944
1547
  socket.setEncoding("utf8");
1548
+ socket.on("close", () => subscribers.delete(socket));
1549
+ socket.on("error", () => subscribers.delete(socket));
945
1550
  socket.on("data", (chunk) => {
946
1551
  buffer += chunk;
947
1552
  let nl;
@@ -950,7 +1555,23 @@ async function startVgdServer(options = {}) {
950
1555
  buffer = buffer.slice(nl + 1);
951
1556
  if (!line) continue;
952
1557
  void Promise.resolve(
953
- handleLine(line, { registry, hostBroker, embedBroker, now, pid, startedAt, socketPath, onShutdownRequest, log })
1558
+ handleLine(line, {
1559
+ registry,
1560
+ hostBroker,
1561
+ embedBroker,
1562
+ freshness: watchEnabled ? freshness : void 0,
1563
+ depCache,
1564
+ subscribe: (repositoryId) => {
1565
+ subscribers.set(socket, { repositoryId });
1566
+ log(`watch-slots: subscriber attached${repositoryId ? ` for ${repositoryId}` : " (all repositories)"}`);
1567
+ },
1568
+ now,
1569
+ pid,
1570
+ startedAt,
1571
+ socketPath,
1572
+ onShutdownRequest,
1573
+ log
1574
+ })
954
1575
  ).then(
955
1576
  (response) => {
956
1577
  socket.write(JSON.stringify(response) + "\n");
@@ -959,16 +1580,16 @@ async function startVgdServer(options = {}) {
959
1580
  }
960
1581
  });
961
1582
  });
962
- await new Promise((resolve2, reject) => {
1583
+ await new Promise((resolve3, reject) => {
963
1584
  server.once("error", reject);
964
1585
  server.listen(socketPath, () => {
965
1586
  server.off("error", reject);
966
- resolve2();
1587
+ resolve3();
967
1588
  });
968
1589
  });
969
1590
  try {
970
- fs2.mkdirSync(path.dirname(pidPath), { recursive: true });
971
- fs2.writeFileSync(pidPath, `${pid}
1591
+ fs4.mkdirSync(path3.dirname(pidPath), { recursive: true });
1592
+ fs4.writeFileSync(pidPath, `${pid}
972
1593
  `, "utf8");
973
1594
  } catch {
974
1595
  }
@@ -978,14 +1599,17 @@ async function startVgdServer(options = {}) {
978
1599
  startedAt,
979
1600
  async close() {
980
1601
  embedBroker.stop();
981
- await new Promise((resolve2) => server.close(() => resolve2()));
1602
+ freshness.stopAll();
1603
+ depCache.clear();
1604
+ subscribers.clear();
1605
+ await new Promise((resolve3) => server.close(() => resolve3()));
982
1606
  try {
983
- fs2.unlinkSync(pidPath);
1607
+ fs4.unlinkSync(pidPath);
984
1608
  } catch {
985
1609
  }
986
1610
  if (!socketPath.startsWith("\\\\.\\pipe")) {
987
1611
  try {
988
- fs2.unlinkSync(socketPath);
1612
+ fs4.unlinkSync(socketPath);
989
1613
  } catch {
990
1614
  }
991
1615
  }
@@ -1007,7 +1631,8 @@ async function handleLine(line, ctx) {
1007
1631
  graphSlots: ctx.registry.graphs.size(),
1008
1632
  version: VGD_PROTOCOL_VERSION,
1009
1633
  socketPath: ctx.socketPath,
1010
- cliVersion: VERSION
1634
+ cliVersion: VERSION,
1635
+ freshness: ctx.freshness?.list() ?? []
1011
1636
  };
1012
1637
  case "shutdown": {
1013
1638
  if (!ctx.onShutdownRequest) {
@@ -1052,6 +1677,59 @@ async function handleLine(line, ctx) {
1052
1677
  buildMs: idx.buildMs
1053
1678
  };
1054
1679
  }
1680
+ case "embed-rank": {
1681
+ const gitRef = req.gitRef ?? ctx.registry.graphs.selectedRef(req.repositoryId);
1682
+ if (!gitRef) return { ok: false, error: "no current gitRef for this repository", code: "no_slot" };
1683
+ const slot = ctx.registry.graphs.get(req.repositoryId, gitRef);
1684
+ if (!slot) {
1685
+ return { ok: false, error: `no graph slot for ${req.repositoryId}@${gitRef} \u2014 publish the map first`, code: "no_slot" };
1686
+ }
1687
+ await ctx.embedBroker.ensureIndex(req.repositoryId, gitRef, slot.graph);
1688
+ const started = Date.now();
1689
+ const result = await ctx.embedBroker.rank(req.repositoryId, gitRef, req.text, req.limit);
1690
+ if (!result) {
1691
+ return { ok: false, error: "semantic unavailable \u2014 rank lexically", code: "semantic_unavailable" };
1692
+ }
1693
+ return {
1694
+ ok: true,
1695
+ ranked: result.ranked,
1696
+ repositoryId: req.repositoryId,
1697
+ gitRef,
1698
+ state: result.state,
1699
+ vectors: result.vectors,
1700
+ model: ctx.embedBroker.status().model,
1701
+ rankMs: Date.now() - started
1702
+ };
1703
+ }
1704
+ case "watch-slots": {
1705
+ if (!ctx.subscribe) {
1706
+ return { ok: false, error: "this listener does not support subscriptions", code: "unsupported" };
1707
+ }
1708
+ ctx.subscribe(req.repositoryId);
1709
+ return { ok: true, watching: true, repositoryId: req.repositoryId };
1710
+ }
1711
+ case "dep-context": {
1712
+ const record = ctx.registry.getById(req.repositoryId);
1713
+ if (!record) {
1714
+ return { ok: false, error: `unknown repository ${req.repositoryId} \u2014 register it first`, code: "not_found" };
1715
+ }
1716
+ const context = ctx.depCache.get(req.repositoryId, record.root);
1717
+ if (!context) {
1718
+ return { ok: false, error: "could not read this repository\u2019s manifests", code: "no_deps" };
1719
+ }
1720
+ return {
1721
+ ok: true,
1722
+ repositoryId: req.repositoryId,
1723
+ manifestHash: context.manifestHash,
1724
+ dependencies: context.inventory.records.map((r) => ({
1725
+ name: r.name,
1726
+ ecosystem: r.ecosystem,
1727
+ declared: r.declared,
1728
+ installed: r.installed
1729
+ })),
1730
+ builtAt: context.builtAt
1731
+ };
1732
+ }
1055
1733
  case "host-status":
1056
1734
  return { ok: true, host: ctx.hostBroker.status() };
1057
1735
  case "host-load": {
@@ -1100,8 +1778,11 @@ async function handleLine(line, ctx) {
1100
1778
  }
1101
1779
  return { ok: true, workspace };
1102
1780
  }
1103
- case "unregister":
1781
+ case "unregister": {
1782
+ const record = ctx.registry.get(req.root);
1783
+ if (record) ctx.freshness?.stop(record.id);
1104
1784
  return { ok: true, removed: ctx.registry.unregister(req.root) };
1785
+ }
1105
1786
  case "register-federation": {
1106
1787
  const workspaces = ctx.registry.registerFederation(req.members, ctx.now);
1107
1788
  return { ok: true, workspaces, federation: true };
@@ -1123,6 +1804,7 @@ async function handleLine(line, ctx) {
1123
1804
  const graph = req.graph;
1124
1805
  ctx.log(`put-graph: ${req.repositoryId}@${req.gitRef} \xB7 ${graph.nodes?.length ?? 0} nodes`);
1125
1806
  ctx.registry.putGraph(req.repositoryId, req.gitRef, graph);
1807
+ startWatching(ctx, req.repositoryId, req.gitRef);
1126
1808
  return {
1127
1809
  ok: true,
1128
1810
  stored: true,
@@ -1147,6 +1829,7 @@ async function handleLine(line, ctx) {
1147
1829
  };
1148
1830
  }
1149
1831
  ctx.registry.putGraph(record.id, gitRef, graph);
1832
+ startWatching(ctx, record.id, gitRef);
1150
1833
  return {
1151
1834
  ok: true,
1152
1835
  stored: true,
@@ -1160,13 +1843,27 @@ async function handleLine(line, ctx) {
1160
1843
  if (!resolved) {
1161
1844
  return { ok: false, error: "no ActiveGraph loaded for repository \u2014 put-graph first", code: "no_graph" };
1162
1845
  }
1163
- const result = queryGraph(resolved.graph, req.query, { limit: req.limit ?? 12, budget: 2e3 });
1846
+ const limit = req.limit ?? 12;
1847
+ let mode = "lexical";
1848
+ let result = queryGraph(resolved.graph, req.query, { limit, budget: 2e3 });
1849
+ if (req.semantic) {
1850
+ const ranking = await ctx.embedBroker.rank(req.repositoryId, resolved.gitRef, req.query);
1851
+ if (ranking && ranking.ranked.length) {
1852
+ result = await queryGraphSemantic(resolved.graph, req.query, {
1853
+ limit,
1854
+ budget: 2e3,
1855
+ semanticRanked: ranking.ranked
1856
+ });
1857
+ mode = `semantic (vgd${ctx.embedBroker.status().model ? `, ${ctx.embedBroker.status().model}` : ""})`;
1858
+ }
1859
+ }
1164
1860
  return {
1165
1861
  ok: true,
1166
1862
  query: req.query,
1167
1863
  repositoryId: req.repositoryId,
1168
1864
  gitRef: resolved.gitRef,
1169
- matches: result.matches.slice(0, req.limit ?? 12).map((m) => ({
1865
+ mode,
1866
+ matches: result.matches.slice(0, limit).map((m) => ({
1170
1867
  id: m.node.id,
1171
1868
  qualifiedName: m.node.qualifiedName,
1172
1869
  kind: m.node.kind,
@@ -1229,6 +1926,10 @@ async function handleLine(line, ctx) {
1229
1926
  }
1230
1927
  }
1231
1928
  }
1929
+ function startWatching(ctx, repositoryId, gitRef) {
1930
+ const record = ctx.registry.getById(repositoryId);
1931
+ if (record) ctx.freshness?.start(repositoryId, record.root, gitRef);
1932
+ }
1232
1933
  function resolveSymbol(graph, symbol) {
1233
1934
  if (graph.nodes.some((n) => n.id === symbol)) return { id: symbol };
1234
1935
  const hit = resolveOne(graph, symbol);
@@ -1315,6 +2016,6 @@ var BranchGraphSession = class {
1315
2016
  }
1316
2017
  };
1317
2018
 
1318
- export { ActiveGraphCache, BranchGraphSession, DEFAULT_ACTIVE_GRAPH_IDLE_MS, VGD_PROTOCOL_VERSION, WorkspaceRegistry, parseRequest, startVgdServer };
1319
- //# sourceMappingURL=chunk-GOGMS3ST.js.map
1320
- //# sourceMappingURL=chunk-GOGMS3ST.js.map
2019
+ export { ActiveGraphCache, BranchGraphSession, DEFAULT_ACTIVE_GRAPH_IDLE_MS, VGD_PROTOCOL_VERSION, WorkspaceRegistry, endOfLine, findPackageLine, findRuntimeLine, isDependencyFile, isManifest, loadScanCache, manifestHash, manifestKind, parseRequest, startVgdServer, writeScanCache };
2020
+ //# sourceMappingURL=chunk-MHJBIGWV.js.map
2021
+ //# sourceMappingURL=chunk-MHJBIGWV.js.map