@vibgrate/cli 2026.817.2 → 2026.818.2

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 (153) hide show
  1. package/dist/activity-MF2GNYAI.js +5 -0
  2. package/dist/activity-MF2GNYAI.js.map +1 -0
  3. package/dist/agent-X6VYCE63.js +30 -0
  4. package/dist/{agent-UQC7QN77.js.map → agent-X6VYCE63.js.map} +1 -1
  5. package/dist/{approvals-ASPJQUZK.js → approvals-TPQFQSWG.js} +5 -5
  6. package/dist/{approvals-ASPJQUZK.js.map → approvals-TPQFQSWG.js.map} +1 -1
  7. package/dist/attach-G5JUIWIT.js +10 -0
  8. package/dist/attach-G5JUIWIT.js.map +1 -0
  9. package/dist/baseline-DXI64VLW.js +9 -0
  10. package/dist/{baseline-FIV6PR2M.js.map → baseline-DXI64VLW.js.map} +1 -1
  11. package/dist/{chunk-AXZZ5CCG.js → chunk-2C33WR5O.js} +38 -19
  12. package/dist/chunk-2C33WR5O.js.map +1 -0
  13. package/dist/chunk-4NTNMT6T.js +6 -0
  14. package/dist/{chunk-PRG2ULPK.js.map → chunk-4NTNMT6T.js.map} +1 -1
  15. package/dist/chunk-5IQBIXHS.js +23 -0
  16. package/dist/chunk-5IQBIXHS.js.map +1 -0
  17. package/dist/{chunk-6K3XK7C5.js → chunk-6ABMV74F.js} +6 -3
  18. package/dist/chunk-6ABMV74F.js.map +1 -0
  19. package/dist/chunk-6BEBJCK4.js +467 -0
  20. package/dist/chunk-6BEBJCK4.js.map +1 -0
  21. package/dist/{chunk-E2FAQREZ.js → chunk-6PFHDHOL.js} +174 -401
  22. package/dist/chunk-6PFHDHOL.js.map +1 -0
  23. package/dist/chunk-6WRG7VDO.js +118 -0
  24. package/dist/chunk-6WRG7VDO.js.map +1 -0
  25. package/dist/{chunk-3NJUOIZQ.js → chunk-AEJYA6HZ.js} +5 -5
  26. package/dist/{chunk-3NJUOIZQ.js.map → chunk-AEJYA6HZ.js.map} +1 -1
  27. package/dist/{chunk-O2DL2Q55.js → chunk-B4EXD5LK.js} +5 -5
  28. package/dist/{chunk-O2DL2Q55.js.map → chunk-B4EXD5LK.js.map} +1 -1
  29. package/dist/{chunk-EXV4QSAD.js → chunk-BMOFJIMG.js} +3 -3
  30. package/dist/{chunk-EXV4QSAD.js.map → chunk-BMOFJIMG.js.map} +1 -1
  31. package/dist/chunk-DBX3DL27.js +26 -0
  32. package/dist/chunk-DBX3DL27.js.map +1 -0
  33. package/dist/{chunk-KNGAZDGF.js → chunk-E3O5UKNA.js} +151 -42
  34. package/dist/chunk-E3O5UKNA.js.map +1 -0
  35. package/dist/{chunk-RBIRXQR6.js → chunk-E53WBVUQ.js} +7 -14
  36. package/dist/chunk-E53WBVUQ.js.map +1 -0
  37. package/dist/{chunk-C3LMWZKF.js → chunk-FFXGWFAE.js} +4 -4
  38. package/dist/{chunk-C3LMWZKF.js.map → chunk-FFXGWFAE.js.map} +1 -1
  39. package/dist/chunk-H5Q4JUY4.js +47 -0
  40. package/dist/chunk-H5Q4JUY4.js.map +1 -0
  41. package/dist/{chunk-LHKQGVRF.js → chunk-HF6R73UC.js} +100 -11
  42. package/dist/chunk-HF6R73UC.js.map +1 -0
  43. package/dist/{chunk-K6DXETMC.js → chunk-JGA63RGF.js} +13 -12
  44. package/dist/chunk-JGA63RGF.js.map +1 -0
  45. package/dist/{chunk-3FMRFECC.js → chunk-KU7FI5EV.js} +3 -3
  46. package/dist/{chunk-3FMRFECC.js.map → chunk-KU7FI5EV.js.map} +1 -1
  47. package/dist/{chunk-5OL2TSHK.js → chunk-L2OS4WHT.js} +4 -4
  48. package/dist/{chunk-5OL2TSHK.js.map → chunk-L2OS4WHT.js.map} +1 -1
  49. package/dist/chunk-LJSJX6U4.js +38 -0
  50. package/dist/chunk-LJSJX6U4.js.map +1 -0
  51. package/dist/{chunk-Q4G2BWE3.js → chunk-MRZHNUN7.js} +12 -6
  52. package/dist/chunk-MRZHNUN7.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-NJQZRM55.js +100 -0
  56. package/dist/chunk-NJQZRM55.js.map +1 -0
  57. package/dist/{chunk-DVW3SVCW.js → chunk-NKLYDSB7.js} +29 -9
  58. package/dist/chunk-NKLYDSB7.js.map +1 -0
  59. package/dist/{chunk-TVDMNIUT.js → chunk-O5IJDMTB.js} +4 -4
  60. package/dist/{chunk-TVDMNIUT.js.map → chunk-O5IJDMTB.js.map} +1 -1
  61. package/dist/chunk-POOL355F.js +237 -0
  62. package/dist/chunk-POOL355F.js.map +1 -0
  63. package/dist/{chunk-XLXJC7H3.js → chunk-PRHH4DVO.js} +5 -118
  64. package/dist/chunk-PRHH4DVO.js.map +1 -0
  65. package/dist/{chunk-3H3SVJUY.js → chunk-RHKOUPWT.js} +107 -542
  66. package/dist/chunk-RHKOUPWT.js.map +1 -0
  67. package/dist/{chunk-WAKP7SK7.js → chunk-V2UGRUTL.js} +225 -48
  68. package/dist/chunk-V2UGRUTL.js.map +1 -0
  69. package/dist/{chunk-2FEB5KJS.js → chunk-V6WONSIK.js} +5 -4
  70. package/dist/chunk-V6WONSIK.js.map +1 -0
  71. package/dist/{chunk-WVMOGIPC.js → chunk-VMAIDM4K.js} +3 -3
  72. package/dist/{chunk-WVMOGIPC.js.map → chunk-VMAIDM4K.js.map} +1 -1
  73. package/dist/{chunk-GOGMS3ST.js → chunk-VQ5KMHW6.js} +860 -65
  74. package/dist/chunk-VQ5KMHW6.js.map +1 -0
  75. package/dist/{chunk-M7IO3DXH.js → chunk-X2MQ7UFL.js} +250 -30
  76. package/dist/chunk-X2MQ7UFL.js.map +1 -0
  77. package/dist/{chunk-OZ3IFXRD.js → chunk-YENM3HT7.js} +5 -4
  78. package/dist/chunk-YENM3HT7.js.map +1 -0
  79. package/dist/{chunk-KJT2R6WW.js → chunk-YF54UMOU.js} +3 -3
  80. package/dist/{chunk-KJT2R6WW.js.map → chunk-YF54UMOU.js.map} +1 -1
  81. package/dist/{chunk-Q3M6K3HY.js → chunk-ZUOBPKVK.js} +3 -3
  82. package/dist/{chunk-Q3M6K3HY.js.map → chunk-ZUOBPKVK.js.map} +1 -1
  83. package/dist/cli.d.ts +6 -0
  84. package/dist/cli.js +711 -463
  85. package/dist/cli.js.map +1 -1
  86. package/dist/client-UQIELEEZ.js +8 -0
  87. package/dist/client-UQIELEEZ.js.map +1 -0
  88. package/dist/daemon-GUMWMKCA.js +31 -0
  89. package/dist/{daemon-FPTRHO5N.js.map → daemon-GUMWMKCA.js.map} +1 -1
  90. package/dist/embed-worker-main.js +3 -3
  91. package/dist/ensure-map-FHDJTO7Y.js +41 -0
  92. package/dist/{ensure-map-342G2IV3.js.map → ensure-map-FHDJTO7Y.js.map} +1 -1
  93. package/dist/graph-backend-W2D3HRRD.js +16 -0
  94. package/dist/{graph-backend-RNWQBT3P.js.map → graph-backend-W2D3HRRD.js.map} +1 -1
  95. package/dist/index.d.ts +633 -4
  96. package/dist/index.js +26 -17
  97. package/dist/index.js.map +1 -1
  98. package/dist/{interactive-SR7TMF6M.js → interactive-2MK4TLRD.js} +42 -34
  99. package/dist/interactive-2MK4TLRD.js.map +1 -0
  100. package/dist/load-4R4HYMBB.js +11 -0
  101. package/dist/{load-AYFECIQB.js.map → load-4R4HYMBB.js.map} +1 -1
  102. package/dist/parse-worker.js +3 -2
  103. package/dist/parse-worker.js.map +1 -1
  104. package/dist/publish-7YHRTBRA.js +9 -0
  105. package/dist/publish-7YHRTBRA.js.map +1 -0
  106. package/dist/{run-outcome-YQQJG3LW.js → run-outcome-H5GVDDZJ.js} +5 -5
  107. package/dist/{run-outcome-YQQJG3LW.js.map → run-outcome-H5GVDDZJ.js.map} +1 -1
  108. package/dist/runtime-session-JBIHHH5N.js +27 -0
  109. package/dist/{runtime-session-CNJ4DAV5.js.map → runtime-session-JBIHHH5N.js.map} +1 -1
  110. package/dist/semantic-status-2JFLIQQJ.js +4 -0
  111. package/dist/semantic-status-2JFLIQQJ.js.map +1 -0
  112. package/dist/{session-H34HUV2Y.js → session-N5IU4K5Q.js} +8 -8
  113. package/dist/{session-H34HUV2Y.js.map → session-N5IU4K5Q.js.map} +1 -1
  114. package/dist/slot-subscription-25GOARW5.js +95 -0
  115. package/dist/slot-subscription-25GOARW5.js.map +1 -0
  116. package/dist/{stream-json-55YDNAFX.js → stream-json-4WLSUF6I.js} +18 -15
  117. package/dist/stream-json-4WLSUF6I.js.map +1 -0
  118. package/dist/version-DTEU3SQD.js +4 -0
  119. package/dist/{version-WSE3QOPK.js.map → version-DTEU3SQD.js.map} +1 -1
  120. package/dist/{vg-mcp-bridge-2CNLHNEL.js → vg-mcp-bridge-KDVYIMFX.js} +12 -11
  121. package/dist/vg-mcp-bridge-KDVYIMFX.js.map +1 -0
  122. package/dist/{vgd-URUZQORN.js → vgd-M7UOPWLB.js} +15 -10
  123. package/dist/vgd-M7UOPWLB.js.map +1 -0
  124. package/package.json +1 -1
  125. package/dist/agent-UQC7QN77.js +0 -27
  126. package/dist/baseline-FIV6PR2M.js +0 -9
  127. package/dist/chunk-2FEB5KJS.js.map +0 -1
  128. package/dist/chunk-3H3SVJUY.js.map +0 -1
  129. package/dist/chunk-6K3XK7C5.js.map +0 -1
  130. package/dist/chunk-AXZZ5CCG.js.map +0 -1
  131. package/dist/chunk-DVW3SVCW.js.map +0 -1
  132. package/dist/chunk-E2FAQREZ.js.map +0 -1
  133. package/dist/chunk-GOGMS3ST.js.map +0 -1
  134. package/dist/chunk-K6DXETMC.js.map +0 -1
  135. package/dist/chunk-KNGAZDGF.js.map +0 -1
  136. package/dist/chunk-LHKQGVRF.js.map +0 -1
  137. package/dist/chunk-M7IO3DXH.js.map +0 -1
  138. package/dist/chunk-OZ3IFXRD.js.map +0 -1
  139. package/dist/chunk-PRG2ULPK.js +0 -6
  140. package/dist/chunk-Q4G2BWE3.js.map +0 -1
  141. package/dist/chunk-RBIRXQR6.js.map +0 -1
  142. package/dist/chunk-WAKP7SK7.js.map +0 -1
  143. package/dist/chunk-XLXJC7H3.js.map +0 -1
  144. package/dist/daemon-FPTRHO5N.js +0 -26
  145. package/dist/ensure-map-342G2IV3.js +0 -33
  146. package/dist/graph-backend-RNWQBT3P.js +0 -15
  147. package/dist/interactive-SR7TMF6M.js.map +0 -1
  148. package/dist/load-AYFECIQB.js +0 -11
  149. package/dist/runtime-session-CNJ4DAV5.js +0 -22
  150. package/dist/stream-json-55YDNAFX.js.map +0 -1
  151. package/dist/version-WSE3QOPK.js +0 -4
  152. package/dist/vg-mcp-bridge-2CNLHNEL.js.map +0 -1
  153. 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-MRZHNUN7.js';
