@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.
- package/dist/activity-MF2GNYAI.js +5 -0
- package/dist/activity-MF2GNYAI.js.map +1 -0
- package/dist/agent-X6VYCE63.js +30 -0
- package/dist/{agent-UQC7QN77.js.map → agent-X6VYCE63.js.map} +1 -1
- package/dist/{approvals-ASPJQUZK.js → approvals-TPQFQSWG.js} +5 -5
- package/dist/{approvals-ASPJQUZK.js.map → approvals-TPQFQSWG.js.map} +1 -1
- package/dist/attach-G5JUIWIT.js +10 -0
- package/dist/attach-G5JUIWIT.js.map +1 -0
- package/dist/baseline-DXI64VLW.js +9 -0
- package/dist/{baseline-FIV6PR2M.js.map → baseline-DXI64VLW.js.map} +1 -1
- package/dist/{chunk-AXZZ5CCG.js → chunk-2C33WR5O.js} +38 -19
- package/dist/chunk-2C33WR5O.js.map +1 -0
- package/dist/chunk-4NTNMT6T.js +6 -0
- package/dist/{chunk-PRG2ULPK.js.map → chunk-4NTNMT6T.js.map} +1 -1
- package/dist/chunk-5IQBIXHS.js +23 -0
- package/dist/chunk-5IQBIXHS.js.map +1 -0
- package/dist/{chunk-6K3XK7C5.js → chunk-6ABMV74F.js} +6 -3
- package/dist/chunk-6ABMV74F.js.map +1 -0
- package/dist/chunk-6BEBJCK4.js +467 -0
- package/dist/chunk-6BEBJCK4.js.map +1 -0
- package/dist/{chunk-E2FAQREZ.js → chunk-6PFHDHOL.js} +174 -401
- package/dist/chunk-6PFHDHOL.js.map +1 -0
- package/dist/chunk-6WRG7VDO.js +118 -0
- package/dist/chunk-6WRG7VDO.js.map +1 -0
- package/dist/{chunk-3NJUOIZQ.js → chunk-AEJYA6HZ.js} +5 -5
- package/dist/{chunk-3NJUOIZQ.js.map → chunk-AEJYA6HZ.js.map} +1 -1
- package/dist/{chunk-O2DL2Q55.js → chunk-B4EXD5LK.js} +5 -5
- package/dist/{chunk-O2DL2Q55.js.map → chunk-B4EXD5LK.js.map} +1 -1
- package/dist/{chunk-EXV4QSAD.js → chunk-BMOFJIMG.js} +3 -3
- package/dist/{chunk-EXV4QSAD.js.map → chunk-BMOFJIMG.js.map} +1 -1
- package/dist/chunk-DBX3DL27.js +26 -0
- package/dist/chunk-DBX3DL27.js.map +1 -0
- package/dist/{chunk-KNGAZDGF.js → chunk-E3O5UKNA.js} +151 -42
- package/dist/chunk-E3O5UKNA.js.map +1 -0
- package/dist/{chunk-RBIRXQR6.js → chunk-E53WBVUQ.js} +7 -14
- package/dist/chunk-E53WBVUQ.js.map +1 -0
- package/dist/{chunk-C3LMWZKF.js → chunk-FFXGWFAE.js} +4 -4
- package/dist/{chunk-C3LMWZKF.js.map → chunk-FFXGWFAE.js.map} +1 -1
- package/dist/chunk-H5Q4JUY4.js +47 -0
- package/dist/chunk-H5Q4JUY4.js.map +1 -0
- package/dist/{chunk-LHKQGVRF.js → chunk-HF6R73UC.js} +100 -11
- package/dist/chunk-HF6R73UC.js.map +1 -0
- package/dist/{chunk-K6DXETMC.js → chunk-JGA63RGF.js} +13 -12
- package/dist/chunk-JGA63RGF.js.map +1 -0
- package/dist/{chunk-3FMRFECC.js → chunk-KU7FI5EV.js} +3 -3
- package/dist/{chunk-3FMRFECC.js.map → chunk-KU7FI5EV.js.map} +1 -1
- package/dist/{chunk-5OL2TSHK.js → chunk-L2OS4WHT.js} +4 -4
- package/dist/{chunk-5OL2TSHK.js.map → chunk-L2OS4WHT.js.map} +1 -1
- package/dist/chunk-LJSJX6U4.js +38 -0
- package/dist/chunk-LJSJX6U4.js.map +1 -0
- package/dist/{chunk-Q4G2BWE3.js → chunk-MRZHNUN7.js} +12 -6
- package/dist/chunk-MRZHNUN7.js.map +1 -0
- package/dist/chunk-NDNNOAS6.js +16 -0
- package/dist/chunk-NDNNOAS6.js.map +1 -0
- package/dist/chunk-NJQZRM55.js +100 -0
- package/dist/chunk-NJQZRM55.js.map +1 -0
- package/dist/{chunk-DVW3SVCW.js → chunk-NKLYDSB7.js} +29 -9
- package/dist/chunk-NKLYDSB7.js.map +1 -0
- package/dist/{chunk-TVDMNIUT.js → chunk-O5IJDMTB.js} +4 -4
- package/dist/{chunk-TVDMNIUT.js.map → chunk-O5IJDMTB.js.map} +1 -1
- package/dist/chunk-POOL355F.js +237 -0
- package/dist/chunk-POOL355F.js.map +1 -0
- package/dist/{chunk-XLXJC7H3.js → chunk-PRHH4DVO.js} +5 -118
- package/dist/chunk-PRHH4DVO.js.map +1 -0
- package/dist/{chunk-3H3SVJUY.js → chunk-RHKOUPWT.js} +107 -542
- package/dist/chunk-RHKOUPWT.js.map +1 -0
- package/dist/{chunk-WAKP7SK7.js → chunk-V2UGRUTL.js} +225 -48
- package/dist/chunk-V2UGRUTL.js.map +1 -0
- package/dist/{chunk-2FEB5KJS.js → chunk-V6WONSIK.js} +5 -4
- package/dist/chunk-V6WONSIK.js.map +1 -0
- package/dist/{chunk-WVMOGIPC.js → chunk-VMAIDM4K.js} +3 -3
- package/dist/{chunk-WVMOGIPC.js.map → chunk-VMAIDM4K.js.map} +1 -1
- package/dist/{chunk-GOGMS3ST.js → chunk-VQ5KMHW6.js} +860 -65
- package/dist/chunk-VQ5KMHW6.js.map +1 -0
- package/dist/{chunk-M7IO3DXH.js → chunk-X2MQ7UFL.js} +250 -30
- package/dist/chunk-X2MQ7UFL.js.map +1 -0
- package/dist/{chunk-OZ3IFXRD.js → chunk-YENM3HT7.js} +5 -4
- package/dist/chunk-YENM3HT7.js.map +1 -0
- package/dist/{chunk-KJT2R6WW.js → chunk-YF54UMOU.js} +3 -3
- package/dist/{chunk-KJT2R6WW.js.map → chunk-YF54UMOU.js.map} +1 -1
- package/dist/{chunk-Q3M6K3HY.js → chunk-ZUOBPKVK.js} +3 -3
- package/dist/{chunk-Q3M6K3HY.js.map → chunk-ZUOBPKVK.js.map} +1 -1
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +711 -463
- package/dist/cli.js.map +1 -1
- package/dist/client-UQIELEEZ.js +8 -0
- package/dist/client-UQIELEEZ.js.map +1 -0
- package/dist/daemon-GUMWMKCA.js +31 -0
- package/dist/{daemon-FPTRHO5N.js.map → daemon-GUMWMKCA.js.map} +1 -1
- package/dist/embed-worker-main.js +3 -3
- package/dist/ensure-map-FHDJTO7Y.js +41 -0
- package/dist/{ensure-map-342G2IV3.js.map → ensure-map-FHDJTO7Y.js.map} +1 -1
- package/dist/graph-backend-W2D3HRRD.js +16 -0
- package/dist/{graph-backend-RNWQBT3P.js.map → graph-backend-W2D3HRRD.js.map} +1 -1
- package/dist/index.d.ts +633 -4
- package/dist/index.js +26 -17
- package/dist/index.js.map +1 -1
- package/dist/{interactive-SR7TMF6M.js → interactive-2MK4TLRD.js} +42 -34
- package/dist/interactive-2MK4TLRD.js.map +1 -0
- package/dist/load-4R4HYMBB.js +11 -0
- package/dist/{load-AYFECIQB.js.map → load-4R4HYMBB.js.map} +1 -1
- package/dist/parse-worker.js +3 -2
- package/dist/parse-worker.js.map +1 -1
- package/dist/publish-7YHRTBRA.js +9 -0
- package/dist/publish-7YHRTBRA.js.map +1 -0
- package/dist/{run-outcome-YQQJG3LW.js → run-outcome-H5GVDDZJ.js} +5 -5
- package/dist/{run-outcome-YQQJG3LW.js.map → run-outcome-H5GVDDZJ.js.map} +1 -1
- package/dist/runtime-session-JBIHHH5N.js +27 -0
- package/dist/{runtime-session-CNJ4DAV5.js.map → runtime-session-JBIHHH5N.js.map} +1 -1
- package/dist/semantic-status-2JFLIQQJ.js +4 -0
- package/dist/semantic-status-2JFLIQQJ.js.map +1 -0
- package/dist/{session-H34HUV2Y.js → session-N5IU4K5Q.js} +8 -8
- package/dist/{session-H34HUV2Y.js.map → session-N5IU4K5Q.js.map} +1 -1
- package/dist/slot-subscription-25GOARW5.js +95 -0
- package/dist/slot-subscription-25GOARW5.js.map +1 -0
- package/dist/{stream-json-55YDNAFX.js → stream-json-4WLSUF6I.js} +18 -15
- package/dist/stream-json-4WLSUF6I.js.map +1 -0
- package/dist/version-DTEU3SQD.js +4 -0
- package/dist/{version-WSE3QOPK.js.map → version-DTEU3SQD.js.map} +1 -1
- package/dist/{vg-mcp-bridge-2CNLHNEL.js → vg-mcp-bridge-KDVYIMFX.js} +12 -11
- package/dist/vg-mcp-bridge-KDVYIMFX.js.map +1 -0
- package/dist/{vgd-URUZQORN.js → vgd-M7UOPWLB.js} +15 -10
- package/dist/vgd-M7UOPWLB.js.map +1 -0
- package/package.json +1 -1
- package/dist/agent-UQC7QN77.js +0 -27
- package/dist/baseline-FIV6PR2M.js +0 -9
- package/dist/chunk-2FEB5KJS.js.map +0 -1
- package/dist/chunk-3H3SVJUY.js.map +0 -1
- package/dist/chunk-6K3XK7C5.js.map +0 -1
- package/dist/chunk-AXZZ5CCG.js.map +0 -1
- package/dist/chunk-DVW3SVCW.js.map +0 -1
- package/dist/chunk-E2FAQREZ.js.map +0 -1
- package/dist/chunk-GOGMS3ST.js.map +0 -1
- package/dist/chunk-K6DXETMC.js.map +0 -1
- package/dist/chunk-KNGAZDGF.js.map +0 -1
- package/dist/chunk-LHKQGVRF.js.map +0 -1
- package/dist/chunk-M7IO3DXH.js.map +0 -1
- package/dist/chunk-OZ3IFXRD.js.map +0 -1
- package/dist/chunk-PRG2ULPK.js +0 -6
- package/dist/chunk-Q4G2BWE3.js.map +0 -1
- package/dist/chunk-RBIRXQR6.js.map +0 -1
- package/dist/chunk-WAKP7SK7.js.map +0 -1
- package/dist/chunk-XLXJC7H3.js.map +0 -1
- package/dist/daemon-FPTRHO5N.js +0 -26
- package/dist/ensure-map-342G2IV3.js +0 -33
- package/dist/graph-backend-RNWQBT3P.js +0 -15
- package/dist/interactive-SR7TMF6M.js.map +0 -1
- package/dist/load-AYFECIQB.js +0 -11
- package/dist/runtime-session-CNJ4DAV5.js +0 -22
- package/dist/stream-json-55YDNAFX.js.map +0 -1
- package/dist/version-WSE3QOPK.js +0 -4
- package/dist/vg-mcp-bridge-2CNLHNEL.js.map +0 -1
- 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 {
|
|
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-
|
|
5
|
-
import { loadGraph } from './chunk-
|
|
6
|
-
import {
|
|
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 {
|
|
9
|
-
import
|
|
10
|
-
import * as
|
|
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 =
|
|
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.
|
|
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(
|
|
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(
|
|
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 [
|
|
486
|
-
leases[
|
|
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
|
-
/**
|
|
601
|
-
|
|
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
|
-
|
|
665
|
-
|
|
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:
|
|
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.
|
|
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.
|
|
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
|
-
|
|
734
|
-
|
|
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.
|
|
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
|
-
|
|
741
|
-
|
|
742
|
-
const
|
|
743
|
-
|
|
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(
|
|
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.
|
|
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((
|
|
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:
|
|
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 =
|
|
906
|
-
const entry =
|
|
907
|
-
if (!
|
|
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
|
-
|
|
1580
|
+
fs4.mkdirSync(path3.dirname(socketPath), { recursive: true });
|
|
935
1581
|
try {
|
|
936
|
-
|
|
1582
|
+
fs4.unlinkSync(socketPath);
|
|
937
1583
|
} catch {
|
|
938
1584
|
}
|
|
939
1585
|
} else {
|
|
940
|
-
|
|
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, {
|
|
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((
|
|
1647
|
+
await new Promise((resolve3, reject) => {
|
|
963
1648
|
server.once("error", reject);
|
|
964
1649
|
server.listen(socketPath, () => {
|
|
965
1650
|
server.off("error", reject);
|
|
966
|
-
|
|
1651
|
+
resolve3();
|
|
967
1652
|
});
|
|
968
1653
|
});
|
|
969
1654
|
try {
|
|
970
|
-
|
|
971
|
-
|
|
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
|
-
|
|
1666
|
+
freshness.stopAll();
|
|
1667
|
+
depCache.clear();
|
|
1668
|
+
subscribers.clear();
|
|
1669
|
+
await new Promise((resolve3) => server.close(() => resolve3()));
|
|
982
1670
|
try {
|
|
983
|
-
|
|
1671
|
+
fs4.unlinkSync(pidPath);
|
|
984
1672
|
} catch {
|
|
985
1673
|
}
|
|
986
1674
|
if (!socketPath.startsWith("\\\\.\\pipe")) {
|
|
987
1675
|
try {
|
|
988
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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-
|
|
1320
|
-
//# sourceMappingURL=chunk-
|
|
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
|