7
+ import { loadGraph } from './chunk-KU7FI5EV.js';
8
+ import { vgdSocketPath, vgdPidPath } from './chunk-NDNNOAS6.js';
9
+ import { embedTargets, readCachedVectors, cosine } from './chunk-2C33WR5O.js';
10
+ import { cacheDir } from './chunk-MVPRPSXU.js';
11
+ import { stableStringify } from './chunk-O3LABX6I.js';
12
+ import { VERSION } from './chunk-4NTNMT6T.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';
@@ -78,9 +85,41 @@ function parseRequest(line) {
78
85
  return {
79
86
  op: "embed-index",
80
87
  repositoryId: repositoryId.trim(),
81
- gitRef: typeof gitRef === "string" && gitRef.trim() ? gitRef.trim() : void 0
88
+ gitRef: typeof gitRef === "string" && gitRef.trim() ? gitRef.trim() : void 0,
89
+ wait: raw.wait === false ? false : void 0
90
+ };
91
+ }
92
+ if (op === "embed-rank") {
93
+ const repositoryId = raw.repositoryId;
94
+ const gitRef = raw.gitRef;
95
+ const text = raw.text;
96
+ const limit = raw.limit;
97
+ if (typeof repositoryId !== "string" || !repositoryId.trim()) return { error: "embed-rank requires repositoryId" };
98
+ if (typeof text !== "string" || !text.trim()) return { error: "embed-rank requires text" };
99
+ return {
100
+ op: "embed-rank",
101
+ repositoryId: repositoryId.trim(),
102
+ gitRef: typeof gitRef === "string" && gitRef.trim() ? gitRef.trim() : void 0,
103
+ // Same bound as embed-query: untrusted socket input, one worker.
104
+ text: text.trim().slice(0, 8e3),
105
+ // Only the ORDER of this list reaches the caller's rank fusion, and a
106
+ // candidate past a few hundred can never surface in a 12-row answer —
107
+ // so a generous cap costs nothing and a huge one buys nothing.
108
+ limit: typeof limit === "number" && limit > 0 ? Math.min(Math.floor(limit), 1e3) : void 0
109
+ };
110
+ }
111
+ if (op === "watch-slots") {
112
+ const repositoryId = raw.repositoryId;
113
+ return {
114
+ op: "watch-slots",
115
+ repositoryId: typeof repositoryId === "string" && repositoryId.trim() ? repositoryId.trim() : void 0
82
116
  };
83
117
  }
118
+ if (op === "dep-context") {
119
+ const repositoryId = raw.repositoryId;
120
+ if (typeof repositoryId !== "string" || !repositoryId.trim()) return { error: "dep-context requires repositoryId" };
121
+ return { op: "dep-context", repositoryId: repositoryId.trim() };
122
+ }
84
123
  if (op === "query-graph") {
85
124
  const repositoryId = raw.repositoryId;
86
125
  const query = raw.query;
@@ -93,7 +132,13 @@ function parseRequest(line) {
93
132
  repositoryId: repositoryId.trim(),
94
133
  query: query.trim(),
95
134
  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
135
+ gitRef: typeof gitRef === "string" && gitRef.trim() ? gitRef.trim() : void 0,
136
+ // Opt-in, and left off the parsed request entirely when not asked: a
137
+ // client that asks for semantic gets the fused ranking when the slot's
138
+ // index is ready, and the lexical answer when it is not. Ranking and
139
+ // fusion both happen here, so the caller needs neither the graph nor the
140
+ // embedder — which is the whole reason a panel can afford to ask.
141
+ semantic: raw.semantic === true ? true : void 0
97
142
  };
98
143
  }
99
144
  if (op === "impact-of") {
@@ -386,6 +431,8 @@ var WorkspaceRegistry = class {
386
431
  * index can never describe a ref its graph no longer holds.
387
432
  */
388
433
  slotListener;
434
+ /** Told after a graph is stored, so the daemon can push to subscribers. */
435
+ publishListener;
389
436
  /** Multi-branch in-memory graphs (shared across registered workspaces). */
390
437
  graphs = new ActiveGraphCache({
391
438
  onEvict: (slot) => this.slotListener?.onGraphEvict(slot.repositoryId, slot.gitRef)
@@ -394,8 +441,17 @@ var WorkspaceRegistry = class {
394
441
  setSlotListener(listener) {
395
442
  this.slotListener = listener;
396
443
  }
444
+ /**
445
+ * Attach the push channel. Separate from {@link GraphSlotListener} on
446
+ * purpose: that one keeps *slot-scoped resources* in step and must run
447
+ * before anyone can query, while this one notifies *other processes* and
448
+ * must run after the store is complete.
449
+ */
450
+ setPublishListener(listener) {
451
+ this.publishListener = listener;
452
+ }
397
453
  register(root, now = () => /* @__PURE__ */ new Date(), meta = {}) {
398
- const resolved = path.resolve(root);
454
+ const resolved = path3.resolve(root);
399
455
  const id = repositoryIdFromRoot(resolved);
400
456
  const git = meta.gitRef ? { ref: meta.gitRef, kind: "branch" } : detectGitRef(resolved);
401
457
  const graphPath = git.kind !== "none" && git.ref ? globalGraphPathForRef(resolved, git.ref) : globalGraphPath(resolved);
@@ -409,7 +465,7 @@ var WorkspaceRegistry = class {
409
465
  gitRef: git.ref || void 0
410
466
  };
411
467
  this.byId.set(id, record);
412
- if (git.ref) this.graphs.select(id, git.ref);
468
+ if (git.ref) this.selectGitRef(id, git.ref);
413
469
  return record;
414
470
  }
415
471
  /**
@@ -421,6 +477,7 @@ var WorkspaceRegistry = class {
421
477
  this.graphs.select(repositoryId, gitRef);
422
478
  this.slotListener?.onGraphPut(repositoryId, gitRef, graph);
423
479
  this.slotListener?.onGraphSelect(repositoryId, gitRef);
480
+ this.publishListener?.(repositoryId, gitRef, graph);
424
481
  }
425
482
  /** Switch the current branch slot for a repository (no load). */
426
483
  selectGitRef(repositoryId, gitRef) {
@@ -432,14 +489,14 @@ var WorkspaceRegistry = class {
432
489
  return members.map((m) => this.register(m.root, now, { label: m.label, role: m.role }));
433
490
  }
434
491
  unregister(root) {
435
- const id = repositoryIdFromRoot(path.resolve(root));
492
+ const id = repositoryIdFromRoot(path3.resolve(root));
436
493
  return this.byId.delete(id);
437
494
  }
438
495
  list() {
439
496
  return [...this.byId.values()].sort((a, b) => a.root.localeCompare(b.root));
440
497
  }
441
498
  get(root) {
442
- return this.byId.get(repositoryIdFromRoot(path.resolve(root)));
499
+ return this.byId.get(repositoryIdFromRoot(path3.resolve(root)));
443
500
  }
444
501
  /** Lookup by stable repository id. */
445
502
  getById(repositoryId) {
@@ -482,8 +539,8 @@ var VgdHostBroker = class {
482
539
  }
483
540
  status() {
484
541
  const leases = {};
485
- for (const [path4, clients] of this.leases) {
486
- leases[path4] = clients.size;
542
+ for (const [path6, clients] of this.leases) {
543
+ leases[path6] = clients.size;
487
544
  }
488
545
  return {
489
546
  poolSize: hostSessionPoolSize(),
@@ -582,10 +639,6 @@ function getVgdHostBroker() {
582
639
  if (!defaultBroker) defaultBroker = new VgdHostBroker();
583
640
  return defaultBroker;
584
641
  }
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
642
  var EmbedBroker = class {
590
643
  child = null;
591
644
  starting = false;
@@ -597,17 +650,21 @@ var EmbedBroker = class {
597
650
  buffer = "";
598
651
  pending = /* @__PURE__ */ new Map();
599
652
  indexes = /* @__PURE__ */ new Map();
600
- /** Slot keys with a build in flight, so warms are not duplicated. */
601
- building = /* @__PURE__ */ new Set();
653
+ /** In-flight builds, so a second caller waits for the same work instead of seeing an empty slot. */
654
+ inflight = /* @__PURE__ */ new Map();
655
+ /** One deferred retry per slot after standing down for an on-disk writer. */
656
+ rewarmTimers = /* @__PURE__ */ new Map();
602
657
  /** The slot most recently made current — logged on change. */
603
658
  currentKey;
604
659
  graphProvider;
660
+ rootProvider;
605
661
  log;
606
662
  spawnWorker;
607
663
  maxCrashes;
608
664
  requestTimeoutMs;
609
665
  batchSize;
610
666
  now;
667
+ sleepImpl;
611
668
  constructor(options = {}) {
612
669
  this.log = options.log ?? (() => {
613
670
  });
@@ -616,6 +673,7 @@ var EmbedBroker = class {
616
673
  this.requestTimeoutMs = options.requestTimeoutMs ?? 12e4;
617
674
  this.batchSize = options.batchSize ?? 256;
618
675
  this.now = options.now ?? (() => Date.now());
676
+ this.sleepImpl = options.sleep ?? ((ms) => new Promise((resolve3) => setTimeout(resolve3, ms)));
619
677
  }
620
678
  status() {
621
679
  return {
@@ -637,6 +695,7 @@ var EmbedBroker = class {
637
695
  state: idx.state,
638
696
  vectors: idx.vectors.size,
639
697
  nodeCount: idx.nodeCount,
698
+ pending: idx.pending,
640
699
  builtAt: idx.builtAt,
641
700
  buildMs: idx.buildMs,
642
701
  error: idx.error
@@ -661,14 +720,15 @@ var EmbedBroker = class {
661
720
  const key = activeGraphSlotKey(repositoryId, gitRef);
662
721
  const existing = this.indexes.get(key);
663
722
  const nodeCount = graph.nodes?.length ?? 0;
664
- if (existing && existing.state === "ready") {
665
- this.log(`semantic: ${repositoryId}@${gitRef} graph republished \u2014 index stale (${existing.vectors.size} vectors dropped)`);
723
+ const kept = existing?.vectors.size ? new Map(existing.vectors) : /* @__PURE__ */ new Map();
724
+ if (existing && existing.vectors.size > 0) {
725
+ this.log(`semantic: ${repositoryId}@${gitRef} graph republished \u2014 index stale (${existing.vectors.size} vectors kept until rebuild)`);
666
726
  }
667
727
  this.indexes.set(key, {
668
728
  repositoryId,
669
729
  gitRef,
670
730
  state: "stale",
671
- vectors: /* @__PURE__ */ new Map(),
731
+ vectors: kept,
672
732
  nodeCount
673
733
  });
674
734
  }
@@ -679,6 +739,15 @@ var EmbedBroker = class {
679
739
  setGraphProvider(provider) {
680
740
  this.graphProvider = provider;
681
741
  }
742
+ /**
743
+ * Supplies the absolute repository root for a slot, so the broker can read
744
+ * that repo's on-disk vector cache. It cannot come from the graph:
745
+ * `meta.root` is deliberately relative (`.`) so snapshots stay portable
746
+ * across machines. The registry holds the real path.
747
+ */
748
+ setRootProvider(provider) {
749
+ this.rootProvider = provider;
750
+ }
682
751
  /**
683
752
  * A slot became current — a branch switch, a new repo, a fresh publish.
684
753
  * The matching index is swapped in, and warmed in the background if it is
@@ -695,7 +764,7 @@ var EmbedBroker = class {
695
764
  }
696
765
  if (this.gaveUp) return;
697
766
  const idx = this.indexes.get(key);
698
- if (idx?.state === "ready" || this.building.has(key)) return;
767
+ if (idx?.state === "ready" || this.inflight.has(key)) return;
699
768
  const graph = this.graphProvider?.(repositoryId, gitRef);
700
769
  if (!graph) return;
701
770
  void this.ensureIndex(repositoryId, gitRef, graph);
@@ -706,18 +775,26 @@ var EmbedBroker = class {
706
775
  const idx = this.indexes.get(key);
707
776
  if (!idx) return;
708
777
  this.indexes.delete(key);
778
+ this.clearRewarm(key);
709
779
  this.log(`semantic: ${repositoryId}@${gitRef} index evicted with its graph slot (${idx.vectors.size} vectors freed)`);
710
780
  }
711
781
  /** Drop everything (daemon shutdown / registry clear). */
712
782
  clear() {
713
783
  this.indexes.clear();
714
- this.building.clear();
784
+ this.inflight.clear();
785
+ for (const timer of this.rewarmTimers.values()) clearTimeout(timer);
786
+ this.rewarmTimers.clear();
715
787
  }
716
788
  /**
717
789
  * Ensure the slot has vectors, building them if needed. Resolves when the
718
790
  * index is ready or has definitively failed; never throws.
791
+ *
792
+ * A second caller for the same slot waits for the in-flight build instead of
793
+ * returning the empty placeholder — that empty return is what made `vg ask`
794
+ * re-embed tens of thousands of nodes in-process while vgd was already doing
795
+ * the same work.
719
796
  */
720
- async ensureIndex(repositoryId, gitRef, graph) {
797
+ async ensureIndex(repositoryId, gitRef, graph, options) {
721
798
  const key = activeGraphSlotKey(repositoryId, gitRef);
722
799
  let idx = this.indexes.get(key);
723
800
  if (!idx) {
@@ -730,43 +807,156 @@ var EmbedBroker = class {
730
807
  idx.error = this.reason ?? "embedding worker unavailable";
731
808
  return idx;
732
809
  }
733
- if (this.building.has(key)) return idx;
734
- this.building.add(key);
810
+ const pending = this.inflight.get(key);
811
+ if (pending) return pending;
812
+ const work = this.buildIndex(key, idx, repositoryId, gitRef, graph, options).finally(() => {
813
+ if (this.inflight.get(key) === work) this.inflight.delete(key);
814
+ });
815
+ this.inflight.set(key, work);
816
+ return work;
817
+ }
818
+ async buildIndex(key, idx, repositoryId, gitRef, graph, options) {
735
819
  idx.state = "building";
736
- const nodes = (graph.nodes ?? []).filter((n) => !!n?.id);
737
820
  const started = this.now();
738
- this.log(`semantic: building index for ${repositoryId}@${gitRef} (${nodes.length} nodes)\u2026`);
821
+ let seeded = this.seedFromDisk(repositoryId, graph);
822
+ if (seeded?.writerBusy && seeded.pending.length && options?.waitForWriter) {
823
+ this.log(`semantic: ${repositoryId}@${gitRef} waiting for another process to finish the on-disk index\u2026`);
824
+ await this.waitWhileWriterBusy(repositoryId, graph, options.waitForWriterMs ?? 3e5);
825
+ seeded = this.seedFromDisk(repositoryId, graph);
826
+ }
827
+ const targets = seeded?.pending ?? embedTargets(graph);
828
+ const vectors = seeded ? seeded.vectors : /* @__PURE__ */ new Map();
829
+ const nodeCount = seeded ? seeded.vectors.size + seeded.pending.length : targets.length;
830
+ idx.nodeCount = nodeCount;
831
+ if (seeded && seeded.vectors.size) {
832
+ this.log(
833
+ `semantic: seeded ${repositoryId}@${gitRef} from the on-disk index \u2014 ${seeded.vectors.size} vectors, ${seeded.pending.length} pending`
834
+ );
835
+ }
836
+ if (seeded?.writerBusy && targets.length) {
837
+ idx.vectors = vectors;
838
+ idx.pending = targets.length;
839
+ idx.state = vectors.size ? "stale" : "absent";
840
+ this.log(
841
+ `semantic: ${repositoryId}@${gitRef} left at ${targets.length} pending \u2014 another process is writing the on-disk index; re-warming when it reports in`
842
+ );
843
+ this.scheduleRewarm(repositoryId, gitRef);
844
+ return idx;
845
+ }
846
+ this.log(`semantic: building index for ${repositoryId}@${gitRef} (${targets.length} of ${nodeCount} nodes)\u2026`);
739
847
  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) => {
848
+ for (let i = 0; i < targets.length; i += this.batchSize) {
849
+ const batch = targets.slice(i, i + this.batchSize);
850
+ const out = await this.embed(batch.map((t) => t.text));
851
+ batch.forEach((t, j) => {
745
852
  const v = out[j];
746
- if (v) vectors.set(n.id, v);
853
+ if (v) vectors.set(t.id, v);
747
854
  });
855
+ if (idx.vectors.size === 0 || idx.vectors === vectors) idx.vectors = vectors;
856
+ idx.pending = Math.max(0, nodeCount - vectors.size);
748
857
  const live = this.indexes.get(key);
749
858
  if (!live || live !== idx) {
750
859
  this.log(`semantic: index build for ${repositoryId}@${gitRef} abandoned \u2014 slot changed underneath it`);
860
+ this.inflight.delete(key);
861
+ if (live && live !== idx) {
862
+ const nextGraph = this.graphProvider?.(repositoryId, gitRef) ?? graph;
863
+ return this.ensureIndex(repositoryId, gitRef, nextGraph, options);
864
+ }
751
865
  return idx;
752
866
  }
753
867
  }
754
868
  idx.vectors = vectors;
755
- idx.nodeCount = nodes.length;
869
+ idx.pending = void 0;
756
870
  idx.state = "ready";
757
871
  idx.builtAt = this.now();
758
872
  idx.buildMs = idx.builtAt - started;
759
873
  idx.error = void 0;
874
+ this.clearRewarm(key);
760
875
  this.log(`semantic: index ready for ${repositoryId}@${gitRef} \u2014 ${vectors.size} vectors in ${idx.buildMs}ms`);
761
876
  } catch (err) {
877
+ idx.vectors = vectors;
878
+ idx.pending = targets.length;
762
879
  idx.state = "failed";
763
880
  idx.error = err instanceof Error ? err.message : String(err);
764
881
  this.log(`semantic: index build failed for ${repositoryId}@${gitRef} \u2014 ${idx.error}; callers fall back to lexical`);
765
- } finally {
766
- this.building.delete(key);
767
882
  }
768
883
  return idx;
769
884
  }
885
+ /** Poll the on-disk lock until the writer is gone or `timeoutMs` elapses. */
886
+ async waitWhileWriterBusy(repositoryId, graph, timeoutMs) {
887
+ const deadline = Date.now() + timeoutMs;
888
+ while (Date.now() < deadline) {
889
+ const read = this.seedFromDisk(repositoryId, graph);
890
+ if (!read?.writerBusy) return;
891
+ await this.sleepImpl(250);
892
+ }
893
+ }
894
+ /** Retry a slot that stood down for an on-disk writer, once the writer should have moved. */
895
+ scheduleRewarm(repositoryId, gitRef) {
896
+ const key = activeGraphSlotKey(repositoryId, gitRef);
897
+ if (this.rewarmTimers.has(key)) return;
898
+ const timer = setTimeout(() => {
899
+ this.rewarmTimers.delete(key);
900
+ const idx = this.indexes.get(key);
901
+ if (!idx || idx.state === "ready" || this.inflight.has(key)) return;
902
+ const graph = this.graphProvider?.(repositoryId, gitRef);
903
+ if (!graph) return;
904
+ void this.ensureIndex(repositoryId, gitRef, graph);
905
+ }, 2e3);
906
+ timer.unref?.();
907
+ this.rewarmTimers.set(key, timer);
908
+ }
909
+ clearRewarm(key) {
910
+ const timer = this.rewarmTimers.get(key);
911
+ if (!timer) return;
912
+ clearTimeout(timer);
913
+ this.rewarmTimers.delete(key);
914
+ }
915
+ /**
916
+ * Read this slot's vectors out of the repo's on-disk cache. Needs the repo
917
+ * root from the registry; a repository the daemon has no root for (a
918
+ * synthetic publish in a test) simply builds from scratch.
919
+ */
920
+ seedFromDisk(repositoryId, graph) {
921
+ const root = this.rootProvider?.(repositoryId);
922
+ if (!root) return null;
923
+ try {
924
+ const read = readCachedVectors(graph, root);
925
+ if (!this.model) this.model = read.model;
926
+ return { vectors: read.vectors, pending: read.pending, writerBusy: read.writerBusy };
927
+ } catch (err) {
928
+ this.log(`semantic: could not read the on-disk index \u2014 ${err instanceof Error ? err.message : String(err)}; embedding from scratch`);
929
+ return null;
930
+ }
931
+ }
932
+ /**
933
+ * Rank a slot's nodes against one question, inside the daemon.
934
+ *
935
+ * This is the op that makes the resident index worth holding. The
936
+ * alternative — shipping the vectors to the caller so it can rank locally —
937
+ * moves tens of megabytes over a socket per ask; ranking here moves one
938
+ * question in and a few hundred (id, score) pairs out.
939
+ *
940
+ * Only the *order* of this list reaches the caller's rank fusion, so
941
+ * truncating at `limit` is not an approximation of the answer: a candidate
942
+ * ranked past a few hundred cannot reach a 12-row result under RRF.
943
+ *
944
+ * Returns null whenever semantic is unavailable — a caller that gets null
945
+ * runs its own in-process pass, exactly as it does with no daemon at all.
946
+ */
947
+ async rank(repositoryId, gitRef, text, limit = 200) {
948
+ const idx = this.slot(repositoryId, gitRef);
949
+ if (!idx || idx.vectors.size === 0) return null;
950
+ const queryVec = await this.embedQuery(text);
951
+ if (!queryVec) return null;
952
+ const scored = [];
953
+ for (const [id, vec] of idx.vectors) {
954
+ const score = cosine(queryVec, vec);
955
+ if (score > 0) scored.push({ id, score });
956
+ }
957
+ scored.sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
958
+ return { ranked: scored.slice(0, limit), state: idx.state, vectors: idx.vectors.size };
959
+ }
770
960
  /** Embed one query string. Returns null when semantic is not available. */
771
961
  async embedQuery(text) {
772
962
  if (this.gaveUp) return null;
@@ -780,6 +970,8 @@ var EmbedBroker = class {
780
970
  }
781
971
  /** Stop the worker (daemon shutdown). Safe to call when already stopped. */
782
972
  stop() {
973
+ for (const timer of this.rewarmTimers.values()) clearTimeout(timer);
974
+ this.rewarmTimers.clear();
783
975
  const child = this.child;
784
976
  this.child = null;
785
977
  this.starting = false;
@@ -880,13 +1072,13 @@ var EmbedBroker = class {
880
1072
  return Promise.reject(error);
881
1073
  }
882
1074
  const id = this.nextId++;
883
- return new Promise((resolve2, reject) => {
1075
+ return new Promise((resolve3, reject) => {
884
1076
  const timer = setTimeout(() => {
885
1077
  this.pending.delete(id);
886
1078
  reject(new Error(`embedding request timed out after ${this.requestTimeoutMs}ms`));
887
1079
  }, this.requestTimeoutMs);
888
1080
  timer.unref?.();
889
- this.pending.set(id, { resolve: resolve2, reject, timer });
1081
+ this.pending.set(id, { resolve: resolve3, reject, timer });
890
1082
  try {
891
1083
  child.stdin?.write(JSON.stringify({ id, op: "embed", texts, query }) + "\n");
892
1084
  } catch (err) {
@@ -902,9 +1094,9 @@ var EmbedBroker = class {
902
1094
  }
903
1095
  };
904
1096
  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)) {
1097
+ const here = path3.dirname(fileURLToPath(import.meta.url));
1098
+ const entry = path3.join(here, "embed-worker-main.js");
1099
+ if (!fs4.existsSync(entry)) {
908
1100
  throw new Error(`embedding worker not found at ${entry} \u2014 semantic runs lexically`);
909
1101
  }
910
1102
  return spawn(process.execPath, [entry], {
@@ -915,6 +1107,446 @@ function defaultSpawnWorker() {
915
1107
  });
916
1108
  }
917
1109
 
1110
+ // src/lsp/manifest-positions.ts
1111
+ function manifestKind(filePath) {
1112
+ const base = (filePath.split(/[\\/]/).pop() ?? "").toLowerCase();
1113
+ if (base === "package.json") return "package.json";
1114
+ if (base === "go.mod") return "go.mod";
1115
+ if (base === "requirements.txt" || /^requirements.*\.txt$/.test(base)) return "requirements.txt";
1116
+ if (base === "pyproject.toml") return "pyproject.toml";
1117
+ if (base === "cargo.toml") return "Cargo.toml";
1118
+ if (base === "pom.xml") return "pom.xml";
1119
+ if (base === "build.gradle" || base === "build.gradle.kts") return "build.gradle";
1120
+ if (base === "gemfile") return "Gemfile";
1121
+ if (base === "composer.json") return "composer.json";
1122
+ if (base === "pubspec.yaml") return "pubspec.yaml";
1123
+ if (base === "mix.exs") return "mix.exs";
1124
+ if (base.endsWith(".csproj") || base.endsWith(".fsproj") || base.endsWith(".vbproj")) return "csproj";
1125
+ if (base === "dockerfile" || base.startsWith("dockerfile.")) return "Dockerfile";
1126
+ return "unknown";
1127
+ }
1128
+ function isManifest(filePath) {
1129
+ return manifestKind(filePath) !== "unknown";
1130
+ }
1131
+ function esc(s) {
1132
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1133
+ }
1134
+ function findPackageLine(text, pkg, kind) {
1135
+ const lines = text.split(/\r?\n/);
1136
+ const p = esc(pkg);
1137
+ const patterns = (() => {
1138
+ switch (kind) {
1139
+ case "package.json":
1140
+ case "composer.json":
1141
+ return [new RegExp(`^\\s*"${p}"\\s*:`)];
1142
+ case "go.mod":
1143
+ return [new RegExp(`^\\s*(?:require\\s+)?${p}\\s+v?\\d`)];
1144
+ case "requirements.txt":
1145
+ return [new RegExp(`^\\s*${p}\\s*(?:\\[[^\\]]*\\])?\\s*(?:[=<>!~]|$)`, "i")];
1146
+ case "pyproject.toml":
1147
+ case "Cargo.toml":
1148
+ return [
1149
+ new RegExp(`^\\s*${p}\\s*=`),
1150
+ new RegExp(`^\\s*"${p}\\s*(?:[=<>!~\\[]|")`)
1151
+ ];
1152
+ case "pom.xml":
1153
+ return [new RegExp(`<artifactId>\\s*${p}\\s*</artifactId>`)];
1154
+ case "build.gradle":
1155
+ return [new RegExp(`["']${p}(?:["':])`)];
1156
+ case "Gemfile":
1157
+ return [new RegExp(`^\\s*gem\\s+["']${p}["']`)];
1158
+ case "pubspec.yaml":
1159
+ return [new RegExp(`^\\s*${p}\\s*:`)];
1160
+ case "mix.exs":
1161
+ return [new RegExp(`\\{\\s*:${p}\\s*,`)];
1162
+ case "csproj":
1163
+ return [new RegExp(`Include\\s*=\\s*"${p}"`, "i")];
1164
+ case "Dockerfile":
1165
+ return [new RegExp(`^\\s*FROM\\s+.*${p}`, "i")];
1166
+ default:
1167
+ return [];
1168
+ }
1169
+ })();
1170
+ for (const re of patterns) {
1171
+ for (let i = 0; i < lines.length; i++) {
1172
+ if (re.test(lines[i])) return i;
1173
+ }
1174
+ }
1175
+ return -1;
1176
+ }
1177
+ function findRuntimeLine(text, kind) {
1178
+ const lines = text.split(/\r?\n/);
1179
+ const patterns = (() => {
1180
+ switch (kind) {
1181
+ case "package.json":
1182
+ return [/^\s*"node"\s*:/, /^\s*"engines"\s*:/];
1183
+ case "go.mod":
1184
+ return [/^\s*go\s+\d/];
1185
+ case "pyproject.toml":
1186
+ return [/^\s*(?:requires-python|python)\s*=/];
1187
+ case "pom.xml":
1188
+ return [/<(?:java\.version|maven\.compiler\.(?:source|target|release))>/];
1189
+ case "build.gradle":
1190
+ return [/(?:sourceCompatibility|targetCompatibility|JavaVersion)/];
1191
+ case "csproj":
1192
+ return [/<TargetFrameworks?>/i];
1193
+ case "Dockerfile":
1194
+ return [/^\s*FROM\s+/i];
1195
+ default:
1196
+ return [];
1197
+ }
1198
+ })();
1199
+ for (const re of patterns) {
1200
+ for (let i = 0; i < lines.length; i++) {
1201
+ if (re.test(lines[i])) return i;
1202
+ }
1203
+ }
1204
+ return -1;
1205
+ }
1206
+ function endOfLine(text, line) {
1207
+ const lines = text.split(/\r?\n/);
1208
+ return (lines[line] ?? "").length;
1209
+ }
1210
+
1211
+ // src/lsp/scan-cache.ts
1212
+ var CACHE_VERSION = "vg-lsp-scan-cache/2";
1213
+ function cachePath(root) {
1214
+ return path3.join(cacheDir(root), "lsp-scan.json");
1215
+ }
1216
+ function manifestHash(root) {
1217
+ const files = [];
1218
+ const walk = (dir) => {
1219
+ let entries;
1220
+ try {
1221
+ entries = fs4.readdirSync(dir, { withFileTypes: true });
1222
+ } catch {
1223
+ return;
1224
+ }
1225
+ for (const entry of entries) {
1226
+ const abs = path3.join(dir, entry.name);
1227
+ if (entry.isDirectory()) {
1228
+ if (isSkippedDirName(entry.name)) continue;
1229
+ walk(abs);
1230
+ continue;
1231
+ }
1232
+ if (!entry.isFile()) continue;
1233
+ const base = entry.name.toLowerCase();
1234
+ if (!isManifest(abs) && !SKIP_FILES.has(base)) continue;
1235
+ try {
1236
+ const rel = path3.relative(root, abs).split(path3.sep).join("/");
1237
+ files.push({ rel, hash: hashBytes(fs4.readFileSync(abs)) });
1238
+ } catch {
1239
+ }
1240
+ }
1241
+ };
1242
+ walk(root);
1243
+ files.sort((a, b) => a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0);
1244
+ return hashString(canonicalize(files));
1245
+ }
1246
+ function isDependencyFile(root, filePath) {
1247
+ const rel = path3.relative(path3.resolve(root), path3.resolve(filePath));
1248
+ if (rel === "" || rel.startsWith("..") || path3.isAbsolute(rel)) return false;
1249
+ const parts = rel.split(path3.sep);
1250
+ if (parts.slice(0, -1).some((dir) => isSkippedDirName(dir))) return false;
1251
+ const base = parts[parts.length - 1].toLowerCase();
1252
+ return isManifest(filePath) || SKIP_FILES.has(base);
1253
+ }
1254
+ function loadScanCache(root, key) {
1255
+ try {
1256
+ const raw = JSON.parse(fs4.readFileSync(cachePath(root), "utf8"));
1257
+ if (raw.version === CACHE_VERSION && raw.manifestHash === key.manifestHash && raw.toolVersion === key.toolVersion && raw.offline === key.offline && raw.artifact) {
1258
+ return { manifestHash: raw.manifestHash, artifact: raw.artifact };
1259
+ }
1260
+ } catch {
1261
+ }
1262
+ return null;
1263
+ }
1264
+ function writeScanCache(root, key, artifact) {
1265
+ try {
1266
+ fs4.mkdirSync(cacheDir(root), { recursive: true });
1267
+ fs4.writeFileSync(
1268
+ cachePath(root),
1269
+ stableStringify(
1270
+ { version: CACHE_VERSION, manifestHash: key.manifestHash, toolVersion: key.toolVersion, offline: key.offline, artifact },
1271
+ 0
1272
+ )
1273
+ );
1274
+ } catch {
1275
+ }
1276
+ }
1277
+
1278
+ // src/runtime/vgd/freshness.ts
1279
+ var DEFAULT_SETTLE_MS = 600;
1280
+ var DEFAULT_MIN_REBUILD_GAP_MS = 5e3;
1281
+ var DEFAULT_REBUILD_TIMEOUT_MS = 10 * 6e4;
1282
+ var FreshnessSupervisor = class {
1283
+ watched = /* @__PURE__ */ new Map();
1284
+ /** Told when a manifest or lockfile is written — the shared dependency cache. */
1285
+ manifestListener;
1286
+ log;
1287
+ reload;
1288
+ select;
1289
+ settleMs;
1290
+ minRebuildGapMs;
1291
+ rebuildTimeoutMs;
1292
+ watchImpl;
1293
+ rebuildImpl;
1294
+ detectRef;
1295
+ now;
1296
+ constructor(options) {
1297
+ this.log = options.log ?? (() => {
1298
+ });
1299
+ this.reload = options.reload;
1300
+ this.select = options.select;
1301
+ this.settleMs = options.settleMs ?? DEFAULT_SETTLE_MS;
1302
+ this.minRebuildGapMs = options.minRebuildGapMs ?? DEFAULT_MIN_REBUILD_GAP_MS;
1303
+ this.rebuildTimeoutMs = options.rebuildTimeoutMs ?? DEFAULT_REBUILD_TIMEOUT_MS;
1304
+ this.watchImpl = options.watch ?? defaultWatch;
1305
+ this.rebuildImpl = options.rebuild ?? ((root) => spawnRebuild(root, this.rebuildTimeoutMs));
1306
+ this.detectRef = options.detectRef ?? defaultDetectRef;
1307
+ this.now = options.now ?? (() => Date.now());
1308
+ }
1309
+ /**
1310
+ * Attach something that must forget derived state when a manifest or
1311
+ * lockfile changes. The watcher is already looking at exactly these files,
1312
+ * so a cache hung off this has no staleness window — unlike a TTL.
1313
+ */
1314
+ setManifestListener(listener) {
1315
+ this.manifestListener = listener;
1316
+ }
1317
+ list() {
1318
+ return [...this.watched.values()].map((w) => ({
1319
+ repositoryId: w.repositoryId,
1320
+ root: w.root,
1321
+ gitRef: w.gitRef,
1322
+ watching: !!w.source,
1323
+ pending: w.pending.size,
1324
+ building: w.building,
1325
+ lastRebuildAt: w.lastRebuildAt || void 0
1326
+ })).sort((a, b) => a.repositoryId.localeCompare(b.repositoryId));
1327
+ }
1328
+ /**
1329
+ * Start watching a repository. Idempotent: re-registering the same root only
1330
+ * updates the ref it believes is current.
1331
+ */
1332
+ start(repositoryId, root, gitRef) {
1333
+ const existing = this.watched.get(repositoryId);
1334
+ if (existing) {
1335
+ existing.gitRef = gitRef;
1336
+ return;
1337
+ }
1338
+ const entry = {
1339
+ repositoryId,
1340
+ root,
1341
+ gitRef,
1342
+ source: null,
1343
+ head: null,
1344
+ timer: null,
1345
+ pending: /* @__PURE__ */ new Set(),
1346
+ building: false,
1347
+ again: false,
1348
+ lastRebuildAt: 0
1349
+ };
1350
+ this.watched.set(repositoryId, entry);
1351
+ entry.source = this.watchImpl(root, (filename) => {
1352
+ if (!isRelevantChange(filename)) return;
1353
+ entry.pending.add(filename);
1354
+ const manifest = isDependencyFile(root, path3.join(root, filename));
1355
+ if (manifest) this.manifestListener?.(entry.repositoryId);
1356
+ this.schedule(entry, manifest ? "manifest" : "source");
1357
+ });
1358
+ const gitDir = path3.join(root, ".git");
1359
+ if (fs4.existsSync(gitDir)) {
1360
+ entry.head = this.watchImpl(gitDir, (filename) => {
1361
+ if (path3.basename(filename) !== "HEAD") return;
1362
+ this.onHeadChanged(entry);
1363
+ });
1364
+ }
1365
+ this.log(
1366
+ `freshness: watching ${repositoryId} @${gitRef} (${entry.source ? "source" : "no source watcher"}${entry.head ? " + HEAD" : ""})`
1367
+ );
1368
+ }
1369
+ /** Stop watching one repository — its slot is gone or was unregistered. */
1370
+ stop(repositoryId) {
1371
+ const entry = this.watched.get(repositoryId);
1372
+ if (!entry) return;
1373
+ this.watched.delete(repositoryId);
1374
+ if (entry.timer) clearTimeout(entry.timer);
1375
+ entry.source?.close();
1376
+ entry.head?.close();
1377
+ this.log(`freshness: stopped watching ${repositoryId}`);
1378
+ }
1379
+ /** Daemon shutdown. */
1380
+ stopAll() {
1381
+ for (const id of [...this.watched.keys()]) this.stop(id);
1382
+ }
1383
+ /**
1384
+ * A branch switch: re-select first, and rebuild only when that ref has no
1385
+ * usable snapshot. Checking out a branch you were on ten minutes ago should
1386
+ * be instant, not a full rebuild.
1387
+ */
1388
+ onHeadChanged(entry) {
1389
+ const ref = this.detectRef(entry.root);
1390
+ if (!ref || ref === entry.gitRef) return;
1391
+ const from = entry.gitRef;
1392
+ entry.gitRef = ref;
1393
+ entry.pending.clear();
1394
+ this.log(`freshness: ${entry.repositoryId} branch ${from} \u2192 ${ref}`);
1395
+ this.select(entry.repositoryId, ref);
1396
+ void this.reload(entry.repositoryId, entry.root, ref).then((nodes) => {
1397
+ if (nodes != null) {
1398
+ this.log(`freshness: ${entry.repositoryId}@${ref} slot warm from disk \u2014 ${nodes} nodes`);
1399
+ } else {
1400
+ this.log(`freshness: ${entry.repositoryId}@${ref} has no snapshot yet \u2014 rebuilding`);
1401
+ this.schedule(entry, "branch");
1402
+ }
1403
+ });
1404
+ }
1405
+ schedule(entry, reason) {
1406
+ if (entry.building) {
1407
+ entry.again = true;
1408
+ return;
1409
+ }
1410
+ if (entry.timer) clearTimeout(entry.timer);
1411
+ const sinceLast = this.now() - entry.lastRebuildAt;
1412
+ const wait = Math.max(this.settleMs, entry.lastRebuildAt ? this.minRebuildGapMs - sinceLast : 0);
1413
+ entry.timer = setTimeout(() => {
1414
+ entry.timer = null;
1415
+ void this.rebuild(entry, reason);
1416
+ }, wait);
1417
+ entry.timer.unref?.();
1418
+ }
1419
+ async rebuild(entry, reason) {
1420
+ if (entry.building) {
1421
+ entry.again = true;
1422
+ return;
1423
+ }
1424
+ entry.building = true;
1425
+ const changed = entry.pending.size;
1426
+ const names = [...entry.pending].sort().slice(0, 3).join(", ");
1427
+ entry.pending.clear();
1428
+ const started = this.now();
1429
+ this.log(
1430
+ `freshness: rebuilding ${entry.repositoryId}@${entry.gitRef} (${reason}${changed ? `, ${changed} file(s): ${names}` : ""})\u2026`
1431
+ );
1432
+ try {
1433
+ const result = await this.rebuildImpl(entry.root);
1434
+ entry.lastRebuildAt = this.now();
1435
+ if (!result.ok) {
1436
+ this.log(`freshness: rebuild failed for ${entry.repositoryId} \u2014 ${result.error ?? "unknown"}; slot keeps its previous map`);
1437
+ return;
1438
+ }
1439
+ const nodes = await this.reload(entry.repositoryId, entry.root, entry.gitRef);
1440
+ this.log(
1441
+ 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`
1442
+ );
1443
+ } catch (err) {
1444
+ this.log(`freshness: rebuild errored for ${entry.repositoryId} \u2014 ${err instanceof Error ? err.message : String(err)}`);
1445
+ } finally {
1446
+ entry.building = false;
1447
+ if (entry.again) {
1448
+ entry.again = false;
1449
+ this.schedule(entry, "source");
1450
+ }
1451
+ }
1452
+ }
1453
+ };
1454
+ function defaultDetectRef(root) {
1455
+ clearDetectGitRefCache(root);
1456
+ return detectGitRef(root).ref;
1457
+ }
1458
+ function defaultWatch(dir, onChange) {
1459
+ try {
1460
+ const w = fs4.watch(dir, { recursive: true }, (_event, filename) => {
1461
+ if (filename) onChange(String(filename));
1462
+ });
1463
+ w.unref?.();
1464
+ return w;
1465
+ } catch {
1466
+ return null;
1467
+ }
1468
+ }
1469
+ function spawnRebuild(root, timeoutMs) {
1470
+ return new Promise((resolve3) => {
1471
+ let child;
1472
+ try {
1473
+ child = spawn(process.execPath, [process.argv[1] ?? "", "build", "-C", root, "--json", "--no-daemon", "--no-warm"], {
1474
+ stdio: "ignore",
1475
+ env: { ...process.env, NODE_OPTIONS: "" }
1476
+ });
1477
+ } catch (e) {
1478
+ resolve3({ ok: false, error: e instanceof Error ? e.message : String(e) });
1479
+ return;
1480
+ }
1481
+ const timer = setTimeout(() => {
1482
+ try {
1483
+ child.kill();
1484
+ } catch {
1485
+ }
1486
+ resolve3({ ok: false, error: `rebuild exceeded ${Math.round(timeoutMs / 1e3)}s` });
1487
+ }, timeoutMs);
1488
+ timer.unref?.();
1489
+ child.on("error", (err) => {
1490
+ clearTimeout(timer);
1491
+ resolve3({ ok: false, error: err.message });
1492
+ });
1493
+ child.on("exit", (code) => {
1494
+ clearTimeout(timer);
1495
+ resolve3(code === 0 ? { ok: true } : { ok: false, error: `vg build exited ${code ?? "null"}` });
1496
+ });
1497
+ });
1498
+ }
1499
+
1500
+ // src/runtime/vgd/dep-cache.ts
1501
+ var DepContextCache = class {
1502
+ entries = /* @__PURE__ */ new Map();
1503
+ log;
1504
+ now;
1505
+ computeHash;
1506
+ computeInventory;
1507
+ constructor(options = {}) {
1508
+ this.log = options.log ?? (() => {
1509
+ });
1510
+ this.now = options.now ?? (() => Date.now());
1511
+ this.computeHash = options.computeHash ?? manifestHash;
1512
+ this.computeInventory = options.computeInventory ?? inventory;
1513
+ }
1514
+ /** Compute or reuse the context for a repository root. Never throws. */
1515
+ get(repositoryId, root) {
1516
+ const hit = this.entries.get(repositoryId);
1517
+ if (hit) return hit;
1518
+ const started = this.now();
1519
+ try {
1520
+ const context = {
1521
+ manifestHash: this.computeHash(root),
1522
+ inventory: this.computeInventory(root),
1523
+ builtAt: this.now()
1524
+ };
1525
+ this.entries.set(repositoryId, context);
1526
+ this.log(
1527
+ `deps: context for ${repositoryId} built in ${context.builtAt - started}ms \u2014 ${context.inventory.records.length} dependency(ies)`
1528
+ );
1529
+ return context;
1530
+ } catch (err) {
1531
+ this.log(`deps: could not build context for ${repositoryId} \u2014 ${err instanceof Error ? err.message : String(err)}`);
1532
+ return null;
1533
+ }
1534
+ }
1535
+ /**
1536
+ * Forget a repository's context. Called from the freshness watcher when a
1537
+ * manifest or lockfile is written, so the next reader recomputes.
1538
+ */
1539
+ invalidate(repositoryId) {
1540
+ if (this.entries.delete(repositoryId)) this.log(`deps: context for ${repositoryId} invalidated by a manifest change`);
1541
+ }
1542
+ clear() {
1543
+ this.entries.clear();
1544
+ }
1545
+ size() {
1546
+ return this.entries.size;
1547
+ }
1548
+ };
1549
+
918
1550
  // src/runtime/vgd/server.ts
919
1551
  async function startVgdServer(options = {}) {
920
1552
  const socketPath = options.socketPath ?? vgdSocketPath();
@@ -925,23 +1557,60 @@ async function startVgdServer(options = {}) {
925
1557
  });
926
1558
  const embedBroker = options.embedBroker ?? new EmbedBroker({ log });
927
1559
  embedBroker.setGraphProvider((repositoryId, gitRef) => registry.graphs.get(repositoryId, gitRef)?.graph);
1560
+ const freshness = options.freshness ?? new FreshnessSupervisor({
1561
+ log,
1562
+ reload: (repositoryId, root, gitRef) => {
1563
+ const graph = loadGraph(root);
1564
+ if (!graph) return Promise.resolve(null);
1565
+ registry.putGraph(repositoryId, gitRef, graph);
1566
+ return Promise.resolve(graph.nodes?.length ?? 0);
1567
+ },
1568
+ select: (repositoryId, gitRef) => registry.selectGitRef(repositoryId, gitRef)
1569
+ });
1570
+ const watchEnabled = options.watch !== false;
1571
+ const depCache = options.depCache ?? new DepContextCache({ log });
1572
+ freshness.setManifestListener((repositoryId) => depCache.invalidate(repositoryId));
1573
+ embedBroker.setRootProvider((repositoryId) => registry.getById(repositoryId)?.root);
928
1574
  registry.setSlotListener(embedBroker);
929
1575
  const onShutdownRequest = options.onShutdownRequest;
930
1576
  const now = options.now ?? (() => /* @__PURE__ */ new Date());
931
1577
  const pid = options.pid ?? process.pid;
932
1578
  const startedAt = Date.now();
933
1579
  if (!socketPath.startsWith("\\\\.\\pipe")) {
934
- fs2.mkdirSync(path.dirname(socketPath), { recursive: true });
1580
+ fs4.mkdirSync(path3.dirname(socketPath), { recursive: true });
935
1581
  try {
936
- fs2.unlinkSync(socketPath);
1582
+ fs4.unlinkSync(socketPath);
937
1583
  } catch {
938
1584
  }
939
1585
  } else {
940
- fs2.mkdirSync(path.dirname(pidPath), { recursive: true });
941
- }
1586
+ fs4.mkdirSync(path3.dirname(pidPath), { recursive: true });
1587
+ }
1588
+ const subscribers = /* @__PURE__ */ new Map();
1589
+ const broadcastSlot = (repositoryId, gitRef, graph) => {
1590
+ if (!subscribers.size) return;
1591
+ const frame = JSON.stringify({
1592
+ ok: true,
1593
+ event: "slot-changed",
1594
+ repositoryId,
1595
+ gitRef,
1596
+ nodeCount: graph.nodes?.length ?? 0,
1597
+ corpusHash: graph.provenance?.corpusHash ?? null
1598
+ }) + "\n";
1599
+ for (const [socket, filter] of subscribers) {
1600
+ if (filter.repositoryId && filter.repositoryId !== repositoryId) continue;
1601
+ try {
1602
+ socket.write(frame);
1603
+ } catch {
1604
+ subscribers.delete(socket);
1605
+ }
1606
+ }
1607
+ };
1608
+ registry.setPublishListener(broadcastSlot);
942
1609
  const server = net.createServer((socket) => {
943
1610
  let buffer = "";
944
1611
  socket.setEncoding("utf8");
1612
+ socket.on("close", () => subscribers.delete(socket));
1613
+ socket.on("error", () => subscribers.delete(socket));
945
1614
  socket.on("data", (chunk) => {
946
1615
  buffer += chunk;
947
1616
  let nl;
@@ -950,7 +1619,23 @@ async function startVgdServer(options = {}) {
950
1619
  buffer = buffer.slice(nl + 1);
951
1620
  if (!line) continue;
952
1621
  void Promise.resolve(
953
- handleLine(line, { registry, hostBroker, embedBroker, now, pid, startedAt, socketPath, onShutdownRequest, log })
1622
+ handleLine(line, {
1623
+ registry,
1624
+ hostBroker,
1625
+ embedBroker,
1626
+ freshness: watchEnabled ? freshness : void 0,
1627
+ depCache,
1628
+ subscribe: (repositoryId) => {
1629
+ subscribers.set(socket, { repositoryId });
1630
+ log(`watch-slots: subscriber attached${repositoryId ? ` for ${repositoryId}` : " (all repositories)"}`);
1631
+ },
1632
+ now,
1633
+ pid,
1634
+ startedAt,
1635
+ socketPath,
1636
+ onShutdownRequest,
1637
+ log
1638
+ })
954
1639
  ).then(
955
1640
  (response) => {
956
1641
  socket.write(JSON.stringify(response) + "\n");
@@ -959,16 +1644,16 @@ async function startVgdServer(options = {}) {
959
1644
  }
960
1645
  });
961
1646
  });
962
- await new Promise((resolve2, reject) => {
1647
+ await new Promise((resolve3, reject) => {
963
1648
  server.once("error", reject);
964
1649
  server.listen(socketPath, () => {
965
1650
  server.off("error", reject);
966
- resolve2();
1651
+ resolve3();
967
1652
  });
968
1653
  });
969
1654
  try {
970
- fs2.mkdirSync(path.dirname(pidPath), { recursive: true });
971
- fs2.writeFileSync(pidPath, `${pid}
1655
+ fs4.mkdirSync(path3.dirname(pidPath), { recursive: true });
1656
+ fs4.writeFileSync(pidPath, `${pid}
972
1657
  `, "utf8");
973
1658
  } catch {
974
1659
  }
@@ -978,14 +1663,17 @@ async function startVgdServer(options = {}) {
978
1663
  startedAt,
979
1664
  async close() {
980
1665
  embedBroker.stop();
981
- await new Promise((resolve2) => server.close(() => resolve2()));
1666
+ freshness.stopAll();
1667
+ depCache.clear();
1668
+ subscribers.clear();
1669
+ await new Promise((resolve3) => server.close(() => resolve3()));
982
1670
  try {
983
- fs2.unlinkSync(pidPath);
1671
+ fs4.unlinkSync(pidPath);
984
1672
  } catch {
985
1673
  }
986
1674
  if (!socketPath.startsWith("\\\\.\\pipe")) {
987
1675
  try {
988
- fs2.unlinkSync(socketPath);
1676
+ fs4.unlinkSync(socketPath);
989
1677
  } catch {
990
1678
  }
991
1679
  }
@@ -1007,7 +1695,8 @@ async function handleLine(line, ctx) {
1007
1695
  graphSlots: ctx.registry.graphs.size(),
1008
1696
  version: VGD_PROTOCOL_VERSION,
1009
1697
  socketPath: ctx.socketPath,
1010
- cliVersion: VERSION
1698
+ cliVersion: VERSION,
1699
+ freshness: ctx.freshness?.list() ?? []
1011
1700
  };
1012
1701
  case "shutdown": {
1013
1702
  if (!ctx.onShutdownRequest) {
@@ -1041,7 +1730,20 @@ async function handleLine(line, ctx) {
1041
1730
  if (!slot) {
1042
1731
  return { ok: false, error: `no graph slot for ${req.repositoryId}@${gitRef} \u2014 publish the map first`, code: "no_slot" };
1043
1732
  }
1044
- const idx = await ctx.embedBroker.ensureIndex(req.repositoryId, gitRef, slot.graph);
1733
+ if (req.wait === false) {
1734
+ void ctx.embedBroker.ensureIndex(req.repositoryId, gitRef, slot.graph, { waitForWriter: true });
1735
+ const kicked = ctx.embedBroker.slot(req.repositoryId, gitRef);
1736
+ return {
1737
+ ok: true,
1738
+ indexed: true,
1739
+ repositoryId: req.repositoryId,
1740
+ gitRef,
1741
+ state: kicked?.state ?? "building",
1742
+ vectors: kicked?.vectors.size ?? 0,
1743
+ buildMs: kicked?.buildMs
1744
+ };
1745
+ }
1746
+ const idx = await ctx.embedBroker.ensureIndex(req.repositoryId, gitRef, slot.graph, { waitForWriter: true });
1045
1747
  return {
1046
1748
  ok: true,
1047
1749
  indexed: true,
@@ -1052,6 +1754,76 @@ async function handleLine(line, ctx) {
1052
1754
  buildMs: idx.buildMs
1053
1755
  };
1054
1756
  }
1757
+ case "embed-rank": {
1758
+ const gitRef = req.gitRef ?? ctx.registry.graphs.selectedRef(req.repositoryId);
1759
+ if (!gitRef) return { ok: false, error: "no current gitRef for this repository", code: "no_slot" };
1760
+ const slot = ctx.registry.graphs.get(req.repositoryId, gitRef);
1761
+ if (!slot) {
1762
+ return { ok: false, error: `no graph slot for ${req.repositoryId}@${gitRef} \u2014 publish the map first`, code: "no_slot" };
1763
+ }
1764
+ const existing = ctx.embedBroker.slot(req.repositoryId, gitRef);
1765
+ if (existing && existing.vectors.size > 0) {
1766
+ if (existing.state !== "ready") {
1767
+ void ctx.embedBroker.ensureIndex(req.repositoryId, gitRef, slot.graph);
1768
+ }
1769
+ } else {
1770
+ void ctx.embedBroker.ensureIndex(req.repositoryId, gitRef, slot.graph, { waitForWriter: true });
1771
+ const warming = ctx.embedBroker.slot(req.repositoryId, gitRef);
1772
+ return {
1773
+ ok: false,
1774
+ error: "semantic index is warming \u2014 retry shortly",
1775
+ code: "semantic_warming",
1776
+ state: warming?.state ?? "building",
1777
+ vectors: warming?.vectors.size ?? 0,
1778
+ pending: warming?.pending,
1779
+ nodeCount: warming?.nodeCount
1780
+ };
1781
+ }
1782
+ const started = Date.now();
1783
+ const result = await ctx.embedBroker.rank(req.repositoryId, gitRef, req.text, req.limit);
1784
+ if (!result) {
1785
+ return { ok: false, error: "semantic unavailable \u2014 rank lexically", code: "semantic_unavailable" };
1786
+ }
1787
+ return {
1788
+ ok: true,
1789
+ ranked: result.ranked,
1790
+ repositoryId: req.repositoryId,
1791
+ gitRef,
1792
+ state: result.state,
1793
+ vectors: result.vectors,
1794
+ model: ctx.embedBroker.status().model,
1795
+ rankMs: Date.now() - started
1796
+ };
1797
+ }
1798
+ case "watch-slots": {
1799
+ if (!ctx.subscribe) {
1800
+ return { ok: false, error: "this listener does not support subscriptions", code: "unsupported" };
1801
+ }
1802
+ ctx.subscribe(req.repositoryId);
1803
+ return { ok: true, watching: true, repositoryId: req.repositoryId };
1804
+ }
1805
+ case "dep-context": {
1806
+ const record = ctx.registry.getById(req.repositoryId);
1807
+ if (!record) {
1808
+ return { ok: false, error: `unknown repository ${req.repositoryId} \u2014 register it first`, code: "not_found" };
1809
+ }
1810
+ const context = ctx.depCache.get(req.repositoryId, record.root);
1811
+ if (!context) {
1812
+ return { ok: false, error: "could not read this repository\u2019s manifests", code: "no_deps" };
1813
+ }
1814
+ return {
1815
+ ok: true,
1816
+ repositoryId: req.repositoryId,
1817
+ manifestHash: context.manifestHash,
1818
+ dependencies: context.inventory.records.map((r) => ({
1819
+ name: r.name,
1820
+ ecosystem: r.ecosystem,
1821
+ declared: r.declared,
1822
+ installed: r.installed
1823
+ })),
1824
+ builtAt: context.builtAt
1825
+ };
1826
+ }
1055
1827
  case "host-status":
1056
1828
  return { ok: true, host: ctx.hostBroker.status() };
1057
1829
  case "host-load": {
@@ -1100,8 +1872,11 @@ async function handleLine(line, ctx) {
1100
1872
  }
1101
1873
  return { ok: true, workspace };
1102
1874
  }
1103
- case "unregister":
1875
+ case "unregister": {
1876
+ const record = ctx.registry.get(req.root);
1877
+ if (record) ctx.freshness?.stop(record.id);
1104
1878
  return { ok: true, removed: ctx.registry.unregister(req.root) };
1879
+ }
1105
1880
  case "register-federation": {
1106
1881
  const workspaces = ctx.registry.registerFederation(req.members, ctx.now);
1107
1882
  return { ok: true, workspaces, federation: true };
@@ -1123,6 +1898,7 @@ async function handleLine(line, ctx) {
1123
1898
  const graph = req.graph;
1124
1899
  ctx.log(`put-graph: ${req.repositoryId}@${req.gitRef} \xB7 ${graph.nodes?.length ?? 0} nodes`);
1125
1900
  ctx.registry.putGraph(req.repositoryId, req.gitRef, graph);
1901
+ startWatching(ctx, req.repositoryId, req.gitRef);
1126
1902
  return {
1127
1903
  ok: true,
1128
1904
  stored: true,
@@ -1147,6 +1923,7 @@ async function handleLine(line, ctx) {
1147
1923
  };
1148
1924
  }
1149
1925
  ctx.registry.putGraph(record.id, gitRef, graph);
1926
+ startWatching(ctx, record.id, gitRef);
1150
1927
  return {
1151
1928
  ok: true,
1152
1929
  stored: true,
@@ -1160,13 +1937,27 @@ async function handleLine(line, ctx) {
1160
1937
  if (!resolved) {
1161
1938
  return { ok: false, error: "no ActiveGraph loaded for repository \u2014 put-graph first", code: "no_graph" };
1162
1939
  }
1163
- const result = queryGraph(resolved.graph, req.query, { limit: req.limit ?? 12, budget: 2e3 });
1940
+ const limit = req.limit ?? 12;
1941
+ let mode = "lexical";
1942
+ let result = queryGraph(resolved.graph, req.query, { limit, budget: 2e3 });
1943
+ if (req.semantic) {
1944
+ const ranking = await ctx.embedBroker.rank(req.repositoryId, resolved.gitRef, req.query);
1945
+ if (ranking && ranking.ranked.length) {
1946
+ result = await queryGraphSemantic(resolved.graph, req.query, {
1947
+ limit,
1948
+ budget: 2e3,
1949
+ semanticRanked: ranking.ranked
1950
+ });
1951
+ mode = `semantic (vgd${ctx.embedBroker.status().model ? `, ${ctx.embedBroker.status().model}` : ""})`;
1952
+ }
1953
+ }
1164
1954
  return {
1165
1955
  ok: true,
1166
1956
  query: req.query,
1167
1957
  repositoryId: req.repositoryId,
1168
1958
  gitRef: resolved.gitRef,
1169
- matches: result.matches.slice(0, req.limit ?? 12).map((m) => ({
1959
+ mode,
1960
+ matches: result.matches.slice(0, limit).map((m) => ({
1170
1961
  id: m.node.id,
1171
1962
  qualifiedName: m.node.qualifiedName,
1172
1963
  kind: m.node.kind,
@@ -1229,6 +2020,10 @@ async function handleLine(line, ctx) {
1229
2020
  }
1230
2021
  }
1231
2022
  }
2023
+ function startWatching(ctx, repositoryId, gitRef) {
2024
+ const record = ctx.registry.getById(repositoryId);
2025
+ if (record) ctx.freshness?.start(repositoryId, record.root, gitRef);
2026
+ }
1232
2027
  function resolveSymbol(graph, symbol) {
1233
2028
  if (graph.nodes.some((n) => n.id === symbol)) return { id: symbol };
1234
2029
  const hit = resolveOne(graph, symbol);
@@ -1315,6 +2110,6 @@ var BranchGraphSession = class {
1315
2110
  }
1316
2111
  };
1317
2112
 
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
2113
+ export { ActiveGraphCache, BranchGraphSession, DEFAULT_ACTIVE_GRAPH_IDLE_MS, VGD_PROTOCOL_VERSION, WorkspaceRegistry, endOfLine, findPackageLine, findRuntimeLine, isDependencyFile, isManifest, loadScanCache, manifestHash, manifestKind, parseRequest, startVgdServer, writeScanCache };
2114
+ //# sourceMappingURL=chunk-VQ5KMHW6.js.map
2115
+ //# sourceMappingURL=chunk-VQ5KMHW6.js.map