@vibgrate/cli 2026.728.3 → 2026.728.4

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 (131) hide show
  1. package/dist/baseline-QD4HVNVJ.js +8 -0
  2. package/dist/{baseline-PPZJBYAJ.js.map → baseline-QD4HVNVJ.js.map} +1 -1
  3. package/dist/{chunk-A2IYFNCQ.js → chunk-2ZHWF7C6.js} +227 -19
  4. package/dist/chunk-2ZHWF7C6.js.map +1 -0
  5. package/dist/{chunk-UZJS7GMK.js → chunk-3LSG7Z4I.js} +10 -6
  6. package/dist/chunk-3LSG7Z4I.js.map +1 -0
  7. package/dist/{chunk-R2QNFXE5.js → chunk-DBOT2ELH.js} +3 -3
  8. package/dist/{chunk-R2QNFXE5.js.map → chunk-DBOT2ELH.js.map} +1 -1
  9. package/dist/{chunk-6UK6QPTR.js → chunk-EETK7S43.js} +137 -34
  10. package/dist/chunk-EETK7S43.js.map +1 -0
  11. package/dist/{chunk-JRAGO6OB.js → chunk-EVWW3BPM.js} +4 -4
  12. package/dist/{chunk-JRAGO6OB.js.map → chunk-EVWW3BPM.js.map} +1 -1
  13. package/dist/chunk-G4HTI4ZM.js +1537 -0
  14. package/dist/chunk-G4HTI4ZM.js.map +1 -0
  15. package/dist/{chunk-VYGEOOLY.js → chunk-HY2RVURA.js} +3 -3
  16. package/dist/{chunk-VYGEOOLY.js.map → chunk-HY2RVURA.js.map} +1 -1
  17. package/dist/chunk-IPICO2RV.js +789 -0
  18. package/dist/chunk-IPICO2RV.js.map +1 -0
  19. package/dist/{chunk-6GRLZ4Z2.js → chunk-LYDH2PHN.js} +8 -8
  20. package/dist/{chunk-6GRLZ4Z2.js.map → chunk-LYDH2PHN.js.map} +1 -1
  21. package/dist/{chunk-6UQMXIAG.js → chunk-OWSFZQOP.js} +62 -12
  22. package/dist/chunk-OWSFZQOP.js.map +1 -0
  23. package/dist/{chunk-WH2N22S3.js → chunk-PRGCCW7D.js} +4 -4
  24. package/dist/{chunk-WH2N22S3.js.map → chunk-PRGCCW7D.js.map} +1 -1
  25. package/dist/{chunk-3BIRGSRS.js → chunk-RCBMY7CR.js} +11 -11
  26. package/dist/{chunk-3BIRGSRS.js.map → chunk-RCBMY7CR.js.map} +1 -1
  27. package/dist/{chunk-OSD546W7.js → chunk-REIGWTTH.js} +5 -3
  28. package/dist/chunk-REIGWTTH.js.map +1 -0
  29. package/dist/{chunk-B3YUO3NW.js → chunk-RYNAK4LK.js} +3 -3
  30. package/dist/{chunk-B3YUO3NW.js.map → chunk-RYNAK4LK.js.map} +1 -1
  31. package/dist/{chunk-YF43FOPP.js → chunk-RZEXHDCF.js} +33 -14
  32. package/dist/chunk-RZEXHDCF.js.map +1 -0
  33. package/dist/chunk-SA4BMEU6.js +2184 -0
  34. package/dist/chunk-SA4BMEU6.js.map +1 -0
  35. package/dist/{chunk-HK3AWUXH.js → chunk-TCJTQGAO.js} +3 -3
  36. package/dist/{chunk-HK3AWUXH.js.map → chunk-TCJTQGAO.js.map} +1 -1
  37. package/dist/{chunk-ZBTY6VV4.js → chunk-US2DAKCM.js} +15 -5
  38. package/dist/chunk-US2DAKCM.js.map +1 -0
  39. package/dist/{chunk-ON25BX6T.js → chunk-UTNNGKKX.js} +3 -3
  40. package/dist/{chunk-ON25BX6T.js.map → chunk-UTNNGKKX.js.map} +1 -1
  41. package/dist/chunk-WDJNUC6V.js +3059 -0
  42. package/dist/chunk-WDJNUC6V.js.map +1 -0
  43. package/dist/{chunk-SZSNPXSW.js → chunk-WHJMS7XC.js} +3 -3
  44. package/dist/{chunk-SZSNPXSW.js.map → chunk-WHJMS7XC.js.map} +1 -1
  45. package/dist/chunk-Z6ZWNWWR.js +30 -0
  46. package/dist/{chunk-DGUM43GV.js.map → chunk-Z6ZWNWWR.js.map} +1 -1
  47. package/dist/{chunk-VI4ITXHQ.js → chunk-ZU7VD4QL.js} +7 -6
  48. package/dist/chunk-ZU7VD4QL.js.map +1 -0
  49. package/dist/cli.js +158 -71
  50. package/dist/cli.js.map +1 -1
  51. package/dist/{execution-env-ZU45JZ4G.js → execution-env-4MS5AYAN.js} +3 -3
  52. package/dist/{execution-env-ZU45JZ4G.js.map → execution-env-4MS5AYAN.js.map} +1 -1
  53. package/dist/{federation-SZWZIFZ7.js → federation-MDQPO3PW.js} +3 -3
  54. package/dist/{federation-SZWZIFZ7.js.map → federation-MDQPO3PW.js.map} +1 -1
  55. package/dist/{fs-O5IQYXHO.js → fs-4G5A65KS.js} +3 -3
  56. package/dist/fs-4G5A65KS.js.map +1 -0
  57. package/dist/{fs-B5HZ2MIU.js → fs-6JKZNQ3U.js} +3 -3
  58. package/dist/fs-6JKZNQ3U.js.map +1 -0
  59. package/dist/{git-ref-XDXB6JP5.js → git-ref-RSRAN4OH.js} +3 -3
  60. package/dist/{git-ref-XDXB6JP5.js.map → git-ref-RSRAN4OH.js.map} +1 -1
  61. package/dist/{graph-backend-UTOL3SHE.js → graph-backend-GBL33GMW.js} +5 -5
  62. package/dist/{graph-backend-UTOL3SHE.js.map → graph-backend-GBL33GMW.js.map} +1 -1
  63. package/dist/index.d.ts +3 -2
  64. package/dist/index.js +12 -10
  65. package/dist/index.js.map +1 -1
  66. package/dist/{interactive-OCWQ326U.js → interactive-32LSIBRG.js} +45 -24
  67. package/dist/interactive-32LSIBRG.js.map +1 -0
  68. package/dist/{llm-host-XYBT4267.js → llm-host-5RE34322.js} +3 -3
  69. package/dist/{llm-host-XYBT4267.js.map → llm-host-5RE34322.js.map} +1 -1
  70. package/dist/load-SEFJA3M5.js +8 -0
  71. package/dist/{load-QPOWQD7C.js.map → load-SEFJA3M5.js.map} +1 -1
  72. package/dist/local-runtime-2ED33KK6.js +6 -0
  73. package/dist/{local-runtime-5O2NN3GA.js.map → local-runtime-2ED33KK6.js.map} +1 -1
  74. package/dist/mcp-tools-KAJS34TN.js +4 -0
  75. package/dist/{mcp-tools-I7JK3S2M.js.map → mcp-tools-KAJS34TN.js.map} +1 -1
  76. package/dist/{model-execution-profile-TFBKCNJG.js → model-execution-profile-ELWF3Z6H.js} +3 -3
  77. package/dist/{model-execution-profile-TFBKCNJG.js.map → model-execution-profile-ELWF3Z6H.js.map} +1 -1
  78. package/dist/model-manager-GJ2YSTSK.js +10 -0
  79. package/dist/{model-manager-LB54VSV7.js.map → model-manager-GJ2YSTSK.js.map} +1 -1
  80. package/dist/{model-orchestrator-4M5FG6MN.js → model-orchestrator-LHYPJBFH.js} +3 -3
  81. package/dist/{model-orchestrator-4M5FG6MN.js.map → model-orchestrator-LHYPJBFH.js.map} +1 -1
  82. package/dist/parse-worker.js +2 -2
  83. package/dist/{paths-2RAH3GME.js → paths-62AKTFSR.js} +3 -3
  84. package/dist/{paths-2RAH3GME.js.map → paths-62AKTFSR.js.map} +1 -1
  85. package/dist/{resolve-gguf-4AFRN4CP.js → resolve-gguf-SDIYGGI6.js} +3 -3
  86. package/dist/{resolve-gguf-4AFRN4CP.js.map → resolve-gguf-SDIYGGI6.js.map} +1 -1
  87. package/dist/{runtime-session-EJQZXANB.js → runtime-session-4IACVDSY.js} +7 -7
  88. package/dist/{runtime-session-EJQZXANB.js.map → runtime-session-4IACVDSY.js.map} +1 -1
  89. package/dist/session-2JT4AUZT.js +13 -0
  90. package/dist/{session-ZJGENHKX.js.map → session-2JT4AUZT.js.map} +1 -1
  91. package/dist/session-store-O4XPXKOV.js +4 -0
  92. package/dist/{session-store-WFBX7HXL.js.map → session-store-O4XPXKOV.js.map} +1 -1
  93. package/dist/{sql-extract-JELDZA6W.js → sql-extract-6UJOLJDL.js} +3 -3
  94. package/dist/{sql-extract-JELDZA6W.js.map → sql-extract-6UJOLJDL.js.map} +1 -1
  95. package/dist/stream-json-Y45JBNYP.js +112 -0
  96. package/dist/stream-json-Y45JBNYP.js.map +1 -0
  97. package/dist/ui-K7D3BCHD.js +5 -0
  98. package/dist/ui-K7D3BCHD.js.map +1 -0
  99. package/dist/vg-mcp-bridge-E4SJFL7Q.js +75 -0
  100. package/dist/vg-mcp-bridge-E4SJFL7Q.js.map +1 -0
  101. package/dist/{vgd-3MHSB4KA.js → vgd-KDQFS33M.js} +6 -6
  102. package/dist/vgd-KDQFS33M.js.map +1 -0
  103. package/package.json +1 -1
  104. package/dist/baseline-PPZJBYAJ.js +0 -8
  105. package/dist/chunk-57QU36P6.js +0 -5645
  106. package/dist/chunk-57QU36P6.js.map +0 -1
  107. package/dist/chunk-6AXHLQBV.js +0 -1892
  108. package/dist/chunk-6AXHLQBV.js.map +0 -1
  109. package/dist/chunk-6UK6QPTR.js.map +0 -1
  110. package/dist/chunk-6UQMXIAG.js.map +0 -1
  111. package/dist/chunk-A2IYFNCQ.js.map +0 -1
  112. package/dist/chunk-DGUM43GV.js +0 -10
  113. package/dist/chunk-OSD546W7.js.map +0 -1
  114. package/dist/chunk-UZJS7GMK.js.map +0 -1
  115. package/dist/chunk-VI4ITXHQ.js.map +0 -1
  116. package/dist/chunk-YF43FOPP.js.map +0 -1
  117. package/dist/chunk-ZBTY6VV4.js.map +0 -1
  118. package/dist/fs-B5HZ2MIU.js.map +0 -1
  119. package/dist/fs-O5IQYXHO.js.map +0 -1
  120. package/dist/interactive-OCWQ326U.js.map +0 -1
  121. package/dist/load-QPOWQD7C.js +0 -8
  122. package/dist/local-runtime-5O2NN3GA.js +0 -5
  123. package/dist/mcp-tools-I7JK3S2M.js +0 -4
  124. package/dist/model-manager-LB54VSV7.js +0 -10
  125. package/dist/session-ZJGENHKX.js +0 -13
  126. package/dist/session-store-WFBX7HXL.js +0 -4
  127. package/dist/stream-json-Z5KC2DM3.js +0 -78
  128. package/dist/stream-json-Z5KC2DM3.js.map +0 -1
  129. package/dist/ui-Q6DJ3GCU.js +0 -5
  130. package/dist/ui-Q6DJ3GCU.js.map +0 -1
  131. package/dist/vgd-3MHSB4KA.js.map +0 -1
@@ -0,0 +1,3059 @@
1
+ import { FREE_PACK, navigationToolsetConfig, deferredToolNames, HOT_TOOLS } from './chunk-SA4BMEU6.js';
2
+ import { edgeId, buildModuleResolver, resolve, applyStaticTestLinkage, nodeId } from './chunk-IPICO2RV.js';
3
+ import { VERSION } from './chunk-RCBMY7CR.js';
4
+ import { acquireLock, releaseLock, indexFor } from './chunk-EETK7S43.js';
5
+ import { init_cli_invocation, whichOnPath, isInstalledOwnBinary } from './chunk-3LSG7Z4I.js';
6
+ import { langById, grammarSetVersion, langForExtension, setGrammarsOverride, parseSource } from './chunk-UTNNGKKX.js';
7
+ import { redactSecrets } from './chunk-RXP66R2E.js';
8
+ import { writeGraphIndex, SCHEMA_VERSION, loadGraph, ensureVibgrateGitignore, vibgrateDir, writeArtifacts } from './chunk-RYNAK4LK.js';
9
+ import { loadCache, serializeGraph, cacheDir, stableStringify } from './chunk-ZKNB4YH4.js';
10
+ import { shortId, canonicalize, hashString, hashBytes } from './chunk-JFGYT6BI.js';
11
+ import * as fs10 from 'fs';
12
+ import * as path10 from 'path';
13
+ import ignore from 'ignore';
14
+ import * as v8 from 'v8';
15
+ import Graph from 'graphology';
16
+ import pagerank from 'graphology-metrics/centrality/pagerank.js';
17
+ import betweenness from 'graphology-metrics/centrality/betweenness.js';
18
+ import eigenvector from 'graphology-metrics/centrality/eigenvector.js';
19
+ import louvain from 'graphology-communities-louvain';
20
+ import * as os from 'os';
21
+ import { fileURLToPath } from 'url';
22
+ import ts from 'typescript';
23
+ import * as fs7 from 'fs/promises';
24
+
25
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
26
+ // Version control, IDE & tool metadata
27
+ ".git",
28
+ ".svn",
29
+ ".hg",
30
+ ".idea",
31
+ ".vscode",
32
+ ".vs",
33
+ ".vibgrate",
34
+ ".wrangler",
35
+ ".turbo",
36
+ ".cache",
37
+ // Migration / tool backup trees (NativeScript, codemods, agent worktrees)
38
+ ".migration_backup",
39
+ ".update_backup",
40
+ ".backup",
41
+ ".bak",
42
+ ".history",
43
+ ".snapshots",
44
+ ".snapshots_bak",
45
+ // JavaScript / Node — installed dependency trees
46
+ "node_modules",
47
+ "bower_components",
48
+ "jspm_packages",
49
+ "web_modules",
50
+ ".pnpm-store",
51
+ ".yarn",
52
+ // JS/TS meta-framework & bundler build output
53
+ ".next",
54
+ ".nuxt",
55
+ ".output",
56
+ ".svelte-kit",
57
+ ".astro",
58
+ ".vercel",
59
+ ".netlify",
60
+ ".angular",
61
+ ".parcel-cache",
62
+ ".docusaurus",
63
+ "storybook-static",
64
+ // Generic build / dist / coverage output
65
+ "dist",
66
+ "build",
67
+ "out",
68
+ "coverage",
69
+ // Native mobile platform build trees (NativeScript / Cordova / Capacitor)
70
+ "platforms",
71
+ "App_Resources",
72
+ // Vendored third-party source trees
73
+ "vendor",
74
+ "Pods",
75
+ "Carthage",
76
+ // Python — virtualenvs & tool caches
77
+ ".venv",
78
+ "venv",
79
+ "virtualenv",
80
+ ".tox",
81
+ ".nox",
82
+ "__pycache__",
83
+ ".mypy_cache",
84
+ ".pytest_cache",
85
+ ".ruff_cache",
86
+ ".eggs",
87
+ ".ipynb_checkpoints",
88
+ // Ruby
89
+ ".bundle",
90
+ // Rust / Maven / sbt / Clojure — all build into target/
91
+ "target",
92
+ // JVM (Gradle / Maven) & .NET build output
93
+ ".gradle",
94
+ "bin",
95
+ "obj",
96
+ "TestResults",
97
+ // Swift / Xcode
98
+ ".build",
99
+ ".swiftpm",
100
+ "DerivedData",
101
+ // Dart / Flutter
102
+ ".dart_tool",
103
+ // Elixir / Erlang
104
+ "_build",
105
+ "deps",
106
+ // Haskell (Cabal / Stack)
107
+ "dist-newstyle",
108
+ ".stack-work",
109
+ // Infrastructure-as-code build/state
110
+ ".terraform",
111
+ ".serverless",
112
+ "cdk.out",
113
+ ".aws-sam"
114
+ ]);
115
+ var SKIP_FILES = /* @__PURE__ */ new Set([
116
+ // JavaScript / Node
117
+ "package-lock.json",
118
+ "npm-shrinkwrap.json",
119
+ "yarn.lock",
120
+ "pnpm-lock.yaml",
121
+ "bun.lockb",
122
+ "bun.lock",
123
+ "deno.lock",
124
+ ".pnp.cjs",
125
+ ".pnp.data.json",
126
+ ".pnp.loader.mjs",
127
+ // Python
128
+ "poetry.lock",
129
+ "pipfile.lock",
130
+ "pdm.lock",
131
+ "uv.lock",
132
+ "pylock.toml",
133
+ "conda-lock.yml",
134
+ // Ruby
135
+ "gemfile.lock",
136
+ // PHP
137
+ "composer.lock",
138
+ // Rust
139
+ "cargo.lock",
140
+ // Go
141
+ "go.sum",
142
+ "go.work.sum",
143
+ "gopkg.lock",
144
+ "glide.lock",
145
+ // .NET
146
+ "packages.lock.json",
147
+ // Java / JVM (Gradle)
148
+ "gradle.lockfile",
149
+ // Swift / Xcode
150
+ "package.resolved",
151
+ // Dart / Flutter
152
+ "pubspec.lock",
153
+ // Elixir
154
+ "mix.lock",
155
+ // Haskell (Cabal / Stack)
156
+ "cabal.project.freeze",
157
+ "stack.yaml.lock",
158
+ // Julia
159
+ "manifest.toml",
160
+ // R
161
+ "renv.lock",
162
+ // Perl (Carton)
163
+ "cpanfile.snapshot",
164
+ // CocoaPods / Carthage
165
+ "podfile.lock",
166
+ "cartfile.resolved",
167
+ // Terraform / IaC
168
+ ".terraform.lock.hcl",
169
+ // Helm
170
+ "chart.lock",
171
+ // C / C++ (Conan)
172
+ "conan.lock",
173
+ // Bazel
174
+ "module.bazel.lock",
175
+ // Nix
176
+ "flake.lock"
177
+ ]);
178
+ function toPosix(p) {
179
+ return p.split(path10.sep).join("/");
180
+ }
181
+ function buildRootIgnore(root, exclude) {
182
+ const ig = ignore();
183
+ const gitignorePath = path10.join(root, ".gitignore");
184
+ if (fs10.existsSync(gitignorePath)) {
185
+ ig.add(fs10.readFileSync(gitignorePath, "utf8"));
186
+ }
187
+ if (exclude.length) ig.add(exclude);
188
+ return ig;
189
+ }
190
+ function discover(options) {
191
+ const root = path10.resolve(options.root);
192
+ const onlyLangs = (options.only ?? []).filter(Boolean);
193
+ const allowLang = (lang) => onlyLangs.length === 0 || onlyLangs.includes(lang.id);
194
+ for (const id of onlyLangs) {
195
+ if (!langById(id)) {
196
+ throw new UsageError(`unknown language "${id}" for --only`);
197
+ }
198
+ }
199
+ const rootIg = buildRootIgnore(root, options.exclude ?? []);
200
+ const scopeAbs = (options.paths && options.paths.length ? options.paths.map((p) => path10.resolve(root, p)) : [root]).filter((p) => fs10.existsSync(p));
201
+ const found = /* @__PURE__ */ new Map();
202
+ const considerFile = (abs) => {
203
+ const rel2 = toPosix(path10.relative(root, abs));
204
+ if (rel2.startsWith("..")) return;
205
+ if (rel2 === "" || rootIg.ignores(rel2)) return;
206
+ if (SKIP_FILES.has(path10.basename(abs).toLowerCase())) return;
207
+ const lang = langForExtension(path10.extname(abs));
208
+ if (!lang || !allowLang(lang)) return;
209
+ found.set(rel2, { rel: rel2, abs, lang });
210
+ };
211
+ const walk = (dir) => {
212
+ let entries;
213
+ try {
214
+ entries = fs10.readdirSync(dir, { withFileTypes: true });
215
+ } catch {
216
+ return;
217
+ }
218
+ for (const entry of entries) {
219
+ const abs = path10.join(dir, entry.name);
220
+ const rel2 = toPosix(path10.relative(root, abs));
221
+ if (entry.isDirectory()) {
222
+ if (SKIP_DIRS.has(entry.name)) continue;
223
+ if (rel2 && rootIg.ignores(`${rel2}/`)) continue;
224
+ walk(abs);
225
+ } else if (entry.isFile()) {
226
+ considerFile(abs);
227
+ }
228
+ }
229
+ };
230
+ for (const scope of scopeAbs) {
231
+ const stat = fs10.statSync(scope);
232
+ if (stat.isDirectory()) walk(scope);
233
+ else if (stat.isFile()) considerFile(scope);
234
+ }
235
+ return [...found.values()].sort((a, b) => a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0);
236
+ }
237
+ var UsageError = class extends Error {
238
+ isUsageError = true;
239
+ constructor(message) {
240
+ super(message);
241
+ this.name = "UsageError";
242
+ }
243
+ };
244
+ var MIB = 1024 * 1024;
245
+ var DEFAULT_MAX_FILE_BYTES = 2 * MIB;
246
+ var DEFAULT_MAX_FILES = 1e5;
247
+ var DEFAULT_TSC_MAX_FILES = 1e4;
248
+ var HEAP_GUARD_FRACTION = 0.9;
249
+ var ResourceLimitError = class extends Error {
250
+ isResourceLimitError = true;
251
+ constructor(message) {
252
+ super(message);
253
+ this.name = "ResourceLimitError";
254
+ }
255
+ };
256
+ function envInt(name, fallback) {
257
+ const raw = process.env[name];
258
+ if (raw === void 0 || raw.trim() === "") return fallback;
259
+ const n = Number(raw);
260
+ return Number.isInteger(n) && n >= 0 ? n : fallback;
261
+ }
262
+ function envIntOptional(name) {
263
+ const n = envInt(name, 0);
264
+ return n > 0 ? n : void 0;
265
+ }
266
+ function defaultMemoryBudgetMb() {
267
+ return Math.floor(v8.getHeapStatistics().heap_size_limit / MIB * HEAP_GUARD_FRACTION);
268
+ }
269
+ function resolveLimits(overrides = {}) {
270
+ return {
271
+ maxFileBytes: overrides.maxFileBytes ?? envInt("VG_MAX_FILE_BYTES", DEFAULT_MAX_FILE_BYTES),
272
+ maxFiles: overrides.maxFiles ?? envInt("VG_MAX_FILES", DEFAULT_MAX_FILES),
273
+ tscMaxFiles: overrides.tscMaxFiles ?? envInt("VG_TSC_MAX_FILES", DEFAULT_TSC_MAX_FILES),
274
+ memoryBudgetMb: overrides.memoryBudgetMb ?? envInt("VG_MEMORY_BUDGET_MB", defaultMemoryBudgetMb())
275
+ };
276
+ }
277
+ function envJobs() {
278
+ return envIntOptional("VG_JOBS");
279
+ }
280
+ function envWorkerHeapMb() {
281
+ return envIntOptional("VG_WORKER_HEAP_MB");
282
+ }
283
+ function checkMemoryBudget(phase, budgetMb) {
284
+ if (budgetMb <= 0) return;
285
+ const usedMb = process.memoryUsage().heapUsed / MIB;
286
+ if (usedMb <= budgetMb) return;
287
+ throw new ResourceLimitError(
288
+ `graph build stopped during ${phase}: heap use ${Math.round(usedMb)} MiB exceeds the ${budgetMb} MiB budget. Narrow the build (scope to sub-paths, add --exclude globs, or --only <langs>), raise the Node heap (NODE_OPTIONS=--max-old-space-size=8192), or set VG_MEMORY_BUDGET_MB (0 disables this safeguard).`
289
+ );
290
+ }
291
+ function formatBytes(n) {
292
+ if (n >= MIB) return `${(n / MIB).toFixed(1)} MiB`;
293
+ if (n >= 1024) return `${(n / 1024).toFixed(1)} KiB`;
294
+ return `${n} B`;
295
+ }
296
+
297
+ // src/engine/scip.ts
298
+ var ROLE_DEFINITION = 1;
299
+ var Reader = class {
300
+ constructor(buf) {
301
+ this.buf = buf;
302
+ }
303
+ buf;
304
+ pos = 0;
305
+ get done() {
306
+ return this.pos >= this.buf.length;
307
+ }
308
+ varint() {
309
+ let result = 0;
310
+ let shift = 0;
311
+ for (; ; ) {
312
+ const b = this.buf[this.pos++];
313
+ result += (b & 127) * 2 ** shift;
314
+ if ((b & 128) === 0) break;
315
+ shift += 7;
316
+ }
317
+ return result;
318
+ }
319
+ tag() {
320
+ const t = this.varint();
321
+ return { field: t >>> 3, wire: t & 7 };
322
+ }
323
+ bytes() {
324
+ const len = this.varint();
325
+ const out = this.buf.subarray(this.pos, this.pos + len);
326
+ this.pos += len;
327
+ return out;
328
+ }
329
+ skip(wire) {
330
+ if (wire === 0) this.varint();
331
+ else if (wire === 2) this.bytes();
332
+ else if (wire === 5) this.pos += 4;
333
+ else if (wire === 1) this.pos += 8;
334
+ }
335
+ };
336
+ var td = new TextDecoder();
337
+ function decodeScipIndex(buf) {
338
+ const index = { documents: [] };
339
+ const r = new Reader(buf);
340
+ while (!r.done) {
341
+ const { field, wire } = r.tag();
342
+ if (field === 1 && wire === 2) decodeMetadata(r.bytes(), index);
343
+ else if (field === 2 && wire === 2) index.documents.push(decodeDocument(r.bytes()));
344
+ else r.skip(wire);
345
+ }
346
+ return index;
347
+ }
348
+ function decodeMetadata(buf, index) {
349
+ const r = new Reader(buf);
350
+ while (!r.done) {
351
+ const { field, wire } = r.tag();
352
+ if (field === 2 && wire === 2) decodeToolInfo(r.bytes(), index);
353
+ else r.skip(wire);
354
+ }
355
+ }
356
+ function decodeToolInfo(buf, index) {
357
+ const r = new Reader(buf);
358
+ while (!r.done) {
359
+ const { field, wire } = r.tag();
360
+ if (field === 1 && wire === 2) index.toolName = td.decode(r.bytes());
361
+ else if (field === 2 && wire === 2) index.toolVersion = td.decode(r.bytes());
362
+ else r.skip(wire);
363
+ }
364
+ }
365
+ function decodeDocument(buf) {
366
+ const doc = { relativePath: "", occurrences: [] };
367
+ const r = new Reader(buf);
368
+ while (!r.done) {
369
+ const { field, wire } = r.tag();
370
+ if (field === 1 && wire === 2) doc.relativePath = td.decode(r.bytes());
371
+ else if (field === 2 && wire === 2) doc.occurrences.push(decodeOccurrence(r.bytes()));
372
+ else r.skip(wire);
373
+ }
374
+ return doc;
375
+ }
376
+ function decodeOccurrence(buf) {
377
+ const occ = { range: [], symbol: "", roles: 0 };
378
+ const r = new Reader(buf);
379
+ while (!r.done) {
380
+ const { field, wire } = r.tag();
381
+ if (field === 1) {
382
+ if (wire === 2) {
383
+ const sub = new Reader(r.bytes());
384
+ while (!sub.done) occ.range.push(sub.varint());
385
+ } else if (wire === 0) {
386
+ occ.range.push(r.varint());
387
+ } else r.skip(wire);
388
+ } else if (field === 2 && wire === 2) occ.symbol = td.decode(r.bytes());
389
+ else if (field === 3 && wire === 0) occ.roles = r.varint();
390
+ else r.skip(wire);
391
+ }
392
+ return occ;
393
+ }
394
+ function scipEdges(index, nodes, relForScip) {
395
+ const nodesByFile = /* @__PURE__ */ new Map();
396
+ for (const n of nodes) {
397
+ if (n.kind === "file" || n.kind === "external") continue;
398
+ const list = nodesByFile.get(n.file);
399
+ if (list) list.push(n);
400
+ else nodesByFile.set(n.file, [n]);
401
+ }
402
+ const symbolToNode = /* @__PURE__ */ new Map();
403
+ const coveredFiles = /* @__PURE__ */ new Set();
404
+ for (const doc of index.documents) {
405
+ const file = relForScip(doc.relativePath);
406
+ const fileNodes = nodesByFile.get(file);
407
+ if (!fileNodes) continue;
408
+ coveredFiles.add(file);
409
+ for (const occ of doc.occurrences) {
410
+ if ((occ.roles & ROLE_DEFINITION) === 0 || !occ.symbol || occ.symbol.startsWith("local ")) continue;
411
+ const line = (occ.range[0] ?? 0) + 1;
412
+ const node = nodeStartingAt(fileNodes, line) ?? enclosing(fileNodes, line);
413
+ if (node) symbolToNode.set(occ.symbol, node);
414
+ }
415
+ }
416
+ const edgeMap = /* @__PURE__ */ new Map();
417
+ let references = 0;
418
+ let resolved = 0;
419
+ for (const doc of index.documents) {
420
+ const file = relForScip(doc.relativePath);
421
+ const fileNodes = nodesByFile.get(file);
422
+ if (!fileNodes) continue;
423
+ for (const occ of doc.occurrences) {
424
+ if ((occ.roles & ROLE_DEFINITION) !== 0 || !occ.symbol || occ.symbol.startsWith("local ")) continue;
425
+ references++;
426
+ const target = symbolToNode.get(occ.symbol);
427
+ if (!target) continue;
428
+ const line = (occ.range[0] ?? 0) + 1;
429
+ const src = enclosing(fileNodes, line);
430
+ if (!src || src.id === target.id) continue;
431
+ const kind = target.kind === "function" || target.kind === "method" ? "call" : "references";
432
+ add(edgeMap, kind, src.id, target.id);
433
+ resolved++;
434
+ }
435
+ }
436
+ return {
437
+ edges: [...edgeMap.values()],
438
+ coveredFiles,
439
+ stats: { documents: index.documents.length, references, resolved }
440
+ };
441
+ }
442
+ function add(map, kind, src, dst) {
443
+ const id = edgeId(kind, src, dst);
444
+ const existing = map.get(id);
445
+ if (existing) {
446
+ existing.count = (existing.count ?? 1) + 1;
447
+ return;
448
+ }
449
+ const resolution = "scip";
450
+ map.set(id, { id, kind, src, dst, resolution, confidence: 1, count: 1 });
451
+ }
452
+ function enclosing(nodes, line) {
453
+ let best;
454
+ for (const n of nodes) {
455
+ if (n.span.start <= line && n.span.end >= line) {
456
+ if (!best || n.span.end - n.span.start < best.span.end - best.span.start) best = n;
457
+ }
458
+ }
459
+ return best;
460
+ }
461
+ function nodeStartingAt(nodes, line) {
462
+ let best;
463
+ for (const n of nodes) {
464
+ if (n.span.start === line) {
465
+ if (!best || n.span.end - n.span.start < best.span.end - best.span.start) best = n;
466
+ }
467
+ }
468
+ return best;
469
+ }
470
+
471
+ // src/engine/rng.ts
472
+ function mulberry32(seed) {
473
+ let a = seed >>> 0;
474
+ return function() {
475
+ a = a + 1831565813 | 0;
476
+ let t = Math.imul(a ^ a >>> 15, 1 | a);
477
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
478
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
479
+ };
480
+ }
481
+ var CLUSTER_SEED = 42;
482
+
483
+ // src/engine/analyze.ts
484
+ var TIER_LARGE_NODES = 5e3;
485
+ var TIER_XL_NODES = 5e4;
486
+ function pickAnalysisTier(nodeCount, forced) {
487
+ if (forced) return forced;
488
+ if (nodeCount > TIER_XL_NODES) return "xl";
489
+ if (nodeCount > TIER_LARGE_NODES) return "large";
490
+ return "full";
491
+ }
492
+ var DEP_EDGES = /* @__PURE__ */ new Set(["call", "import", "extends", "implements", "references"]);
493
+ var CLUSTER_EDGE_KINDS = /* @__PURE__ */ new Set([...DEP_EDGES, "contains"]);
494
+ var IMPORTANCE_WEIGHTS = { pagerank: 0.4, betweenness: 0.25, eigenvector: 0.2, degree: 0.15 };
495
+ var DEFAULT_BETWEENNESS_LIMIT = 5e3;
496
+ function analyze(nodes, edges, options = {}) {
497
+ const tier = pickAnalysisTier(nodes.length, options.tier);
498
+ const mode = options.cluster ?? (tier === "xl" ? "louvain" : "louvain");
499
+ const ids = [...nodes.map((n) => n.id)].sort((a, b) => a.localeCompare(b));
500
+ const dg = simpleGraph(ids, edges, DEP_EDGES, "directed");
501
+ const pr = safeMetric(() => pagerank(dg), ids);
502
+ const bwLimit = options.betweennessLimit ?? DEFAULT_BETWEENNESS_LIMIT;
503
+ const bw = tier === "full" && dg.order <= bwLimit ? safeMetric(() => betweenness(dg), ids) : zeros(ids);
504
+ const ev = tier === "full" || tier === "large" ? safeMetric(() => eigenvector(dg), ids) : zeros(ids);
505
+ const deg = degreeMap(dg, ids);
506
+ const prN = normalize(pr);
507
+ const bwN = normalize(bw);
508
+ const evN = normalize(ev);
509
+ const degN = normalize(deg);
510
+ const weights = tier === "xl" ? { pagerank: 0.7, betweenness: 0, eigenvector: 0, degree: 0.3 } : tier === "large" ? { pagerank: 0.5, betweenness: 0, eigenvector: 0.25, degree: 0.25 } : IMPORTANCE_WEIGHTS;
511
+ const importance = /* @__PURE__ */ new Map();
512
+ for (const id of ids) {
513
+ const score = weights.pagerank * (prN.get(id) ?? 0) + weights.betweenness * (bwN.get(id) ?? 0) + weights.eigenvector * (evN.get(id) ?? 0) + weights.degree * (degN.get(id) ?? 0);
514
+ importance.set(id, score);
515
+ }
516
+ const importanceN = normalize(importance);
517
+ const vals = [...importanceN.values()];
518
+ const mean = vals.reduce((a, b) => a + b, 0) / (vals.length || 1);
519
+ const variance = vals.reduce((a, b) => a + (b - mean) ** 2, 0) / (vals.length || 1);
520
+ const std = Math.sqrt(variance);
521
+ const hubThreshold = mean + 2 * std;
522
+ const { areaByNode, mode: actualMode } = tier === "xl" ? clusterFileLevel(nodes, edges, mode) : cluster(ids, edges, mode);
523
+ const outNodes = nodes.map((n) => ({
524
+ ...n,
525
+ centrality: {
526
+ degree: round(degN.get(n.id) ?? 0),
527
+ pagerank: round(prN.get(n.id) ?? 0),
528
+ betweenness: round(bwN.get(n.id) ?? 0),
529
+ eigenvector: round(evN.get(n.id) ?? 0)
530
+ },
531
+ importance: round(importanceN.get(n.id) ?? 0),
532
+ area: areaByNode.get(n.id) ?? -1,
533
+ isHub: (importanceN.get(n.id) ?? 0) >= hubThreshold && (importanceN.get(n.id) ?? 0) > 0
534
+ })).sort((a, b) => a.id.localeCompare(b.id));
535
+ const areas = buildAreas(outNodes, edges, areaByNode);
536
+ const outEdges = scoreSurprise(edges, areaByNode);
537
+ return { nodes: outNodes, edges: outEdges, areas, cluster: actualMode, tier };
538
+ }
539
+ function clusterFileLevel(nodes, edges, mode) {
540
+ const fileNodes = nodes.filter((n) => n.kind === "file");
541
+ const fileIds = fileNodes.map((n) => n.id).sort((a, b) => a.localeCompare(b));
542
+ if (fileIds.length === 0) {
543
+ return { areaByNode: new Map(nodes.map((n) => [n.id, -1])), mode: "none" };
544
+ }
545
+ const fileIdByPath = new Map(fileNodes.map((n) => [n.file, n.id]));
546
+ const fileOf = /* @__PURE__ */ new Map();
547
+ for (const n of nodes) {
548
+ if (n.kind === "file") fileOf.set(n.id, n.id);
549
+ else if (n.file) {
550
+ const fid = fileIdByPath.get(n.file);
551
+ if (fid) fileOf.set(n.id, fid);
552
+ }
553
+ }
554
+ const fileEdges = [];
555
+ const seen = /* @__PURE__ */ new Set();
556
+ for (const e of edges) {
557
+ if (!DEP_EDGES.has(e.kind)) continue;
558
+ const sf = fileOf.get(e.src);
559
+ const df = fileOf.get(e.dst);
560
+ if (!sf || !df || sf === df) continue;
561
+ const key = `${e.kind}|${sf}|${df}`;
562
+ if (seen.has(key)) continue;
563
+ seen.add(key);
564
+ fileEdges.push({ ...e, src: sf, dst: df, id: key });
565
+ }
566
+ const { areaByNode: fileAreas, mode: used } = cluster(fileIds, fileEdges, mode);
567
+ const areaByNode = /* @__PURE__ */ new Map();
568
+ for (const n of nodes) {
569
+ const fid = fileOf.get(n.id);
570
+ areaByNode.set(n.id, fid ? fileAreas.get(fid) ?? -1 : -1);
571
+ }
572
+ return { areaByNode, mode: used };
573
+ }
574
+ function simpleGraph(ids, edges, kinds, type) {
575
+ const g = new Graph({ type, multi: false, allowSelfLoops: false });
576
+ for (const id of ids) g.addNode(id);
577
+ for (const e of edges) {
578
+ if (!kinds.has(e.kind)) continue;
579
+ if (e.src === e.dst) continue;
580
+ if (!g.hasNode(e.src) || !g.hasNode(e.dst)) continue;
581
+ const w = (e.confidence || 0.5) * (e.count ?? 1);
582
+ if (type === "directed") {
583
+ g.updateDirectedEdge(e.src, e.dst, (attr) => ({ weight: (attr?.weight ?? 0) + w }));
584
+ } else {
585
+ g.updateUndirectedEdge(e.src, e.dst, (attr) => ({ weight: (attr?.weight ?? 0) + w }));
586
+ }
587
+ }
588
+ return g;
589
+ }
590
+ function cluster(ids, edges, mode) {
591
+ if (mode === "none" || ids.length === 0) {
592
+ return { areaByNode: new Map(ids.map((id) => [id, -1])), mode: "none" };
593
+ }
594
+ const used = "louvain";
595
+ const ug = simpleGraph(ids, edges, CLUSTER_EDGE_KINDS, "undirected");
596
+ let communities;
597
+ try {
598
+ communities = louvain(ug, { rng: mulberry32(CLUSTER_SEED), resolution: 1 });
599
+ } catch {
600
+ return { areaByNode: new Map(ids.map((id) => [id, -1])), mode: "none" };
601
+ }
602
+ const byCommunity = /* @__PURE__ */ new Map();
603
+ for (const id of ids) {
604
+ const ci = communities[id] ?? -1;
605
+ const list = byCommunity.get(ci);
606
+ if (list) list.push(id);
607
+ else byCommunity.set(ci, [id]);
608
+ }
609
+ const ordered = [...byCommunity.entries()].map(([, members]) => members.sort((a, b) => a.localeCompare(b))).sort((a, b) => a[0].localeCompare(b[0]));
610
+ const areaByNode = /* @__PURE__ */ new Map();
611
+ ordered.forEach((members, areaId) => {
612
+ for (const id of members) areaByNode.set(id, areaId);
613
+ });
614
+ return { areaByNode, mode: used };
615
+ }
616
+ function buildAreas(nodes, edges, areaByNode) {
617
+ const byArea = /* @__PURE__ */ new Map();
618
+ for (const n of nodes) {
619
+ const a = areaByNode.get(n.id) ?? -1;
620
+ if (a < 0) continue;
621
+ const list = byArea.get(a);
622
+ if (list) list.push(n);
623
+ else byArea.set(a, [n]);
624
+ }
625
+ const externalByArea = /* @__PURE__ */ new Map();
626
+ const internalByArea = /* @__PURE__ */ new Map();
627
+ for (const e of edges) {
628
+ if (!DEP_EDGES.has(e.kind)) continue;
629
+ const sa = areaByNode.get(e.src);
630
+ const da = areaByNode.get(e.dst);
631
+ if (sa === void 0 || da === void 0 || sa < 0 || da < 0) continue;
632
+ if (sa === da) internalByArea.set(sa, (internalByArea.get(sa) ?? 0) + 1);
633
+ else {
634
+ externalByArea.set(sa, (externalByArea.get(sa) ?? 0) + 1);
635
+ externalByArea.set(da, (externalByArea.get(da) ?? 0) + 1);
636
+ }
637
+ }
638
+ const areas = [];
639
+ for (const [id, members] of byArea) {
640
+ const sorted = [...members].sort(
641
+ (a, b) => b.importance - a.importance || a.id.localeCompare(b.id)
642
+ );
643
+ const label = sorted[0]?.qualifiedName ?? `area-${id}`;
644
+ const internal = internalByArea.get(id) ?? 0;
645
+ const external = externalByArea.get(id) ?? 0;
646
+ const cohesion = internal + external > 0 ? internal / (internal + external) : 0;
647
+ areas.push({
648
+ id,
649
+ label,
650
+ size: members.length,
651
+ members: members.map((m) => m.id).sort((a, b) => a.localeCompare(b)),
652
+ cohesion: round(cohesion),
653
+ externalEdges: external
654
+ });
655
+ }
656
+ return areas.sort((a, b) => a.id - b.id);
657
+ }
658
+ function scoreSurprise(edges, areaByNode) {
659
+ const pairCount = /* @__PURE__ */ new Map();
660
+ for (const e of edges) {
661
+ if (!DEP_EDGES.has(e.kind)) continue;
662
+ const sa = areaByNode.get(e.src);
663
+ const da = areaByNode.get(e.dst);
664
+ if (sa === void 0 || da === void 0 || sa < 0 || da < 0 || sa === da) continue;
665
+ const key = pairKey(sa, da);
666
+ pairCount.set(key, (pairCount.get(key) ?? 0) + 1);
667
+ }
668
+ return edges.map((e) => {
669
+ if (!DEP_EDGES.has(e.kind)) return e;
670
+ const sa = areaByNode.get(e.src);
671
+ const da = areaByNode.get(e.dst);
672
+ if (sa === void 0 || da === void 0 || sa < 0 || da < 0 || sa === da) return e;
673
+ const count = pairCount.get(pairKey(sa, da)) ?? 1;
674
+ return { ...e, surprise: round(1 / count) };
675
+ }).sort(
676
+ (a, b) => a.kind.localeCompare(b.kind) || a.src.localeCompare(b.src) || a.dst.localeCompare(b.dst)
677
+ );
678
+ }
679
+ function safeMetric(fn, ids) {
680
+ try {
681
+ const res = fn();
682
+ return new Map(ids.map((id) => [id, res[id] ?? 0]));
683
+ } catch {
684
+ return zeros(ids);
685
+ }
686
+ }
687
+ function degreeMap(g, ids) {
688
+ return new Map(ids.map((id) => [id, g.hasNode(id) ? g.degree(id) : 0]));
689
+ }
690
+ function zeros(ids) {
691
+ return new Map(ids.map((id) => [id, 0]));
692
+ }
693
+ function normalize(m) {
694
+ let max = 0;
695
+ for (const v of m.values()) if (v > max) max = v;
696
+ if (max <= 0) return new Map(m);
697
+ return new Map([...m].map(([k, v]) => [k, v / max]));
698
+ }
699
+ function round(x) {
700
+ return Math.round(x * 1e6) / 1e6;
701
+ }
702
+ function pairKey(a, b) {
703
+ return a < b ? `${a}|${b}` : `${b}|${a}`;
704
+ }
705
+ var DEFAULT_PATHS = [
706
+ "coverage/lcov.info",
707
+ "lcov.info",
708
+ "coverage/coverage-final.json",
709
+ "coverage-final.json"
710
+ ];
711
+ function loadCoverage(root, explicit) {
712
+ const candidates = (explicit && explicit.length ? explicit : DEFAULT_PATHS).map(
713
+ (p) => path10.resolve(root, p)
714
+ );
715
+ const found = candidates.filter((p) => fs10.existsSync(p));
716
+ if (found.length === 0) return null;
717
+ const map = /* @__PURE__ */ new Map();
718
+ for (const file of found) {
719
+ try {
720
+ const text = fs10.readFileSync(file, "utf8");
721
+ if (file.endsWith(".json")) mergeIstanbul(map, text, root);
722
+ else mergeLcov(map, text, root);
723
+ } catch {
724
+ }
725
+ }
726
+ return map.size ? map : null;
727
+ }
728
+ function rel(root, p) {
729
+ const abs = path10.isAbsolute(p) ? p : path10.resolve(root, p);
730
+ return path10.relative(root, abs).split(path10.sep).join("/");
731
+ }
732
+ function bump(map, file, line, hits) {
733
+ let lh = map.get(file);
734
+ if (!lh) {
735
+ lh = /* @__PURE__ */ new Map();
736
+ map.set(file, lh);
737
+ }
738
+ lh.set(line, Math.max(lh.get(line) ?? 0, hits));
739
+ }
740
+ function mergeLcov(map, text, root) {
741
+ let current = null;
742
+ for (const raw of text.split("\n")) {
743
+ const line = raw.trim();
744
+ if (line.startsWith("SF:")) current = rel(root, line.slice(3));
745
+ else if (line.startsWith("DA:") && current) {
746
+ const [ln, hits] = line.slice(3).split(",");
747
+ const n = Number(ln);
748
+ const h = Number(hits);
749
+ if (Number.isFinite(n)) bump(map, current, n, Number.isFinite(h) ? h : 0);
750
+ } else if (line === "end_of_record") current = null;
751
+ }
752
+ }
753
+ function mergeIstanbul(map, text, root) {
754
+ const data = JSON.parse(text);
755
+ for (const [key, entry] of Object.entries(data)) {
756
+ const file = rel(root, entry.path ?? key);
757
+ const stmts = entry.statementMap ?? {};
758
+ const counts = entry.s ?? {};
759
+ for (const [id, loc] of Object.entries(stmts)) {
760
+ const hits = counts[id] ?? 0;
761
+ const start = loc.start?.line;
762
+ const end = loc.end?.line ?? start;
763
+ if (!Number.isFinite(start)) continue;
764
+ for (let ln = start; ln <= end; ln++) bump(map, file, ln, hits);
765
+ }
766
+ }
767
+ }
768
+ function applyCoverage(nodes, coverage) {
769
+ return nodes.map((n) => {
770
+ if (n.kind === "file" || n.kind === "external") return n;
771
+ const lh = coverage.get(n.file);
772
+ if (!lh) return n;
773
+ let instrumented = 0;
774
+ let covered = 0;
775
+ for (let ln = n.span.start; ln <= n.span.end; ln++) {
776
+ const hits = lh.get(ln);
777
+ if (hits === void 0) continue;
778
+ instrumented++;
779
+ if (hits > 0) covered++;
780
+ }
781
+ if (instrumented === 0) return n;
782
+ const fraction = Math.round(covered / instrumented * 1e6) / 1e6;
783
+ const tested = covered > 0 ? true : n.kind === "function" || n.kind === "method" ? false : n.tested;
784
+ return { ...n, coverage: fraction, tested };
785
+ });
786
+ }
787
+
788
+ // src/engine/facts.ts
789
+ function buildFacts(parses, nodes, edges) {
790
+ const facts = [];
791
+ const byId = new Map(nodes.map((n) => [n.id, n]));
792
+ const defsByFile = /* @__PURE__ */ new Map();
793
+ for (const n of nodes) {
794
+ if (n.kind === "file" || n.kind === "external") continue;
795
+ const list = defsByFile.get(n.file);
796
+ if (list) list.push(n);
797
+ else defsByFile.set(n.file, [n]);
798
+ }
799
+ for (const n of nodes) {
800
+ if ((n.kind === "function" || n.kind === "method") && n.signature) {
801
+ facts.push(makeFact("contract", [n.id], { signature: n.signature }, "declared", "Observed", [
802
+ { file: n.file, span: n.span }
803
+ ]));
804
+ }
805
+ }
806
+ for (const p of parses) {
807
+ const defs = defsByFile.get(p.rel);
808
+ if (!defs) continue;
809
+ for (const g of p.guards) {
810
+ const owner = enclosing2(defs, g.line);
811
+ if (!owner) continue;
812
+ facts.push(
813
+ makeFact("invariant", [owner.id], { guard: g.expr }, "static", "Derived", [
814
+ { file: p.rel, span: { start: g.line, end: g.line } }
815
+ ])
816
+ );
817
+ }
818
+ }
819
+ const testsByNode = /* @__PURE__ */ new Map();
820
+ for (const e of edges) {
821
+ if (e.kind !== "test") continue;
822
+ const testFile = byId.get(e.src)?.file;
823
+ if (!testFile) continue;
824
+ const list = testsByNode.get(e.dst);
825
+ if (list) list.push(testFile);
826
+ else testsByNode.set(e.dst, [testFile]);
827
+ }
828
+ for (const [nodeId2, testFiles] of testsByNode) {
829
+ const n = byId.get(nodeId2);
830
+ if (!n) continue;
831
+ facts.push(
832
+ makeFact(
833
+ "characterization",
834
+ [nodeId2],
835
+ { pinnedBy: [...new Set(testFiles)].sort() },
836
+ "static",
837
+ "Observed",
838
+ [{ file: n.file, span: n.span }]
839
+ )
840
+ );
841
+ }
842
+ return facts.sort((a, b) => a.id.localeCompare(b.id));
843
+ }
844
+ function makeFact(kind, subjectIds, predicate, derivedBy, confidence, evidence) {
845
+ const id = shortId(canonicalize({ t: "fact", kind, subjectIds, predicate }));
846
+ return { id, kind, subjectIds, predicate, derivedBy, confidence, evidence };
847
+ }
848
+ function enclosing2(defs, line) {
849
+ let best;
850
+ for (const d of defs) {
851
+ if (d.span.start <= line && d.span.end >= line) {
852
+ if (!best || d.span.end - d.span.start < best.span.end - best.span.start) best = d;
853
+ }
854
+ }
855
+ return best;
856
+ }
857
+
858
+ // src/engine/grounding.ts
859
+ function groundGraph(nodes, edges, parses, packs = [FREE_PACK]) {
860
+ const byId = new Map(nodes.map((n) => [n.id, n]));
861
+ const importsByFile = /* @__PURE__ */ new Map();
862
+ for (const p of parses) importsByFile.set(p.rel, p.imports.map((i) => i.source.toLowerCase()));
863
+ const calleesByNode = /* @__PURE__ */ new Map();
864
+ for (const e of edges) {
865
+ if (e.kind !== "call") continue;
866
+ const callee = byId.get(e.dst)?.name;
867
+ if (!callee) continue;
868
+ const list = calleesByNode.get(e.src);
869
+ if (list) list.push(callee.toLowerCase());
870
+ else calleesByNode.set(e.src, [callee.toLowerCase()]);
871
+ }
872
+ const out = [];
873
+ for (const n of nodes) {
874
+ if (n.kind === "file" || n.kind === "external") continue;
875
+ const nameParts = new Set(
876
+ n.name.split(/[^a-zA-Z0-9]+|(?<=[a-z])(?=[A-Z])/).filter(Boolean).map((s) => s.toLowerCase())
877
+ );
878
+ const imports = importsByFile.get(n.file) ?? [];
879
+ const callees = calleesByNode.get(n.id) ?? [];
880
+ for (const pack of packs) {
881
+ for (const entry of pack.entries) {
882
+ const conf = matchConfidence(entry, nameParts, imports, callees);
883
+ if (conf > 0) {
884
+ out.push({
885
+ src: n.id,
886
+ packEntryId: entry.id,
887
+ kind: entry.kind,
888
+ confidence: conf,
889
+ rationale: entry.rationale,
890
+ citation: entry.citation
891
+ });
892
+ }
893
+ }
894
+ }
895
+ }
896
+ return out.sort(
897
+ (a, b) => a.src.localeCompare(b.src) || a.packEntryId.localeCompare(b.packEntryId)
898
+ );
899
+ }
900
+ function matchConfidence(entry, nameParts, imports, callees) {
901
+ let conf = 0;
902
+ if (entry.match.imports) {
903
+ for (const sig of entry.match.imports) {
904
+ if (imports.some((imp) => imp.includes(sig.toLowerCase()))) {
905
+ conf = Math.max(conf, 0.8);
906
+ break;
907
+ }
908
+ }
909
+ }
910
+ if (entry.match.calls) {
911
+ const want = new Set(entry.match.calls.map((s) => s.toLowerCase()));
912
+ if (callees.some((c) => want.has(c))) conf = Math.max(conf, 0.7);
913
+ }
914
+ if (entry.match.keywords) {
915
+ if (entry.match.keywords.some((k) => nameParts.has(k.toLowerCase()))) {
916
+ conf = Math.max(conf, 0.5);
917
+ }
918
+ }
919
+ return conf;
920
+ }
921
+ var DEFAULT_INLINE_THRESHOLD = 24;
922
+ async function parseFiles(files, options = {}) {
923
+ const threshold = options.inlineThreshold ?? DEFAULT_INLINE_THRESHOLD;
924
+ const cores = Math.max(1, os.cpus()?.length ?? 1);
925
+ const jobs = Math.max(1, options.jobs ?? envJobs() ?? (Math.min(cores - 1, files.length) || 1));
926
+ const workerFile = resolveWorkerFile();
927
+ const useInline = options.inline === true || jobs <= 1 || files.length < threshold || workerFile === null;
928
+ if (useInline) {
929
+ if (options.grammarsDir) setGrammarsOverride(options.grammarsDir);
930
+ return sortByRel(await parseInline(files, options));
931
+ }
932
+ return sortByRel(await parsePooled(files, jobs, workerFile, options));
933
+ }
934
+ var MEM_CHECK_EVERY = 64;
935
+ async function parseInline(files, options) {
936
+ const { onProgress, memoryBudgetMb = 0 } = options;
937
+ const out = [];
938
+ onProgress?.(0, files.length);
939
+ for (const file of files) {
940
+ try {
941
+ const source = fs10.readFileSync(file.abs, "utf8");
942
+ out.push(await parseSource(file.rel, file.lang.id, source));
943
+ } catch (err) {
944
+ out.push(emptyParse(file, `parse failed: ${err.message}`));
945
+ }
946
+ onProgress?.(out.length, files.length);
947
+ if (out.length % MEM_CHECK_EVERY === 0) checkMemoryBudget("parse", memoryBudgetMb);
948
+ }
949
+ return out;
950
+ }
951
+ async function parsePooled(files, jobs, workerFile, options) {
952
+ const { onProgress, grammarsDir, memoryBudgetMb = 0 } = options;
953
+ const { default: Tinypool } = await import('tinypool');
954
+ const workerHeapMb = envWorkerHeapMb();
955
+ const pool = new Tinypool({
956
+ filename: workerFile,
957
+ maxThreads: jobs,
958
+ minThreads: 1,
959
+ ...workerHeapMb ? { resourceLimits: { maxOldGenerationSizeMb: workerHeapMb } } : {}
960
+ });
961
+ try {
962
+ const total = files.length;
963
+ const buckets = chunk(
964
+ files.map((f) => ({ rel: f.rel, abs: f.abs, lang: f.lang.id })),
965
+ Math.min(total, jobs * 8)
966
+ );
967
+ let done = 0;
968
+ onProgress?.(0, total);
969
+ const results = await Promise.all(
970
+ buckets.map(
971
+ (b) => pool.run({ tasks: b, grammarsDir }).then((r) => {
972
+ done += b.length;
973
+ onProgress?.(done, total);
974
+ checkMemoryBudget("parse", memoryBudgetMb);
975
+ return r;
976
+ })
977
+ )
978
+ );
979
+ return results.flat();
980
+ } catch (err) {
981
+ if (isWorkerOom(err)) {
982
+ throw new ResourceLimitError(
983
+ `graph build stopped: a parse worker exceeded its ${workerHeapMb ?? "?"} MiB heap cap (VG_WORKER_HEAP_MB). Raise the cap, exclude the offending files (--exclude), or run single-threaded with --jobs 1.`
984
+ );
985
+ }
986
+ throw err;
987
+ } finally {
988
+ await pool.destroy();
989
+ }
990
+ }
991
+ function isWorkerOom(err) {
992
+ const e = err;
993
+ return e?.code === "ERR_WORKER_OUT_OF_MEMORY" || /out of memory/i.test(e?.message ?? "");
994
+ }
995
+ function resolveWorkerFile() {
996
+ const here = path10.dirname(fileURLToPath(import.meta.url));
997
+ const candidate = path10.join(here, "parse-worker.js");
998
+ return fs10.existsSync(candidate) ? candidate : null;
999
+ }
1000
+ function chunk(items, buckets) {
1001
+ const out = Array.from({ length: Math.min(buckets, items.length || 1) }, () => []);
1002
+ if (out.length === 0) return [];
1003
+ items.forEach((item, i) => out[i % out.length].push(item));
1004
+ return out.filter((c) => c.length > 0);
1005
+ }
1006
+ function sortByRel(parses) {
1007
+ return parses.sort((a, b) => a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0);
1008
+ }
1009
+ function emptyParse(file, warning) {
1010
+ return {
1011
+ rel: file.rel,
1012
+ lang: file.lang.id,
1013
+ hash: "",
1014
+ bytes: 0,
1015
+ defs: [],
1016
+ calls: [],
1017
+ imports: [],
1018
+ heritage: [],
1019
+ typeRefs: [],
1020
+ guards: [],
1021
+ warnings: [warning]
1022
+ };
1023
+ }
1024
+ var PRECISE_KIND = "tsc";
1025
+ function tsResolveEdges(root, tsFiles, nodes) {
1026
+ const empty = {
1027
+ edges: [],
1028
+ coveredFiles: /* @__PURE__ */ new Set(),
1029
+ stats: { files: 0, calls: 0, jsx: 0, heritage: 0, resolved: 0 }
1030
+ };
1031
+ if (tsFiles.length === 0) return empty;
1032
+ const absToRel = new Map(tsFiles.map((f) => [normalize2(f.abs), f.rel]));
1033
+ const options = compilerOptions(root);
1034
+ let program;
1035
+ try {
1036
+ program = ts.createProgram(tsFiles.map((f) => f.abs), options);
1037
+ } catch {
1038
+ return empty;
1039
+ }
1040
+ const checker = program.getTypeChecker();
1041
+ const nodesByFile = /* @__PURE__ */ new Map();
1042
+ const fileNodeByRel = /* @__PURE__ */ new Map();
1043
+ for (const n of nodes) {
1044
+ if (n.kind === "file") {
1045
+ fileNodeByRel.set(n.file, n);
1046
+ continue;
1047
+ }
1048
+ if (n.kind === "external") continue;
1049
+ const list = nodesByFile.get(n.file);
1050
+ if (list) list.push(n);
1051
+ else nodesByFile.set(n.file, [n]);
1052
+ }
1053
+ const relForAbs = (abs) => absToRel.get(normalize2(abs));
1054
+ const edges = /* @__PURE__ */ new Map();
1055
+ const covered = /* @__PURE__ */ new Set();
1056
+ const stats = empty.stats;
1057
+ const interfaceCalls = [];
1058
+ const byId = /* @__PURE__ */ new Map();
1059
+ for (const list of nodesByFile.values()) for (const n of list) byId.set(n.id, n);
1060
+ for (const file of tsFiles) {
1061
+ const sf = program.getSourceFile(file.abs);
1062
+ if (!sf) continue;
1063
+ covered.add(file.rel);
1064
+ stats.files++;
1065
+ const fileNodes = nodesByFile.get(file.rel) ?? [];
1066
+ const fileNode = fileNodeByRel.get(file.rel);
1067
+ const lineOf = (pos) => sf.getLineAndCharacterOfPosition(pos).line + 1;
1068
+ const visit = (node) => {
1069
+ if (ts.isCallExpression(node) || ts.isNewExpression(node)) {
1070
+ stats.calls++;
1071
+ const target = resolveTarget(checker, node.expression, nodesByFile, relForAbs);
1072
+ if (target) {
1073
+ const src = enclosing3(fileNodes, lineOf(node.getStart(sf))) ?? fileNode;
1074
+ if (src && src.id !== target.id) {
1075
+ add2(edges, callKind(target), src.id, target.id);
1076
+ stats.resolved++;
1077
+ if (target.kind === "interface" && ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
1078
+ interfaceCalls.push({ srcId: src.id, interfaceId: target.id, method: node.expression.name.text });
1079
+ }
1080
+ }
1081
+ }
1082
+ } else if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
1083
+ if (isComponentTag(node.tagName)) {
1084
+ stats.jsx++;
1085
+ const target = resolveTarget(checker, node.tagName, nodesByFile, relForAbs);
1086
+ if (target) {
1087
+ const src = enclosing3(fileNodes, lineOf(node.getStart(sf))) ?? fileNode;
1088
+ if (src && src.id !== target.id) {
1089
+ add2(edges, callKind(target), src.id, target.id);
1090
+ stats.resolved++;
1091
+ }
1092
+ }
1093
+ }
1094
+ } else if (ts.isClassDeclaration(node) && node.heritageClauses) {
1095
+ const classLine = lineOf(node.getStart(sf));
1096
+ const src = enclosing3(fileNodes, classLine);
1097
+ for (const clause of node.heritageClauses) {
1098
+ const kind = clause.token === ts.SyntaxKind.ImplementsKeyword ? "implements" : "extends";
1099
+ for (const t of clause.types) {
1100
+ stats.heritage++;
1101
+ const target = resolveTarget(checker, t.expression, nodesByFile, relForAbs);
1102
+ if (src && target && src.id !== target.id) {
1103
+ add2(edges, kind, src.id, target.id);
1104
+ stats.resolved++;
1105
+ }
1106
+ }
1107
+ }
1108
+ }
1109
+ ts.forEachChild(node, visit);
1110
+ };
1111
+ visit(sf);
1112
+ }
1113
+ const implsByInterface = /* @__PURE__ */ new Map();
1114
+ for (const e of edges.values()) {
1115
+ if (e.kind !== "implements" && e.kind !== "extends") continue;
1116
+ if (byId.get(e.dst)?.kind !== "interface") continue;
1117
+ const list = implsByInterface.get(e.dst);
1118
+ if (list) list.push(e.src);
1119
+ else implsByInterface.set(e.dst, [e.src]);
1120
+ }
1121
+ for (const { srcId, interfaceId, method } of interfaceCalls) {
1122
+ const impls = implsByInterface.get(interfaceId);
1123
+ if (!impls || impls.length !== 1) continue;
1124
+ const impl = byId.get(impls[0]);
1125
+ if (!impl) continue;
1126
+ const target = (nodesByFile.get(impl.file) ?? []).find(
1127
+ (n) => (n.kind === "method" || n.kind === "function") && n.qualifiedName === `${impl.qualifiedName}.${method}`
1128
+ );
1129
+ if (target && target.id !== srcId) {
1130
+ add2(edges, "call", srcId, target.id);
1131
+ stats.resolved++;
1132
+ }
1133
+ }
1134
+ return { edges: [...edges.values()], coveredFiles: covered, stats };
1135
+ }
1136
+ function callKind(target) {
1137
+ return target.kind === "function" || target.kind === "method" || target.kind === "class" || target.kind === "component" ? "call" : "references";
1138
+ }
1139
+ function isComponentTag(tag) {
1140
+ if (ts.isIdentifier(tag)) return /^[A-Z]/.test(tag.text);
1141
+ return ts.isPropertyAccessExpression(tag);
1142
+ }
1143
+ function resolveTarget(checker, expr, nodesByFile, relForAbs) {
1144
+ let sym = checker.getSymbolAtLocation(expr);
1145
+ if (!sym) return null;
1146
+ if (sym.flags & ts.SymbolFlags.Alias) {
1147
+ try {
1148
+ sym = checker.getAliasedSymbol(sym);
1149
+ } catch {
1150
+ }
1151
+ }
1152
+ const decls = sym.declarations;
1153
+ if (!decls) return null;
1154
+ for (const decl of decls) {
1155
+ const df = decl.getSourceFile();
1156
+ const rel2 = relForAbs(df.fileName);
1157
+ if (!rel2) continue;
1158
+ const fileNodes = nodesByFile.get(rel2);
1159
+ if (!fileNodes) continue;
1160
+ const line = df.getLineAndCharacterOfPosition(decl.getStart(df)).line + 1;
1161
+ const node = pickNode(fileNodes, line, expectedKinds(decl));
1162
+ if (node) return node;
1163
+ }
1164
+ return null;
1165
+ }
1166
+ function expectedKinds(decl) {
1167
+ if (ts.isMethodDeclaration(decl) || ts.isMethodSignature(decl)) return /* @__PURE__ */ new Set(["method"]);
1168
+ if (ts.isFunctionDeclaration(decl) || ts.isFunctionExpression(decl) || ts.isArrowFunction(decl)) {
1169
+ return /* @__PURE__ */ new Set(["function", "method", "component"]);
1170
+ }
1171
+ if (ts.isClassDeclaration(decl) || ts.isClassExpression(decl)) return /* @__PURE__ */ new Set(["class", "component"]);
1172
+ if (ts.isInterfaceDeclaration(decl)) return /* @__PURE__ */ new Set(["interface"]);
1173
+ if (ts.isPropertyDeclaration(decl) || ts.isPropertySignature(decl) || ts.isGetAccessorDeclaration(decl) || ts.isSetAccessorDeclaration(decl)) {
1174
+ return /* @__PURE__ */ new Set(["property", "method"]);
1175
+ }
1176
+ if (ts.isConstructorDeclaration(decl)) return /* @__PURE__ */ new Set(["class", "method"]);
1177
+ if (ts.isVariableDeclaration(decl)) return /* @__PURE__ */ new Set(["function", "property", "component"]);
1178
+ return /* @__PURE__ */ new Set();
1179
+ }
1180
+ function pickNode(nodes, line, want) {
1181
+ let best;
1182
+ for (const n of nodes) {
1183
+ if (n.span.start !== line) continue;
1184
+ if (!best || better(n, best, want)) best = n;
1185
+ }
1186
+ return best ?? enclosing3(nodes, line);
1187
+ }
1188
+ function better(n, best, want) {
1189
+ const nWanted = want.has(n.kind);
1190
+ const bWanted = want.has(best.kind);
1191
+ if (nWanted !== bWanted) return nWanted;
1192
+ const nSize = n.span.end - n.span.start;
1193
+ const bSize = best.span.end - best.span.start;
1194
+ if (nSize !== bSize) return nSize < bSize;
1195
+ return n.id < best.id;
1196
+ }
1197
+ function compilerOptions(root) {
1198
+ const base = {
1199
+ allowJs: true,
1200
+ checkJs: false,
1201
+ skipLibCheck: true,
1202
+ noEmit: true,
1203
+ noResolve: false,
1204
+ target: ts.ScriptTarget.ES2022,
1205
+ module: ts.ModuleKind.ESNext,
1206
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
1207
+ jsx: ts.JsxEmit.Preserve,
1208
+ allowImportingTsExtensions: true,
1209
+ resolveJsonModule: true
1210
+ };
1211
+ const configPath = ts.findConfigFile(root, ts.sys.fileExists, "tsconfig.json") ?? ts.findConfigFile(root, ts.sys.fileExists, "tsconfig.base.json");
1212
+ if (configPath) {
1213
+ try {
1214
+ const host = {
1215
+ ...ts.sys,
1216
+ onUnRecoverableConfigFileDiagnostic: () => {
1217
+ }
1218
+ };
1219
+ const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, host);
1220
+ if (parsed) {
1221
+ return { ...parsed.options, ...base, baseUrl: parsed.options.baseUrl, paths: parsed.options.paths };
1222
+ }
1223
+ } catch {
1224
+ }
1225
+ }
1226
+ return base;
1227
+ }
1228
+ function add2(map, kind, src, dst) {
1229
+ const id = edgeId(kind, src, dst);
1230
+ const existing = map.get(id);
1231
+ if (existing) {
1232
+ existing.count = (existing.count ?? 1) + 1;
1233
+ return;
1234
+ }
1235
+ map.set(id, { id, kind, src, dst, resolution: PRECISE_KIND, confidence: 1, count: 1 });
1236
+ }
1237
+ function enclosing3(nodes, line) {
1238
+ let best;
1239
+ for (const n of nodes) {
1240
+ if (n.span.start <= line && n.span.end >= line) {
1241
+ if (!best || n.span.end - n.span.start < best.span.end - best.span.start) best = n;
1242
+ }
1243
+ }
1244
+ return best;
1245
+ }
1246
+ function normalize2(p) {
1247
+ return path10.resolve(p).split(path10.sep).join("/");
1248
+ }
1249
+
1250
+ // src/engine/epistemic.ts
1251
+ var SEMANTIC_RESOLVERS = /* @__PURE__ */ new Set(["scip", "stackgraph", "tsc"]);
1252
+ function classifyEpistemic(edge, dstKind) {
1253
+ if (SEMANTIC_RESOLVERS.has(edge.resolution)) return "observed";
1254
+ switch (edge.kind) {
1255
+ case "contains":
1256
+ return "observed";
1257
+ case "import":
1258
+ return dstKind === "external" || dstKind === void 0 ? "declared" : "observed";
1259
+ case "test":
1260
+ case "coverage":
1261
+ return "declared";
1262
+ default:
1263
+ return "name-matched";
1264
+ }
1265
+ }
1266
+ function epistemicBreakdown(edges) {
1267
+ const out = { observed: 0, "name-matched": 0, declared: 0 };
1268
+ for (const e of edges) {
1269
+ if (e.epistemic) out[e.epistemic]++;
1270
+ }
1271
+ return out;
1272
+ }
1273
+ var DOC_BODY_MAX_CHARS = 8e3;
1274
+ var DOC_FILE_MAX_BYTES = 512 * 1024;
1275
+ var DOC_MAX_FILES = 600;
1276
+ var DOC_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".mdx", ".txt", ".rst", ".adoc"]);
1277
+ var ENV_EXAMPLE_NAMES = /* @__PURE__ */ new Set([
1278
+ "example.env",
1279
+ "env.example",
1280
+ ".env.example",
1281
+ ".env.sample",
1282
+ ".env.template",
1283
+ ".env.development.example",
1284
+ ".env.local.example",
1285
+ "dotenv.example"
1286
+ ]);
1287
+ var EXACT_BASENAME = {
1288
+ // Manifests / package managers
1289
+ "package.json": "manifest",
1290
+ "package.yaml": "manifest",
1291
+ "package.yml": "manifest",
1292
+ "pyproject.toml": "manifest",
1293
+ "setup.cfg": "manifest",
1294
+ "requirements.txt": "manifest",
1295
+ "requirements-dev.txt": "manifest",
1296
+ "pipfile": "manifest",
1297
+ "go.mod": "manifest",
1298
+ "go.work": "manifest",
1299
+ "cargo.toml": "manifest",
1300
+ "composer.json": "manifest",
1301
+ "gemfile": "manifest",
1302
+ "podfile": "manifest",
1303
+ "pubspec.yaml": "manifest",
1304
+ "mix.exs": "manifest",
1305
+ "build.gradle": "manifest",
1306
+ "build.gradle.kts": "manifest",
1307
+ "settings.gradle": "manifest",
1308
+ "settings.gradle.kts": "manifest",
1309
+ "pom.xml": "manifest",
1310
+ "project.clj": "manifest",
1311
+ "stack.yaml": "manifest",
1312
+ "package.swift": "manifest",
1313
+ // Workspace / monorepo
1314
+ "pnpm-workspace.yaml": "workspace",
1315
+ "pnpm-workspace.yml": "workspace",
1316
+ "lerna.json": "workspace",
1317
+ "nx.json": "workspace",
1318
+ "turbo.json": "workspace",
1319
+ "rush.json": "workspace",
1320
+ "workspace.json": "workspace",
1321
+ // Docker
1322
+ dockerfile: "docker",
1323
+ "dockerfile.dev": "docker",
1324
+ "dockerfile.prod": "docker",
1325
+ "dockerfile.local": "docker",
1326
+ containerfile: "docker",
1327
+ // Compose
1328
+ "docker-compose.yml": "compose",
1329
+ "docker-compose.yaml": "compose",
1330
+ "compose.yml": "compose",
1331
+ "compose.yaml": "compose",
1332
+ "docker-compose.dev.yml": "compose",
1333
+ "docker-compose.prod.yml": "compose",
1334
+ "docker-compose.override.yml": "compose",
1335
+ // Task runners
1336
+ makefile: "task-runner",
1337
+ gnumakefile: "task-runner",
1338
+ justfile: "task-runner",
1339
+ "taskfile.yml": "task-runner",
1340
+ "taskfile.yaml": "task-runner",
1341
+ rakefile: "task-runner",
1342
+ // Build / tool config (exact)
1343
+ "tsconfig.json": "build-config",
1344
+ "jsconfig.json": "build-config",
1345
+ "vite.config.ts": "build-config",
1346
+ "vite.config.js": "build-config",
1347
+ "vite.config.mjs": "build-config",
1348
+ "vitest.config.ts": "build-config",
1349
+ "vitest.config.js": "build-config",
1350
+ "jest.config.js": "build-config",
1351
+ "jest.config.ts": "build-config",
1352
+ "jest.config.cjs": "build-config",
1353
+ "webpack.config.js": "build-config",
1354
+ "webpack.config.ts": "build-config",
1355
+ "rollup.config.js": "build-config",
1356
+ "esbuild.config.js": "build-config",
1357
+ "next.config.js": "build-config",
1358
+ "next.config.mjs": "build-config",
1359
+ "next.config.ts": "build-config",
1360
+ "nuxt.config.ts": "build-config",
1361
+ "nuxt.config.js": "build-config",
1362
+ "astro.config.mjs": "build-config",
1363
+ "svelte.config.js": "build-config",
1364
+ "remix.config.js": "build-config",
1365
+ "angular.json": "build-config",
1366
+ "babel.config.js": "build-config",
1367
+ "babel.config.json": "build-config",
1368
+ ".babelrc": "build-config",
1369
+ ".babelrc.js": "build-config",
1370
+ ".babelrc.json": "build-config",
1371
+ "eslint.config.js": "build-config",
1372
+ "eslint.config.mjs": "build-config",
1373
+ "eslint.config.cjs": "build-config",
1374
+ "eslint.config.ts": "build-config",
1375
+ ".eslintrc": "build-config",
1376
+ ".eslintrc.js": "build-config",
1377
+ ".eslintrc.cjs": "build-config",
1378
+ ".eslintrc.json": "build-config",
1379
+ ".eslintrc.yml": "build-config",
1380
+ ".prettierrc": "build-config",
1381
+ ".prettierrc.js": "build-config",
1382
+ ".prettierrc.json": "build-config",
1383
+ ".prettierrc.yml": "build-config",
1384
+ "prettier.config.js": "build-config",
1385
+ "prettier.config.cjs": "build-config",
1386
+ "ruff.toml": "build-config",
1387
+ ".ruff.toml": "build-config",
1388
+ "pytest.ini": "build-config",
1389
+ "tox.ini": "build-config",
1390
+ "mypy.ini": "build-config",
1391
+ ".mypy.ini": "build-config",
1392
+ "phpunit.xml": "build-config",
1393
+ "phpunit.xml.dist": "build-config",
1394
+ // CI exact
1395
+ "jenkinsfile": "ci",
1396
+ ".gitlab-ci.yml": "ci",
1397
+ ".travis.yml": "ci",
1398
+ "azure-pipelines.yml": "ci",
1399
+ "buildkite.yml": "ci",
1400
+ "cloudbuild.yaml": "ci",
1401
+ "appveyor.yml": "ci",
1402
+ "circle.yml": "ci",
1403
+ // Deploy / infra (root-level common names)
1404
+ "vercel.json": "infra",
1405
+ "netlify.toml": "infra",
1406
+ "wrangler.toml": "infra",
1407
+ "wrangler.json": "infra",
1408
+ "wrangler.jsonc": "infra",
1409
+ "fly.toml": "infra",
1410
+ "railway.toml": "infra",
1411
+ "render.yaml": "infra",
1412
+ "app.yaml": "infra",
1413
+ "Procfile": "infra",
1414
+ "procfile": "infra",
1415
+ "heroku.yml": "infra",
1416
+ "serverless.yml": "infra",
1417
+ "serverless.yaml": "infra",
1418
+ "sam.yaml": "infra",
1419
+ "samconfig.toml": "infra",
1420
+ "cdk.json": "infra",
1421
+ "pulumi.yaml": "infra",
1422
+ "terraform.tf": "infra",
1423
+ // Agent / project meta (also covered by .md extension)
1424
+ "agents.md": "markdown",
1425
+ "claude.md": "markdown",
1426
+ "codeowners": "other-config",
1427
+ ".nvmrc": "other-config",
1428
+ ".node-version": "other-config",
1429
+ ".python-version": "other-config",
1430
+ "runtime.txt": "other-config",
1431
+ ".tool-versions": "other-config",
1432
+ "editorconfig": "other-config",
1433
+ ".editorconfig": "other-config"
1434
+ };
1435
+ var BASENAME_PREFIX = [
1436
+ { re: /^dockerfile(\.|$)/i, category: "docker" },
1437
+ { re: /^docker-compose(\.|$)/i, category: "compose" },
1438
+ { re: /^compose\.(ya?ml)$/i, category: "compose" },
1439
+ { re: /^tsconfig(\..+)?\.json$/i, category: "build-config" },
1440
+ { re: /^jsconfig(\..+)?\.json$/i, category: "build-config" },
1441
+ { re: /^vite\.config\./i, category: "build-config" },
1442
+ { re: /^vitest\.config\./i, category: "build-config" },
1443
+ { re: /^jest\.config\./i, category: "build-config" },
1444
+ { re: /^webpack\.config\./i, category: "build-config" },
1445
+ { re: /^rollup\.config\./i, category: "build-config" },
1446
+ { re: /^next\.config\./i, category: "build-config" },
1447
+ { re: /^nuxt\.config\./i, category: "build-config" },
1448
+ { re: /^astro\.config\./i, category: "build-config" },
1449
+ { re: /^eslint\.config\./i, category: "build-config" },
1450
+ { re: /^\.eslintrc/i, category: "build-config" },
1451
+ { re: /^openapi\./i, category: "api-contract" },
1452
+ { re: /^swagger\./i, category: "api-contract" },
1453
+ { re: /^requirements(-\w+)?\.txt$/i, category: "manifest" },
1454
+ { re: /^dockerfile/i, category: "docker" }
1455
+ ];
1456
+ var PATH_PATTERNS = [
1457
+ // GitHub Actions / workflows
1458
+ { re: /^\.github\/workflows\/.+\.ya?ml$/i, category: "ci" },
1459
+ { re: /^\.github\/actions\/.+/i, category: "ci" },
1460
+ { re: /^\.circleci\/.+\.ya?ml$/i, category: "ci" },
1461
+ { re: /^\.buildkite\/.+/i, category: "ci" },
1462
+ // Helm / k8s (values + charts — not every template blob)
1463
+ { re: /^charts\/[^/]+\/(chart\.ya?ml|values.*\.ya?ml)$/i, category: "infra" },
1464
+ { re: /^deploy\/.+\.ya?ml$/i, category: "infra" },
1465
+ { re: /^k8s\/.+\.ya?ml$/i, category: "infra" },
1466
+ { re: /^kubernetes\/.+\.ya?ml$/i, category: "infra" },
1467
+ { re: /^infra\/.+\.ya?ml$/i, category: "infra" },
1468
+ { re: /^terraform\/.+\.tf$/i, category: "infra" },
1469
+ // API contracts by path
1470
+ { re: /(^|\/)(openapi|swagger)(\.|\/)/i, category: "api-contract" },
1471
+ { re: /(^|\/)api(-)?(spec|schema|contracts?)\//i, category: "api-contract" }
1472
+ ];
1473
+ var API_EXTENSIONS = /* @__PURE__ */ new Set([".graphql", ".gql", ".proto", ".avsc", ".thrift"]);
1474
+ var IMPORTANCE = {
1475
+ markdown: 0.1,
1476
+ text: 0.06,
1477
+ "env-example": 0.12,
1478
+ manifest: 0.16,
1479
+ docker: 0.14,
1480
+ compose: 0.15,
1481
+ ci: 0.14,
1482
+ "build-config": 0.11,
1483
+ "api-contract": 0.13,
1484
+ "task-runner": 0.13,
1485
+ workspace: 0.14,
1486
+ infra: 0.12,
1487
+ "other-config": 0.08
1488
+ };
1489
+ function toPosix2(p) {
1490
+ return p.split(path10.sep).join("/");
1491
+ }
1492
+ function buildRootIgnore2(root, exclude) {
1493
+ const ig = ignore();
1494
+ const gitignorePath = path10.join(root, ".gitignore");
1495
+ if (fs10.existsSync(gitignorePath)) {
1496
+ try {
1497
+ ig.add(fs10.readFileSync(gitignorePath, "utf8"));
1498
+ } catch {
1499
+ }
1500
+ }
1501
+ if (exclude.length) ig.add(exclude);
1502
+ return ig;
1503
+ }
1504
+ function classifyProjectContext(relOrName) {
1505
+ const rel2 = relOrName.replace(/\\/g, "/");
1506
+ const base = path10.basename(rel2);
1507
+ const baseLower = base.toLowerCase();
1508
+ if (/^\.env($|\.)/i.test(base) && !isEnvExampleName(baseLower)) return null;
1509
+ if (SKIP_FILES.has(baseLower)) return null;
1510
+ if (isEnvExampleName(baseLower)) return "env-example";
1511
+ const exact = EXACT_BASENAME[baseLower] ?? EXACT_BASENAME[base];
1512
+ if (exact) return exact;
1513
+ for (const { re, category } of BASENAME_PREFIX) {
1514
+ if (re.test(base)) return category;
1515
+ }
1516
+ for (const { re, category } of PATH_PATTERNS) {
1517
+ if (re.test(rel2)) return category;
1518
+ }
1519
+ const ext = path10.extname(baseLower);
1520
+ if (DOC_EXTENSIONS.has(ext)) return ext === ".txt" ? "text" : "markdown";
1521
+ if (API_EXTENSIONS.has(ext)) return "api-contract";
1522
+ if ((ext === ".yaml" || ext === ".yml" || ext === ".json") && /openapi|swagger|asyncapi/i.test(base)) {
1523
+ return "api-contract";
1524
+ }
1525
+ if (/^docker-compose\..+\.ya?ml$/i.test(base) || /^compose\..+\.ya?ml$/i.test(base)) {
1526
+ return "compose";
1527
+ }
1528
+ return null;
1529
+ }
1530
+ function isEnvExampleName(baseLower) {
1531
+ if (ENV_EXAMPLE_NAMES.has(baseLower)) return true;
1532
+ if (/^(\.)?env\.(example|sample|template)(\.|$)/i.test(baseLower)) return true;
1533
+ if (/^example\.env/i.test(baseLower)) return true;
1534
+ return false;
1535
+ }
1536
+ function discoverDocs(options) {
1537
+ const root = path10.resolve(options.root);
1538
+ const maxFiles = options.maxFiles ?? DOC_MAX_FILES;
1539
+ const rootIg = buildRootIgnore2(root, options.exclude ?? []);
1540
+ const scopeAbs = (options.paths?.length ? options.paths.map((p) => path10.resolve(root, p)) : [root]).filter(
1541
+ (p) => fs10.existsSync(p)
1542
+ );
1543
+ const found = /* @__PURE__ */ new Map();
1544
+ const consider = (abs) => {
1545
+ if (found.size >= maxFiles) return;
1546
+ const rel2 = toPosix2(path10.relative(root, abs));
1547
+ if (!rel2 || rel2.startsWith("..")) return;
1548
+ if (rootIg.ignores(rel2)) return;
1549
+ const base = path10.basename(abs);
1550
+ if (SKIP_FILES.has(base.toLowerCase())) return;
1551
+ const category = classifyProjectContext(rel2);
1552
+ if (!category) return;
1553
+ try {
1554
+ const st = fs10.statSync(abs);
1555
+ if (!st.isFile() || st.size > DOC_FILE_MAX_BYTES) return;
1556
+ if (st.size === 0) return;
1557
+ } catch {
1558
+ return;
1559
+ }
1560
+ found.set(rel2, { rel: rel2, abs, kind: category, category });
1561
+ };
1562
+ const walk = (dir, depth) => {
1563
+ if (found.size >= maxFiles || depth > 12) return;
1564
+ let entries;
1565
+ try {
1566
+ entries = fs10.readdirSync(dir, { withFileTypes: true });
1567
+ } catch {
1568
+ return;
1569
+ }
1570
+ for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
1571
+ if (found.size >= maxFiles) return;
1572
+ const abs = path10.join(dir, entry.name);
1573
+ const rel2 = toPosix2(path10.relative(root, abs));
1574
+ if (entry.isDirectory()) {
1575
+ if (SKIP_DIRS.has(entry.name)) continue;
1576
+ if (rel2 && rootIg.ignores(`${rel2}/`) && !rel2.startsWith(".github")) continue;
1577
+ walk(abs, depth + 1);
1578
+ } else if (entry.isFile()) {
1579
+ consider(abs);
1580
+ }
1581
+ }
1582
+ };
1583
+ for (const scope of scopeAbs) {
1584
+ try {
1585
+ const st = fs10.statSync(scope);
1586
+ if (st.isDirectory()) walk(scope, 0);
1587
+ else if (st.isFile()) consider(scope);
1588
+ } catch {
1589
+ }
1590
+ }
1591
+ return [...found.values()].sort((a, b) => a.rel.localeCompare(b.rel));
1592
+ }
1593
+ function prepareDocBody(rel2, raw, category) {
1594
+ const scrubbed = redactSecrets(raw);
1595
+ if (scrubbed.length <= DOC_BODY_MAX_CHARS) return scrubbed;
1596
+ if (category === "manifest" && path10.basename(rel2).toLowerCase() === "package.json") {
1597
+ return summarizePackageJson(scrubbed);
1598
+ }
1599
+ if (category === "api-contract" && /\.(ya?ml|json)$/i.test(rel2)) {
1600
+ return scrubbed.slice(0, DOC_BODY_MAX_CHARS);
1601
+ }
1602
+ if (category === "ci") {
1603
+ return scrubbed.slice(0, DOC_BODY_MAX_CHARS);
1604
+ }
1605
+ return scrubbed.slice(0, DOC_BODY_MAX_CHARS);
1606
+ }
1607
+ function summarizePackageJson(raw) {
1608
+ try {
1609
+ const o = JSON.parse(raw);
1610
+ const keep = {};
1611
+ for (const k of [
1612
+ "name",
1613
+ "version",
1614
+ "description",
1615
+ "private",
1616
+ "type",
1617
+ "packageManager",
1618
+ "engines",
1619
+ "workspaces",
1620
+ "scripts",
1621
+ "dependencies",
1622
+ "devDependencies",
1623
+ "peerDependencies",
1624
+ "optionalDependencies",
1625
+ "main",
1626
+ "bin",
1627
+ "exports"
1628
+ ]) {
1629
+ if (o[k] !== void 0) keep[k] = o[k];
1630
+ }
1631
+ const s = JSON.stringify(keep, null, 2);
1632
+ return s.length <= DOC_BODY_MAX_CHARS ? s : s.slice(0, DOC_BODY_MAX_CHARS);
1633
+ } catch {
1634
+ return raw.slice(0, DOC_BODY_MAX_CHARS);
1635
+ }
1636
+ }
1637
+ function documentNodesFromDocs(docs) {
1638
+ const nodes = [];
1639
+ for (const d of docs) {
1640
+ let raw = "";
1641
+ try {
1642
+ raw = fs10.readFileSync(d.abs, "utf8");
1643
+ } catch {
1644
+ continue;
1645
+ }
1646
+ const category = d.category ?? d.kind;
1647
+ const scrubbed = prepareDocBody(d.rel, raw, category);
1648
+ if (!scrubbed.trim()) continue;
1649
+ const name = path10.basename(d.rel);
1650
+ const qualifiedName = d.rel.replace(/\\/g, "/");
1651
+ const id = nodeId({
1652
+ kind: "document",
1653
+ qualifiedName,
1654
+ file: qualifiedName,
1655
+ signature: category
1656
+ });
1657
+ const lineCount = Math.max(1, scrubbed.split("\n").length);
1658
+ const langTag = category === "markdown" ? "md" : category === "env-example" ? "env" : category === "manifest" ? "manifest" : category === "docker" || category === "compose" ? "docker" : category === "ci" ? "ci" : category === "api-contract" ? "api" : category === "build-config" ? "config" : category === "task-runner" ? "task" : category === "workspace" ? "workspace" : category === "infra" ? "infra" : "txt";
1659
+ nodes.push({
1660
+ id,
1661
+ kind: "document",
1662
+ name,
1663
+ qualifiedName,
1664
+ file: qualifiedName,
1665
+ span: { start: 1, end: lineCount },
1666
+ lang: langTag,
1667
+ signature: `document:${category}`,
1668
+ doc: scrubbed,
1669
+ importance: IMPORTANCE[category] ?? 0.05,
1670
+ centrality: { degree: 0, pagerank: 0, betweenness: 0, eigenvector: 0 },
1671
+ area: -1,
1672
+ isHub: false,
1673
+ tested: null
1674
+ });
1675
+ }
1676
+ return nodes.sort((a, b) => a.file.localeCompare(b.file) || a.id.localeCompare(b.id));
1677
+ }
1678
+ function shardTsProjects(root, files) {
1679
+ if (files.length === 0) return [];
1680
+ const cache = /* @__PURE__ */ new Map();
1681
+ const byKey = /* @__PURE__ */ new Map();
1682
+ for (const f of files) {
1683
+ const dir = path10.dirname(f.abs);
1684
+ const key = nearestProjectKey(root, dir, cache);
1685
+ const list = byKey.get(key);
1686
+ if (list) list.push(f);
1687
+ else byKey.set(key, [f]);
1688
+ }
1689
+ return [...byKey.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([key, shardFiles]) => ({
1690
+ key,
1691
+ files: [...shardFiles].sort((a, b) => a.rel.localeCompare(b.rel))
1692
+ }));
1693
+ }
1694
+ function nearestProjectKey(root, startDir, cache) {
1695
+ const hit = cache.get(startDir);
1696
+ if (hit !== void 0) return hit;
1697
+ let dir = startDir;
1698
+ const rootAbs = path10.resolve(root);
1699
+ while (true) {
1700
+ if (fs10.existsSync(path10.join(dir, "tsconfig.json")) || fs10.existsSync(path10.join(dir, "tsconfig.base.json")) || fs10.existsSync(path10.join(dir, "package.json"))) {
1701
+ const key = toPosix3(path10.relative(rootAbs, dir)) || ".";
1702
+ cache.set(startDir, key);
1703
+ return key;
1704
+ }
1705
+ if (dir === rootAbs || path10.dirname(dir) === dir) {
1706
+ cache.set(startDir, ".");
1707
+ return ".";
1708
+ }
1709
+ dir = path10.dirname(dir);
1710
+ }
1711
+ }
1712
+ function toPosix3(p) {
1713
+ return p.split(path10.sep).join("/");
1714
+ }
1715
+
1716
+ // src/engine/timing.ts
1717
+ var StageTimer = class {
1718
+ marks = /* @__PURE__ */ new Map();
1719
+ durations = {};
1720
+ start(name) {
1721
+ this.marks.set(name, nowMs());
1722
+ }
1723
+ end(name) {
1724
+ const t0 = this.marks.get(name);
1725
+ if (t0 === void 0) return 0;
1726
+ const ms = Math.round((nowMs() - t0) * 1e3) / 1e3;
1727
+ this.durations[name] = ms;
1728
+ this.marks.delete(name);
1729
+ return ms;
1730
+ }
1731
+ /** Run `fn` under a named stage. */
1732
+ async measure(name, fn) {
1733
+ this.start(name);
1734
+ try {
1735
+ return await fn();
1736
+ } finally {
1737
+ this.end(name);
1738
+ }
1739
+ }
1740
+ snapshot() {
1741
+ return { ...this.durations };
1742
+ }
1743
+ };
1744
+ function nowMs() {
1745
+ return Number(process.hrtime.bigint() / 1000n) / 1e3;
1746
+ }
1747
+
1748
+ // src/engine/summaries.ts
1749
+ var DEPEND_EDGES = /* @__PURE__ */ new Set(["call", "import", "extends", "implements", "references"]);
1750
+ var HUB_LIMIT = 40;
1751
+ function buildSummaries(graph) {
1752
+ const index = indexFor(graph);
1753
+ const candidates = graph.nodes.filter((n) => n.kind !== "file" && n.kind !== "external" && n.kind !== "document").sort((a, b) => b.importance - a.importance || a.qualifiedName.localeCompare(b.qualifiedName)).slice(0, HUB_LIMIT);
1754
+ const hubs = candidates.map((n) => {
1755
+ const d1 = reverseDependents(index, n.id, 1);
1756
+ const d2 = reverseDependents(index, n.id, 2);
1757
+ const files = /* @__PURE__ */ new Set();
1758
+ for (const id of d2) {
1759
+ const node = index.node(id);
1760
+ if (node?.file) files.add(node.file);
1761
+ }
1762
+ return {
1763
+ id: n.id,
1764
+ name: n.qualifiedName,
1765
+ kind: n.kind,
1766
+ file: n.file,
1767
+ importance: n.importance,
1768
+ direct: d1.size,
1769
+ depth2: d2.size,
1770
+ files: files.size,
1771
+ tested: n.tested
1772
+ };
1773
+ });
1774
+ hubs.sort((a, b) => b.importance - a.importance || a.name.localeCompare(b.name));
1775
+ return { hubs };
1776
+ }
1777
+ function reverseDependents(index, rootId, maxDepth) {
1778
+ const affected = /* @__PURE__ */ new Set();
1779
+ let frontier = [rootId];
1780
+ for (let depth = 1; depth <= maxDepth; depth++) {
1781
+ const next = [];
1782
+ for (const id of frontier) {
1783
+ for (const e of index.in(id)) {
1784
+ if (!DEPEND_EDGES.has(e.kind)) continue;
1785
+ if (e.src === rootId || affected.has(e.src)) continue;
1786
+ affected.add(e.src);
1787
+ next.push(e.src);
1788
+ }
1789
+ }
1790
+ frontier = next;
1791
+ if (frontier.length === 0) break;
1792
+ }
1793
+ return affected;
1794
+ }
1795
+ var emptyNode = (partial) => ({
1796
+ ...partial,
1797
+ span: partial.span ?? { start: 1, end: 1 },
1798
+ importance: 0,
1799
+ centrality: { degree: 0, pagerank: 0, betweenness: 0, eigenvector: 0 },
1800
+ area: -1,
1801
+ isHub: false,
1802
+ tested: null
1803
+ });
1804
+ function extractManifests(root, opts = {}) {
1805
+ const absRoot = path10.resolve(root);
1806
+ const ig = buildRootIgnore3(absRoot, opts.exclude ?? []);
1807
+ const scopes = (opts.paths?.length ? opts.paths : ["."]).map((p) => path10.resolve(absRoot, p)).filter((p) => fs10.existsSync(p));
1808
+ const found = /* @__PURE__ */ new Map();
1809
+ for (const scope of scopes) {
1810
+ walkManifests(absRoot, scope, ig, found);
1811
+ }
1812
+ const nodes = /* @__PURE__ */ new Map();
1813
+ const edges = /* @__PURE__ */ new Map();
1814
+ let deps = 0;
1815
+ for (const rel2 of [...found.keys()].sort()) {
1816
+ const abs = found.get(rel2);
1817
+ const base = path10.posix.basename(rel2);
1818
+ try {
1819
+ if (base === "package.json") {
1820
+ deps += ingestPackageJson(rel2, abs, nodes, edges);
1821
+ } else if (base === "go.mod") {
1822
+ deps += ingestGoMod(rel2, abs, nodes, edges);
1823
+ }
1824
+ } catch {
1825
+ }
1826
+ }
1827
+ return {
1828
+ nodes: [...nodes.values()].sort((a, b) => a.id.localeCompare(b.id)),
1829
+ edges: [...edges.values()].sort(
1830
+ (a, b) => a.kind.localeCompare(b.kind) || a.src.localeCompare(b.src) || a.dst.localeCompare(b.dst)
1831
+ ),
1832
+ files: found.size,
1833
+ deps
1834
+ };
1835
+ }
1836
+ function ingestPackageJson(rel2, abs, nodes, edges) {
1837
+ const raw = JSON.parse(fs10.readFileSync(abs, "utf8"));
1838
+ const pkgName = raw.name && String(raw.name).trim() || path10.posix.dirname(rel2) || ".";
1839
+ const localId = nodeId({ kind: "package", qualifiedName: pkgName, file: rel2 });
1840
+ nodes.set(
1841
+ localId,
1842
+ emptyNode({
1843
+ id: localId,
1844
+ kind: "package",
1845
+ name: pkgName.includes("/") ? pkgName.split("/").pop() : pkgName,
1846
+ qualifiedName: pkgName,
1847
+ file: rel2,
1848
+ lang: "json"
1849
+ })
1850
+ );
1851
+ const fileId = nodeId({ kind: "file", qualifiedName: rel2, file: rel2 });
1852
+ nodes.set(
1853
+ fileId,
1854
+ emptyNode({
1855
+ id: fileId,
1856
+ kind: "file",
1857
+ name: path10.posix.basename(rel2),
1858
+ qualifiedName: rel2,
1859
+ file: rel2,
1860
+ lang: "json"
1861
+ })
1862
+ );
1863
+ addEdge(edges, "contains", fileId, localId, 1);
1864
+ let n = 0;
1865
+ const depMaps = [raw.dependencies, raw.peerDependencies, raw.optionalDependencies];
1866
+ const names = /* @__PURE__ */ new Set();
1867
+ for (const m of depMaps) {
1868
+ if (!m || typeof m !== "object") continue;
1869
+ for (const name of Object.keys(m)) {
1870
+ if (name) names.add(name);
1871
+ }
1872
+ }
1873
+ for (const name of [...names].sort()) {
1874
+ const extId = ensureExternal(nodes, name);
1875
+ addEdge(edges, "import", localId, extId, 1);
1876
+ n++;
1877
+ }
1878
+ return n;
1879
+ }
1880
+ function ingestGoMod(rel2, abs, nodes, edges) {
1881
+ const text = fs10.readFileSync(abs, "utf8");
1882
+ let moduleName = path10.posix.dirname(rel2) || ".";
1883
+ const moduleMatch = /^\s*module\s+(\S+)/m.exec(text);
1884
+ if (moduleMatch) moduleName = moduleMatch[1];
1885
+ const localId = nodeId({ kind: "package", qualifiedName: moduleName, file: rel2 });
1886
+ nodes.set(
1887
+ localId,
1888
+ emptyNode({
1889
+ id: localId,
1890
+ kind: "package",
1891
+ name: moduleName.split("/").pop() ?? moduleName,
1892
+ qualifiedName: moduleName,
1893
+ file: rel2,
1894
+ lang: "go"
1895
+ })
1896
+ );
1897
+ const fileId = nodeId({ kind: "file", qualifiedName: rel2, file: rel2 });
1898
+ nodes.set(
1899
+ fileId,
1900
+ emptyNode({
1901
+ id: fileId,
1902
+ kind: "file",
1903
+ name: path10.posix.basename(rel2),
1904
+ qualifiedName: rel2,
1905
+ file: rel2,
1906
+ lang: "go"
1907
+ })
1908
+ );
1909
+ addEdge(edges, "contains", fileId, localId, 1);
1910
+ const reqNames = /* @__PURE__ */ new Set();
1911
+ const block = /require\s*\(([\s\S]*?)\)/g;
1912
+ let bm;
1913
+ while ((bm = block.exec(text)) !== null) {
1914
+ for (const line of bm[1].split("\n")) {
1915
+ const m = /^\s*(\S+)\s+v\S+/.exec(line);
1916
+ if (m && !line.trim().startsWith("//")) reqNames.add(m[1]);
1917
+ }
1918
+ }
1919
+ const single = /^\s*require\s+(\S+)\s+v\S+/gm;
1920
+ let sm;
1921
+ while ((sm = single.exec(text)) !== null) reqNames.add(sm[1]);
1922
+ let n = 0;
1923
+ for (const name of [...reqNames].sort()) {
1924
+ const extId = ensureExternal(nodes, name);
1925
+ addEdge(edges, "import", localId, extId, 1);
1926
+ n++;
1927
+ }
1928
+ return n;
1929
+ }
1930
+ function ensureExternal(nodes, name) {
1931
+ const id = nodeId({ kind: "external", qualifiedName: name, file: "" });
1932
+ if (!nodes.has(id)) {
1933
+ nodes.set(
1934
+ id,
1935
+ emptyNode({
1936
+ id,
1937
+ kind: "external",
1938
+ name,
1939
+ qualifiedName: name,
1940
+ file: "",
1941
+ lang: "external"
1942
+ })
1943
+ );
1944
+ }
1945
+ return id;
1946
+ }
1947
+ function addEdge(edges, kind, src, dst, confidence) {
1948
+ if (src === dst) return;
1949
+ const id = edgeId(kind, src, dst);
1950
+ if (edges.has(id)) return;
1951
+ edges.set(id, {
1952
+ id,
1953
+ kind,
1954
+ src,
1955
+ dst,
1956
+ resolution: "heuristic",
1957
+ confidence,
1958
+ epistemic: "declared",
1959
+ count: 1
1960
+ });
1961
+ }
1962
+ function buildRootIgnore3(root, exclude) {
1963
+ const ig = ignore();
1964
+ const gitignorePath = path10.join(root, ".gitignore");
1965
+ if (fs10.existsSync(gitignorePath)) {
1966
+ try {
1967
+ ig.add(fs10.readFileSync(gitignorePath, "utf8"));
1968
+ } catch {
1969
+ }
1970
+ }
1971
+ if (exclude.length) ig.add(exclude);
1972
+ return ig;
1973
+ }
1974
+ function walkManifests(root, dir, ig, found) {
1975
+ let entries;
1976
+ try {
1977
+ entries = fs10.readdirSync(dir, { withFileTypes: true });
1978
+ } catch {
1979
+ return;
1980
+ }
1981
+ for (const entry of entries) {
1982
+ const abs = path10.join(dir, entry.name);
1983
+ const rel2 = path10.relative(root, abs).split(path10.sep).join("/");
1984
+ if (entry.isDirectory()) {
1985
+ if (SKIP_DIRS.has(entry.name)) continue;
1986
+ if (rel2 && ig.ignores(`${rel2}/`)) continue;
1987
+ walkManifests(root, abs, ig, found);
1988
+ } else if (entry.isFile()) {
1989
+ const base = entry.name;
1990
+ if (base !== "package.json" && base !== "go.mod") continue;
1991
+ if (rel2 && ig.ignores(rel2)) continue;
1992
+ found.set(rel2, abs);
1993
+ }
1994
+ }
1995
+ }
1996
+ async function hashFilesParallel(jobs, concurrency) {
1997
+ if (jobs.length === 0) return [];
1998
+ const limit = Math.max(
1999
+ 1,
2000
+ Math.min(concurrency ?? Math.max(1, (os.cpus()?.length ?? 4) * 2), jobs.length)
2001
+ );
2002
+ const out = new Array(jobs.length);
2003
+ let next = 0;
2004
+ async function worker() {
2005
+ for (; ; ) {
2006
+ const i = next++;
2007
+ if (i >= jobs.length) return;
2008
+ const job = jobs[i];
2009
+ try {
2010
+ const buf = await fs7.readFile(job.abs);
2011
+ out[i] = { rel: job.rel, hash: hashBytes(buf), ok: true };
2012
+ } catch {
2013
+ out[i] = { rel: job.rel, hash: "", ok: false };
2014
+ }
2015
+ }
2016
+ }
2017
+ await Promise.all(Array.from({ length: limit }, () => worker()));
2018
+ return out;
2019
+ }
2020
+
2021
+ // src/engine/build.ts
2022
+ async function buildGraph(options) {
2023
+ const timer = new StageTimer();
2024
+ timer.start("total");
2025
+ const root = path10.resolve(options.root);
2026
+ timer.start("discover");
2027
+ const files = discover({
2028
+ root,
2029
+ only: options.only,
2030
+ exclude: options.exclude,
2031
+ paths: options.paths
2032
+ });
2033
+ timer.end("discover");
2034
+ const limits = resolveLimits(options.limits);
2035
+ if (limits.maxFiles > 0 && files.length > limits.maxFiles) {
2036
+ throw new ResourceLimitError(
2037
+ `graph build stopped: ${files.length.toLocaleString()} files exceed the ${limits.maxFiles.toLocaleString()}-file limit. Scope the build (pass sub-paths, add --exclude globs, or --only <langs>), or set VG_MAX_FILES to raise the limit (0 disables it).`
2038
+ );
2039
+ }
2040
+ checkMemoryBudget("discovery", limits.memoryBudgetMb);
2041
+ const grammars = grammarSetVersion();
2042
+ const cache = loadCache(root, {
2043
+ toolVersion: VERSION,
2044
+ grammars,
2045
+ disabled: options.noCache
2046
+ });
2047
+ timer.start("hash");
2048
+ const hashes = /* @__PURE__ */ new Map();
2049
+ const fileStats = [];
2050
+ const toParse = [];
2051
+ const reused = [];
2052
+ const buildWarnings = [];
2053
+ let statHits = 0;
2054
+ const pendingHash = [];
2055
+ for (const file of files) {
2056
+ let stat;
2057
+ try {
2058
+ stat = fs10.statSync(file.abs);
2059
+ } catch {
2060
+ continue;
2061
+ }
2062
+ if (limits.maxFileBytes > 0 && stat.size > limits.maxFileBytes) {
2063
+ fileStats.push({
2064
+ rel: file.rel,
2065
+ size: stat.size,
2066
+ mtimeMs: stat.mtimeMs,
2067
+ hash: hashString(`vg:oversize:${stat.size}`)
2068
+ });
2069
+ buildWarnings.push(
2070
+ `${file.rel}: skipped \u2014 ${formatBytes(stat.size)} exceeds the ${formatBytes(limits.maxFileBytes)} per-file limit (set VG_MAX_FILE_BYTES to raise it, 0 to disable)`
2071
+ );
2072
+ continue;
2073
+ }
2074
+ const byStat = options.noCache ? void 0 : cache.getByStat(file.rel, stat.mtimeMs, stat.size);
2075
+ if (byStat) {
2076
+ hashes.set(file.rel, byStat.hash);
2077
+ if (byStat.parse.hash !== byStat.hash) byStat.parse.hash = byStat.hash;
2078
+ fileStats.push({
2079
+ rel: file.rel,
2080
+ size: stat.size,
2081
+ mtimeMs: stat.mtimeMs,
2082
+ hash: byStat.hash
2083
+ });
2084
+ reused.push(byStat.parse);
2085
+ statHits++;
2086
+ continue;
2087
+ }
2088
+ pendingHash.push({ file, size: stat.size, mtimeMs: stat.mtimeMs });
2089
+ }
2090
+ if (pendingHash.length) {
2091
+ const jobs = pendingHash.map((p) => ({ rel: p.file.rel, abs: p.file.abs }));
2092
+ const results = await hashFilesParallel(jobs, options.jobs);
2093
+ const byRel = new Map(results.map((r) => [r.rel, r]));
2094
+ for (const p of pendingHash) {
2095
+ const r = byRel.get(p.file.rel);
2096
+ if (!r || !r.ok) continue;
2097
+ hashes.set(p.file.rel, r.hash);
2098
+ fileStats.push({
2099
+ rel: p.file.rel,
2100
+ size: p.size,
2101
+ mtimeMs: p.mtimeMs,
2102
+ hash: r.hash
2103
+ });
2104
+ const cached = cache.get(p.file.rel, r.hash);
2105
+ if (cached) {
2106
+ reused.push(cached);
2107
+ cache.set(p.file.rel, cached, { mtimeMs: p.mtimeMs, size: p.size });
2108
+ } else {
2109
+ toParse.push(p.file);
2110
+ }
2111
+ }
2112
+ }
2113
+ timer.end("hash");
2114
+ timer.start("parse");
2115
+ const parsedNew = await parseFiles(toParse, {
2116
+ jobs: options.jobs,
2117
+ inline: options.inline,
2118
+ onProgress: options.onParseProgress,
2119
+ grammarsDir: options.grammarsDir,
2120
+ memoryBudgetMb: limits.memoryBudgetMb
2121
+ });
2122
+ timer.end("parse");
2123
+ checkMemoryBudget("parse", limits.memoryBudgetMb);
2124
+ for (const p of parsedNew) {
2125
+ const st = fileStats.find((f) => f.rel === p.rel);
2126
+ cache.set(p.rel, p, st ? { mtimeMs: st.mtimeMs, size: st.size } : void 0);
2127
+ }
2128
+ const currentRels = new Set(files.map((f) => f.rel));
2129
+ cache.prune(currentRels);
2130
+ cache.save();
2131
+ const parses = [...reused, ...parsedNew].sort(
2132
+ (a, b) => a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0
2133
+ );
2134
+ const warnings = [...buildWarnings, ...parses.flatMap((p) => p.warnings ?? [])];
2135
+ timer.start("resolve");
2136
+ const moduleResolver = buildModuleResolver(root, new Set(parses.map((p) => p.rel)));
2137
+ const resolved = resolve(parses, moduleResolver);
2138
+ timer.end("resolve");
2139
+ checkMemoryBudget("resolve", limits.memoryBudgetMb);
2140
+ const manifests = extractManifests(root, {
2141
+ exclude: options.exclude,
2142
+ paths: options.paths
2143
+ });
2144
+ if (manifests.files > 0) {
2145
+ const byId = new Map(resolved.nodes.map((n) => [n.id, n]));
2146
+ for (const n of manifests.nodes) {
2147
+ if (!byId.has(n.id)) {
2148
+ byId.set(n.id, n);
2149
+ resolved.nodes.push(n);
2150
+ }
2151
+ }
2152
+ const edgeById = new Map(resolved.edges.map((e) => [e.id, e]));
2153
+ for (const e of manifests.edges) {
2154
+ if (!edgeById.has(e.id)) {
2155
+ edgeById.set(e.id, e);
2156
+ resolved.edges.push(e);
2157
+ }
2158
+ }
2159
+ resolved.nodes.sort((a, b) => a.id.localeCompare(b.id));
2160
+ resolved.edges.sort(
2161
+ (a, b) => a.kind.localeCompare(b.kind) || a.src.localeCompare(b.src) || a.dst.localeCompare(b.dst)
2162
+ );
2163
+ }
2164
+ const nodeFileById = new Map(resolved.nodes.map((n) => [n.id, n.file]));
2165
+ let edges = resolved.edges;
2166
+ const resolvers = [...resolved.stats.resolvers];
2167
+ const preciseCoveredFiles = /* @__PURE__ */ new Set();
2168
+ let tscStats;
2169
+ const skipTsc = options.noTsc || options.fast;
2170
+ let tsFiles = skipTsc ? [] : files.filter((f) => (f.lang.id === "ts" || f.lang.id === "tsx" || f.lang.id === "js") && hashes.has(f.rel)).map((f) => ({ rel: f.rel, abs: f.abs }));
2171
+ if (limits.tscMaxFiles > 0 && tsFiles.length > limits.tscMaxFiles) {
2172
+ warnings.push(
2173
+ `typescript resolver skipped \u2014 ${tsFiles.length.toLocaleString()} TS/JS files exceed the ${limits.tscMaxFiles.toLocaleString()}-file limit; calls use the heuristic resolver (set VG_TSC_MAX_FILES to raise it, 0 to disable)`
2174
+ );
2175
+ tsFiles = [];
2176
+ }
2177
+ timer.start("tsc");
2178
+ if (tsFiles.length) {
2179
+ const shards = shardTsProjects(root, tsFiles);
2180
+ let filesCovered = 0;
2181
+ let calls = 0;
2182
+ let jsx = 0;
2183
+ let heritage = 0;
2184
+ let resolvedCount = 0;
2185
+ const coveredAll = /* @__PURE__ */ new Set();
2186
+ const preciseAll = [];
2187
+ for (const shard of shards) {
2188
+ const res = tsResolveEdges(root, shard.files, resolved.nodes);
2189
+ if (res.stats.files === 0) continue;
2190
+ filesCovered += res.stats.files;
2191
+ calls += res.stats.calls;
2192
+ jsx += res.stats.jsx;
2193
+ heritage += res.stats.heritage;
2194
+ resolvedCount += res.stats.resolved;
2195
+ for (const f of res.coveredFiles) coveredAll.add(f);
2196
+ preciseAll.push(...res.edges);
2197
+ }
2198
+ if (filesCovered > 0) {
2199
+ edges = mergePreciseEdges(edges, preciseAll, coveredAll, nodeFileById);
2200
+ for (const f of coveredAll) preciseCoveredFiles.add(f);
2201
+ if (!resolvers.includes("tsc")) resolvers.unshift("tsc");
2202
+ tscStats = {
2203
+ files: filesCovered,
2204
+ calls,
2205
+ jsx,
2206
+ heritage,
2207
+ resolved: resolvedCount,
2208
+ shards: shards.length
2209
+ };
2210
+ }
2211
+ checkMemoryBudget("typescript resolution", limits.memoryBudgetMb);
2212
+ }
2213
+ timer.end("tsc");
2214
+ const scip = options.noScip ? null : loadScipIndex(root, options.scip);
2215
+ let scipStats;
2216
+ if (scip) {
2217
+ const res = scipEdges(scip.index, resolved.nodes, toRepoRel);
2218
+ edges = mergePreciseEdges(edges, res.edges, res.coveredFiles, nodeFileById);
2219
+ for (const f of res.coveredFiles) preciseCoveredFiles.add(f);
2220
+ if (!resolvers.includes("scip")) resolvers.unshift("scip");
2221
+ scipStats = { ...res.stats, tool: scip.tool };
2222
+ }
2223
+ const linked = applyStaticTestLinkage(resolved.nodes, edges);
2224
+ let nodes = linked.nodes;
2225
+ const coverage = options.noCoverage ? null : loadCoverage(root, options.coverage);
2226
+ if (coverage) nodes = applyCoverage(nodes, coverage);
2227
+ const docs = discoverDocs({
2228
+ root,
2229
+ exclude: options.exclude,
2230
+ paths: options.paths
2231
+ });
2232
+ for (const d of docs) {
2233
+ try {
2234
+ const st = fs10.statSync(d.abs);
2235
+ const hash = hashBytes(fs10.readFileSync(d.abs));
2236
+ hashes.set(d.rel, hash);
2237
+ fileStats.push({ rel: d.rel, size: st.size, mtimeMs: st.mtimeMs, hash });
2238
+ } catch {
2239
+ }
2240
+ }
2241
+ const docNodes = documentNodesFromDocs(docs);
2242
+ if (docNodes.length) nodes = [...nodes, ...docNodes];
2243
+ timer.start("analyze");
2244
+ const analysis = analyze(nodes, linked.edges, {
2245
+ cluster: options.cluster,
2246
+ tier: options.analysisTier
2247
+ });
2248
+ timer.end("analyze");
2249
+ checkMemoryBudget("analysis", limits.memoryBudgetMb);
2250
+ const languages = [.../* @__PURE__ */ new Set([...parses.map((p) => p.lang), ...docNodes.map((n) => n.lang)])].sort();
2251
+ const edgeKinds = [...new Set(analysis.edges.map((e) => e.kind))].sort();
2252
+ const corpusHash = computeCorpusHash(parses, hashes);
2253
+ const nodeKindById = new Map(analysis.nodes.map((n) => [n.id, n.kind]));
2254
+ for (const e of analysis.edges) {
2255
+ e.epistemic = classifyEpistemic(e, nodeKindById.get(e.dst));
2256
+ }
2257
+ const toolchain = computeToolchain(grammars, resolvers);
2258
+ const generatedAt = options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
2259
+ const testsCount = linked.testFiles.length;
2260
+ const untestedCount = analysis.nodes.filter(
2261
+ (n) => (n.kind === "function" || n.kind === "method") && n.tested === false
2262
+ ).length;
2263
+ const graph = {
2264
+ schemaVersion: SCHEMA_VERSION,
2265
+ generatedAt,
2266
+ provenance: {
2267
+ tool: "vg",
2268
+ version: VERSION,
2269
+ grammars: Object.fromEntries(languages.map((l) => [l, grammars])),
2270
+ resolver: resolvers,
2271
+ deep: options.deep ?? false,
2272
+ corpusHash,
2273
+ toolchain
2274
+ },
2275
+ meta: {
2276
+ root: path10.basename(root) === "" ? "." : ".",
2277
+ languages,
2278
+ analysisTier: analysis.tier,
2279
+ counts: {
2280
+ nodes: analysis.nodes.length,
2281
+ edges: analysis.edges.length,
2282
+ areas: analysis.areas.length,
2283
+ tests: testsCount,
2284
+ untested: untestedCount
2285
+ },
2286
+ cluster: analysis.cluster,
2287
+ edgeKinds
2288
+ },
2289
+ nodes: analysis.nodes,
2290
+ edges: analysis.edges,
2291
+ areas: analysis.areas
2292
+ };
2293
+ const survivingIds = new Set(analysis.nodes.map((n) => n.id));
2294
+ const unknowns = resolved.unresolved.filter((u) => !preciseCoveredFiles.has(u.fromRel) && survivingIds.has(u.from)).map((u) => ({ from: u.from, name: u.name, kind: u.kind, count: u.count }));
2295
+ if (unknowns.length) graph.unknowns = unknowns;
2296
+ graph.summaries = buildSummaries(graph);
2297
+ {
2298
+ const facts = buildFacts(parses, analysis.nodes, analysis.edges);
2299
+ if (facts.length) graph.facts = facts;
2300
+ }
2301
+ if (!options.noGround) {
2302
+ const grounding = groundGraph(analysis.nodes, analysis.edges, parses);
2303
+ if (grounding.length) graph.grounding = grounding;
2304
+ }
2305
+ let indexResult;
2306
+ if (!options.noIndex) {
2307
+ timer.start("index");
2308
+ const wr = writeGraphIndex(root, graph);
2309
+ indexResult = { ok: wr.ok, path: wr.ok ? wr.path : void 0, reason: wr.reason };
2310
+ timer.end("index");
2311
+ }
2312
+ timer.end("total");
2313
+ const stages = timer.snapshot();
2314
+ return {
2315
+ graph,
2316
+ timing: { totalMs: stages.total ?? 0, stages },
2317
+ reparsed: parsedNew.length,
2318
+ reused: reused.length,
2319
+ statHits,
2320
+ totalFiles: files.length,
2321
+ fileStats,
2322
+ resolveStats: resolved.stats,
2323
+ tsc: tscStats,
2324
+ scip: scipStats,
2325
+ index: indexResult,
2326
+ warnings
2327
+ };
2328
+ }
2329
+ function toRepoRel(p) {
2330
+ return p.split("\\").join("/").replace(/^\.?\//, "");
2331
+ }
2332
+ function loadScipIndex(root, explicit) {
2333
+ const candidates = [
2334
+ explicit,
2335
+ path10.join(root, "index.scip"),
2336
+ path10.join(root, ".vibgrate", "index.scip")
2337
+ ].filter((p) => Boolean(p));
2338
+ for (const file of candidates) {
2339
+ const abs = path10.isAbsolute(file) ? file : path10.resolve(root, file);
2340
+ if (!fs10.existsSync(abs)) continue;
2341
+ try {
2342
+ const index = decodeScipIndex(new Uint8Array(fs10.readFileSync(abs)));
2343
+ if (index.documents.length) {
2344
+ return { index, tool: index.toolVersion ? `${index.toolName} ${index.toolVersion}` : index.toolName };
2345
+ }
2346
+ } catch {
2347
+ }
2348
+ }
2349
+ return null;
2350
+ }
2351
+ var PRECISE_KINDS = /* @__PURE__ */ new Set(["call", "references", "extends", "implements"]);
2352
+ function mergePreciseEdges(base, precise, coveredFiles, nodeFileById) {
2353
+ const byId = /* @__PURE__ */ new Map();
2354
+ for (const e of base) {
2355
+ if (PRECISE_KINDS.has(e.kind) && e.resolution === "heuristic") {
2356
+ const srcFile = nodeFileById.get(e.src);
2357
+ if (srcFile && coveredFiles.has(srcFile)) continue;
2358
+ }
2359
+ byId.set(e.id, e);
2360
+ }
2361
+ for (const e of precise) byId.set(e.id, e);
2362
+ return [...byId.values()].sort(
2363
+ (a, b) => a.kind.localeCompare(b.kind) || a.src.localeCompare(b.src) || a.dst.localeCompare(b.dst)
2364
+ );
2365
+ }
2366
+ function computeToolchain(grammars, resolvers) {
2367
+ const sortedResolvers = [...new Set(resolvers)].sort();
2368
+ return {
2369
+ schema: SCHEMA_VERSION,
2370
+ tool: VERSION,
2371
+ grammars,
2372
+ resolvers: sortedResolvers,
2373
+ fingerprint: shortId(
2374
+ canonicalize({
2375
+ schema: SCHEMA_VERSION,
2376
+ tool: VERSION,
2377
+ grammars,
2378
+ resolvers: sortedResolvers
2379
+ })
2380
+ )
2381
+ };
2382
+ }
2383
+ function computeCorpusHash(parses, hashes) {
2384
+ const fromParses = parses.map((p) => [p.rel, hashes.get(p.rel) ?? p.hash]);
2385
+ const fromHashes = [...hashes.entries()].map(([rel2, hash]) => [rel2, hash]);
2386
+ const byRel = /* @__PURE__ */ new Map();
2387
+ for (const [rel2, hash] of [...fromParses, ...fromHashes]) byRel.set(rel2, hash);
2388
+ const list = [...byRel.entries()].sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
2389
+ return hashString(canonicalize(list));
2390
+ }
2391
+
2392
+ // src/engine/verify.ts
2393
+ var PINNED = "1970-01-01T00:00:00.000Z";
2394
+ async function verifyDeterminism(opts) {
2395
+ const base = { root: opts.root, only: opts.only, exclude: opts.exclude, generatedAt: PINNED };
2396
+ const buildA = await buildGraph({ ...base, noCache: true, jobs: opts.jobs });
2397
+ const a = serializeGraph(buildA.graph);
2398
+ const b = serializeGraph((await buildGraph({ ...base, noCache: true, jobs: 1 })).graph);
2399
+ const cached = serializeGraph((await buildGraph({ ...base, noCache: false })).graph);
2400
+ const checks = [
2401
+ { name: "run-to-run determinism", ok: a === b, detail: diffHint(a, b) },
2402
+ { name: "cache safety (incremental == full)", ok: a === cached, detail: diffHint(a, cached) }
2403
+ ];
2404
+ const scoped = Boolean(opts.only?.length || opts.exclude?.length);
2405
+ const committed = scoped ? null : loadGraph(opts.root);
2406
+ const committedFp = committed?.provenance.toolchain?.fingerprint;
2407
+ const currentFp = buildA.graph.provenance.toolchain?.fingerprint;
2408
+ if (committedFp && currentFp) {
2409
+ const ok = committedFp === currentFp;
2410
+ checks.push({
2411
+ name: "toolchain fingerprint matches committed graph",
2412
+ ok,
2413
+ detail: ok ? void 0 : `committed toolchain ${committedFp} != current ${currentFp} (grammar/resolver versions differ \u2014 rebuild or align the toolchain)`
2414
+ });
2415
+ }
2416
+ return {
2417
+ ok: checks.every((c) => c.ok),
2418
+ checks,
2419
+ digest: hashString(a)
2420
+ };
2421
+ }
2422
+ function diffHint(x, y) {
2423
+ if (x === y) return void 0;
2424
+ const xl = x.split("\n");
2425
+ const yl = y.split("\n");
2426
+ const n = Math.max(xl.length, yl.length);
2427
+ for (let i = 0; i < n; i++) {
2428
+ if (xl[i] !== yl[i]) {
2429
+ return `first divergence at line ${i + 1}`;
2430
+ }
2431
+ }
2432
+ return "outputs differ in length";
2433
+ }
2434
+
2435
+ // src/util/exit.ts
2436
+ var ExitCode = {
2437
+ OK: 0,
2438
+ ERROR: 1,
2439
+ GATE_FAILED: 2,
2440
+ NOT_FOUND: 3,
2441
+ NON_DETERMINISTIC: 4,
2442
+ USAGE_ERROR: 5
2443
+ };
2444
+ var CliError = class extends Error {
2445
+ code;
2446
+ constructor(message, code = ExitCode.ERROR) {
2447
+ super(message);
2448
+ this.name = "CliError";
2449
+ this.code = code;
2450
+ }
2451
+ };
2452
+ function usageError(message) {
2453
+ return new CliError(message, ExitCode.USAGE_ERROR);
2454
+ }
2455
+ var SNAPSHOT_VERSION = "vg-freshness/1";
2456
+ function snapshotPath(root) {
2457
+ return path10.join(cacheDir(root), "freshness.json");
2458
+ }
2459
+ function writeSnapshot(root, corpusHash, fileStats, scope = {}) {
2460
+ const files = {};
2461
+ for (const f of fileStats) files[f.rel] = { size: f.size, mtimeMs: f.mtimeMs, hash: f.hash };
2462
+ const snapshot = { version: SNAPSHOT_VERSION, corpusHash, scope: pruneScope(scope), files };
2463
+ try {
2464
+ fs10.mkdirSync(cacheDir(root), { recursive: true });
2465
+ fs10.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
2466
+ } catch {
2467
+ }
2468
+ }
2469
+ function loadSnapshot(root) {
2470
+ try {
2471
+ const loaded = JSON.parse(fs10.readFileSync(snapshotPath(root), "utf8"));
2472
+ if (loaded.version === SNAPSHOT_VERSION && loaded.files && typeof loaded.corpusHash === "string") {
2473
+ return { ...loaded, scope: loaded.scope ?? {} };
2474
+ }
2475
+ } catch {
2476
+ }
2477
+ return null;
2478
+ }
2479
+ function hasDrift(drift) {
2480
+ return drift.changed.length + drift.added.length + drift.removed.length > 0;
2481
+ }
2482
+ function driftCount(drift) {
2483
+ return drift.changed.length + drift.added.length + drift.removed.length;
2484
+ }
2485
+ function probeFreshness(root) {
2486
+ const snapshot = loadSnapshot(root);
2487
+ if (!snapshot) return null;
2488
+ const { scope } = snapshot;
2489
+ let discovered;
2490
+ try {
2491
+ const code = discover({ root, only: scope.only, exclude: scope.exclude, paths: scope.paths });
2492
+ const docs = discoverDocs({ root, exclude: scope.exclude, paths: scope.paths });
2493
+ const byRel = /* @__PURE__ */ new Map();
2494
+ for (const f of code) byRel.set(f.rel, f.abs);
2495
+ for (const d of docs) {
2496
+ if (!byRel.has(d.rel)) byRel.set(d.rel, d.abs);
2497
+ }
2498
+ discovered = [...byRel.entries()].map(([rel2, abs]) => ({ rel: rel2, abs })).sort((a, b) => a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0);
2499
+ } catch {
2500
+ return null;
2501
+ }
2502
+ const drift = { changed: [], added: [], removed: [] };
2503
+ const seen = /* @__PURE__ */ new Set();
2504
+ let absorbed = false;
2505
+ for (const file of discovered) {
2506
+ seen.add(file.rel);
2507
+ const entry = snapshot.files[file.rel];
2508
+ if (!entry) {
2509
+ drift.added.push(file.rel);
2510
+ continue;
2511
+ }
2512
+ let stat;
2513
+ try {
2514
+ stat = fs10.statSync(file.abs);
2515
+ } catch {
2516
+ drift.removed.push(file.rel);
2517
+ continue;
2518
+ }
2519
+ if (stat.size === entry.size && stat.mtimeMs === entry.mtimeMs) continue;
2520
+ if (entry.hash.startsWith("vg:oversize:")) {
2521
+ const expected = `vg:oversize:${stat.size}`;
2522
+ if (entry.hash === expected) {
2523
+ snapshot.files[file.rel] = { size: stat.size, mtimeMs: stat.mtimeMs, hash: entry.hash };
2524
+ absorbed = true;
2525
+ } else {
2526
+ drift.changed.push(file.rel);
2527
+ }
2528
+ continue;
2529
+ }
2530
+ let hash = "";
2531
+ try {
2532
+ hash = hashBytes(fs10.readFileSync(file.abs));
2533
+ } catch {
2534
+ drift.removed.push(file.rel);
2535
+ continue;
2536
+ }
2537
+ if (hash === entry.hash) {
2538
+ snapshot.files[file.rel] = { size: stat.size, mtimeMs: stat.mtimeMs, hash };
2539
+ absorbed = true;
2540
+ } else {
2541
+ drift.changed.push(file.rel);
2542
+ }
2543
+ }
2544
+ for (const rel2 of Object.keys(snapshot.files)) {
2545
+ if (!seen.has(rel2)) drift.removed.push(rel2);
2546
+ }
2547
+ if (absorbed && !hasDrift(drift)) {
2548
+ try {
2549
+ fs10.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
2550
+ } catch {
2551
+ }
2552
+ }
2553
+ drift.changed.sort();
2554
+ drift.added.sort();
2555
+ drift.removed.sort();
2556
+ return { drift, scope, corpusHash: snapshot.corpusHash };
2557
+ }
2558
+ function pruneScope(scope) {
2559
+ const out = {};
2560
+ if (scope.only?.length) out.only = [...scope.only].sort();
2561
+ if (scope.exclude?.length) out.exclude = [...scope.exclude];
2562
+ if (scope.paths?.length) out.paths = [...scope.paths];
2563
+ if (scope.deep) out.deep = true;
2564
+ if (scope.noGround) out.noGround = true;
2565
+ if (scope.scip) out.scip = scope.scip;
2566
+ if (scope.noScip) out.noScip = true;
2567
+ if (scope.noTsc) out.noTsc = true;
2568
+ if (scope.cluster) out.cluster = scope.cluster;
2569
+ if (scope.grammarsDir) out.grammarsDir = scope.grammarsDir;
2570
+ return out;
2571
+ }
2572
+
2573
+ // src/install/content.ts
2574
+ var NUDGE_BEGIN = "<!-- vg:begin -->";
2575
+ var NUDGE_END = "<!-- vg:end -->";
2576
+ var INSTALL_CONTENT_VERSION = 2;
2577
+ var VERSION_MARKER_RE = /<!--\s*vg:v(\d+)\b[^>]*-->/;
2578
+ function versionMarker() {
2579
+ return `<!-- vg:v${INSTALL_CONTENT_VERSION} \xB7 managed by \`vg install\` \u2014 auto-refreshed when these instructions evolve; remove this line to opt out -->`;
2580
+ }
2581
+ function installedContentVersion(text) {
2582
+ const m = VERSION_MARKER_RE.exec(text);
2583
+ return m ? Number(m[1]) : null;
2584
+ }
2585
+ function clientFlag(client) {
2586
+ return client ? `--client=${client}` : "--client=<your-ai>";
2587
+ }
2588
+ function skillMarkdown(client) {
2589
+ const cf = clientFlag(client);
2590
+ return `---
2591
+ name: vg
2592
+ description: Query the local code graph (vg) for structure, impact, and navigation instead of grepping/reading many files.
2593
+ ---
2594
+
2595
+ ${versionMarker()}
2596
+
2597
+ # vg \u2014 the code map
2598
+
2599
+ This repo has a deterministic code graph built by \`vg\`. Prefer it over reading or
2600
+ grepping many files \u2014 it is smaller, more relevant context, and free.
2601
+
2602
+ ## Use the MCP tools \u2014 not the CLI
2603
+
2604
+ When the \`vg\` MCP server is registered (it is after \`vg install\`), **always call
2605
+ its read-only tools instead of shelling out to the CLI.** The server keeps the
2606
+ map parsed, the relation index warm, and the embedding model loaded across
2607
+ calls, so an MCP call answers in milliseconds \u2014 while every CLI invocation pays
2608
+ Node startup plus a fresh map parse, hundreds of times more. Tools:
2609
+ \`query_graph\`, \`get_node\`, \`impact_of\`, \`find_path\`, \`list_hubs\`, \`list_areas\`,
2610
+ \`get_graph_summary\`, \`search_symbols\`. They are side-effect-free and
2611
+ auto-approvable, and the server records which client is calling automatically.
2612
+ Reach for the CLI only when the MCP server is genuinely unavailable \u2014 never as
2613
+ the first resort.
2614
+
2615
+ ## CLI fallback \u2014 only when the MCP server is unavailable
2616
+
2617
+ If (and only if) no \`vg\` MCP server is available, use the CLI \u2014 and **always
2618
+ pass \`${cf}\`** so your calls are counted (that's how the CLI-vs-MCP split is
2619
+ measured and the tools improved):
2620
+
2621
+ - **Understand code:** \`vg "<question>" ${cf}\` \u2014 a budget-bounded, fact-annotated context block.
2622
+ - **Find a symbol:** \`vg show <name> ${cf}\` \u2014 what it is, what it calls, what calls it.
2623
+ - **Before changing something:** \`vg impact <name> ${cf}\` \u2014 what breaks if you change it.
2624
+ - **Navigate:** \`vg path <A> <B> ${cf}\`, \`vg tree <name> ${cf}\`.
2625
+
2626
+ ## Dependencies & library docs
2627
+
2628
+ - **Upgrade drift:** \`vg drift\` lists what is outdated across dependencies
2629
+ (offline; \`--online\` for currency). \`vg scan\` scores upgrade drift and
2630
+ \`vg report\` renders it (text | json | sarif | md).
2631
+ - **Version-correct docs:** \`vg lib <name>\` returns drift-annotated, version-
2632
+ specific usage docs for a library \u2014 inject these instead of guessing an API.
2633
+
2634
+ ### Library-docs discipline
2635
+
2636
+ When a task needs a library's API, use the docs tools before web search or
2637
+ training-data recall \u2014 they are official content matched to the version **this
2638
+ project has installed**, and they win when the two conflict.
2639
+
2640
+ - **Workflow:** \`resolve_library\` once per library, then \`library_docs\` with the
2641
+ returned \`targetId\` and a focused query (good: "zod refine custom error
2642
+ message"; bad: "zod"). Never guess a targetId.
2643
+ - **Budget:** at most **3 docs calls per task**. If 2 \`library_docs\` calls have
2644
+ not surfaced the section you need, read the package source under
2645
+ \`node_modules\` instead of searching again.
2646
+ - **Skip the docs tools** for language built-ins, stable well-known syntax, or
2647
+ an API already shown in the current context \u2014 they add nothing there.
2648
+
2649
+ ## Keep it fresh
2650
+
2651
+ The map keeps itself fresh: \`vg ask\` and the MCP tools detect changed files
2652
+ and rebuild it incrementally before answering \u2014 you can edit code and query
2653
+ immediately. Running \`vg\` after a large pull still warms everything in one go.
2654
+ The map lives at \`.vibgrate/graph.json\` and is deterministic (byte-identical
2655
+ across machines).
2656
+ `;
2657
+ }
2658
+ function nudgeMarkdown(smallRepo, client) {
2659
+ const cf = clientFlag(client);
2660
+ if (smallRepo) {
2661
+ return `${NUDGE_BEGIN}
2662
+ ${versionMarker()}
2663
+ ## Code navigation (vg)
2664
+
2665
+ This repo is small enough that searching files directly is fine. Still, before a
2666
+ change, the \`vg\` graph shows what breaks: use the \`vg\` MCP tools when the server
2667
+ is registered (\`impact_of\`, \`query_graph\` \u2014 the warm server answers far faster
2668
+ than spawning the CLI). Only without the server, fall back to the CLI and pass
2669
+ \`${cf}\` so the call is counted: \`vg impact <name> ${cf}\`,
2670
+ \`vg "<question>" ${cf}\`.
2671
+ ${NUDGE_END}`;
2672
+ }
2673
+ return `${NUDGE_BEGIN}
2674
+ ${versionMarker()}
2675
+ ## Code navigation (vg) \u2014 use the MCP tools first
2676
+
2677
+ A deterministic code graph is available. It is usually the faster, cheaper path
2678
+ than reading or grepping many files. **When the \`vg\` MCP server is registered,
2679
+ always use its tools** (\`query_graph\`, \`get_node\`, \`impact_of\`,
2680
+ \`search_symbols\`, \u2026) \u2014 the map, indexes, and embedding model stay warm across
2681
+ calls, so they answer in milliseconds, while every CLI invocation pays Node
2682
+ startup plus a fresh map parse. Fall back to the CLI only when the server is
2683
+ unavailable, and then pass \`${cf}\` so the call is counted:
2684
+
2685
+ - \`vg "<question>" ${cf}\` \u2014 a compact, fact-annotated context block for a question.
2686
+ - \`vg show <name> ${cf}\` \u2014 what a symbol is, its callers and callees.
2687
+ - \`vg impact <name> ${cf}\` \u2014 what breaks if you change it.
2688
+
2689
+ This is advisory, not mandatory \u2014 remove it with \`vg uninstall\` (or delete this
2690
+ block).
2691
+ ${NUDGE_END}`;
2692
+ }
2693
+ function mcpServerEntry(launch = { command: "vg", args: ["serve"] }) {
2694
+ return { command: launch.command, args: launch.args };
2695
+ }
2696
+
2697
+ // src/install/registry.ts
2698
+ init_cli_invocation();
2699
+ var ASSISTANTS = [
2700
+ {
2701
+ id: "claude",
2702
+ label: "Claude Code",
2703
+ skill: ".claude/skills/vg/SKILL.md",
2704
+ mcp: { file: ".mcp.json", key: "mcpServers" },
2705
+ nudge: { file: "CLAUDE.md", kind: "block" },
2706
+ markers: ["CLAUDE.md", ".claude"],
2707
+ homeMarkers: [".claude", ".claude.json"],
2708
+ bin: ["claude"]
2709
+ },
2710
+ {
2711
+ id: "cursor",
2712
+ label: "Cursor",
2713
+ mcp: { file: ".cursor/mcp.json", key: "mcpServers" },
2714
+ nudge: { file: ".cursor/rules/vg.mdc", kind: "file" },
2715
+ markers: [".cursor", ".cursorrules"],
2716
+ homeMarkers: [".cursor"],
2717
+ bin: ["cursor-agent"]
2718
+ },
2719
+ {
2720
+ id: "windsurf",
2721
+ label: "Windsurf",
2722
+ mcp: { file: ".windsurf/mcp.json", key: "mcpServers" },
2723
+ nudge: { file: ".windsurf/rules/vg.md", kind: "file" },
2724
+ markers: [".windsurf", ".windsurfrules"],
2725
+ homeMarkers: [".codeium/windsurf"]
2726
+ },
2727
+ {
2728
+ id: "vscode",
2729
+ label: "VS Code (Copilot Chat)",
2730
+ mcp: { file: ".vscode/mcp.json", key: "servers", vscode: true },
2731
+ nudge: { file: ".github/copilot-instructions.md", kind: "block" },
2732
+ markers: [".github/copilot-instructions.md"]
2733
+ },
2734
+ {
2735
+ id: "codex",
2736
+ label: "Codex",
2737
+ skill: ".codex/skills/vg/SKILL.md",
2738
+ nudge: { file: "AGENTS.md", kind: "block" },
2739
+ markers: [".codex"],
2740
+ homeMarkers: [".codex"],
2741
+ bin: ["codex"]
2742
+ },
2743
+ {
2744
+ id: "gemini",
2745
+ label: "Gemini CLI",
2746
+ skill: ".gemini/skills/vg/SKILL.md",
2747
+ nudge: { file: "GEMINI.md", kind: "block" },
2748
+ markers: ["GEMINI.md", ".gemini"],
2749
+ homeMarkers: [".gemini"],
2750
+ bin: ["gemini"]
2751
+ },
2752
+ // Skill + advisory AGENTS.md nudge (the broad-reach common denominator). MCP
2753
+ // registration for these hosts is host-specific and added as their formats
2754
+ // stabilise; the skill + nudge work today.
2755
+ { id: "grok", label: "Grok CLI", skill: ".grok/skills/vg/SKILL.md", nudge: { file: "AGENTS.md", kind: "block" }, markers: [".grok", "GROK.md"], homeMarkers: [".grok"], bin: ["grok"] },
2756
+ { id: "opencode", label: "OpenCode", skill: ".opencode/skills/vg/SKILL.md", nudge: { file: "AGENTS.md", kind: "block" }, markers: [".opencode", "opencode.json"], homeMarkers: [".config/opencode"], bin: ["opencode"] },
2757
+ { id: "kilo", label: "Kilo Code", skill: ".kilo/skills/vg/SKILL.md", nudge: { file: "AGENTS.md", kind: "block" }, markers: [".kilo", ".kilocode"], homeMarkers: [".kilocode"] },
2758
+ { id: "aider", label: "Aider", skill: ".aider/vg/SKILL.md", nudge: { file: "AGENTS.md", kind: "block" }, markers: [".aider", ".aider.conf.yml"], homeMarkers: [".aider.conf.yml"], bin: ["aider"] },
2759
+ { id: "factory", label: "Factory Droid", skill: ".factory/skills/vg/SKILL.md", nudge: { file: "AGENTS.md", kind: "block" }, markers: [".factory"], homeMarkers: [".factory"], bin: ["droid"] },
2760
+ { id: "trae", label: "Trae", skill: ".trae/skills/vg/SKILL.md", nudge: { file: "AGENTS.md", kind: "block" }, markers: [".trae"], homeMarkers: [".trae"] },
2761
+ { id: "kiro", label: "Kiro", skill: ".kiro/skills/vg/SKILL.md", nudge: { file: ".kiro/steering/vg.md", kind: "file" }, markers: [".kiro"], homeMarkers: [".kiro"] },
2762
+ { id: "amp", label: "Amp", skill: ".agents/skills/vg/SKILL.md", nudge: { file: "AGENTS.md", kind: "block" }, homeMarkers: [".config/amp"], bin: ["amp"] },
2763
+ { id: "kimi", label: "Kimi Code", skill: ".kimi/skills/vg/SKILL.md", nudge: { file: "AGENTS.md", kind: "block" }, markers: [".kimi"], homeMarkers: [".kimi"] },
2764
+ { id: "codebuddy", label: "CodeBuddy", skill: ".codebuddy/skills/vg/SKILL.md", nudge: { file: "CODEBUDDY.md", kind: "block" }, markers: ["CODEBUDDY.md", ".codebuddy"], homeMarkers: [".codebuddy"] },
2765
+ { id: "copilot-cli", label: "GitHub Copilot CLI", skill: ".copilot/skills/vg/SKILL.md", nudge: { file: "AGENTS.md", kind: "block" }, markers: [".copilot"], homeMarkers: [".copilot"], bin: ["copilot"] },
2766
+ { id: "pi", label: "Pi", skill: ".pi/agent/skills/vg/SKILL.md", nudge: { file: "AGENTS.md", kind: "block" }, markers: [".pi"], homeMarkers: [".pi"] },
2767
+ { id: "devin", label: "Devin CLI", skill: ".devin/skills/vg/SKILL.md", nudge: { file: "AGENTS.md", kind: "block" }, markers: [".devin"], homeMarkers: [".devin"] },
2768
+ { id: "hermes", label: "Hermes", skill: ".hermes/skills/vg/SKILL.md", nudge: { file: "AGENTS.md", kind: "block" }, markers: [".hermes"], homeMarkers: [".hermes"] },
2769
+ { id: "openclaw", label: "OpenClaw", skill: ".openclaw/skills/vg/SKILL.md", nudge: { file: "AGENTS.md", kind: "block" }, markers: [".openclaw"], homeMarkers: [".openclaw"], bin: ["openclaw"] },
2770
+ { id: "agents", label: "Agent-Skills (generic)", skill: ".agents/skills/vg/SKILL.md", nudge: { file: "AGENTS.md", kind: "block" }, markers: ["AGENTS.md", ".agents"] }
2771
+ ];
2772
+ function assistantById(id) {
2773
+ return ASSISTANTS.find((a) => a.id === id);
2774
+ }
2775
+ function detectAssistants(root, opts = {}) {
2776
+ const home = opts.home ?? os.homedir();
2777
+ const which = opts.which ?? whichOnPath;
2778
+ const found = [];
2779
+ for (const assistant of ASSISTANTS) {
2780
+ const repoHit = (assistant.markers ?? []).find((m) => fs10.existsSync(path10.join(root, m)));
2781
+ if (repoHit) {
2782
+ found.push({ assistant, via: "repo", marker: repoHit });
2783
+ continue;
2784
+ }
2785
+ const homeHit = (assistant.homeMarkers ?? []).find((m) => fs10.existsSync(path10.join(home, m)));
2786
+ if (homeHit) {
2787
+ found.push({ assistant, via: "home", marker: homeHit });
2788
+ continue;
2789
+ }
2790
+ const binHit = (assistant.bin ?? []).find((b) => which(b) !== null);
2791
+ if (binHit) found.push({ assistant, via: "path", marker: binHit });
2792
+ }
2793
+ return found;
2794
+ }
2795
+ function detectServeLaunch(which = whichOnPath) {
2796
+ const vg = which("vg");
2797
+ if (vg && isInstalledOwnBinary(vg)) return { command: "vg", args: ["serve"] };
2798
+ const vibgrate = which("vibgrate");
2799
+ if (vibgrate && isInstalledOwnBinary(vibgrate)) {
2800
+ return {
2801
+ command: "vibgrate",
2802
+ args: ["serve"],
2803
+ note: vg ? "`vg` on PATH belongs to another tool \u2014 registered the identical `vibgrate serve` instead" : "`vg` is not on PATH \u2014 registered the identical `vibgrate serve` instead"
2804
+ };
2805
+ }
2806
+ return {
2807
+ command: "npx",
2808
+ args: ["-y", "-p", "@vibgrate/cli", "vg", "serve"],
2809
+ note: "vg is not installed on PATH \u2014 registered an npx launcher. Install globally (`npm i -g @vibgrate/cli`) and rerun `vg install` for a faster startup."
2810
+ };
2811
+ }
2812
+ function installAssistant(a, opts) {
2813
+ const wrote = [];
2814
+ const skipped = [];
2815
+ let note;
2816
+ if (a.skill) {
2817
+ writeFileEnsured(path10.join(opts.root, a.skill), skillMarkdown(a.id));
2818
+ wrote.push(a.skill);
2819
+ }
2820
+ if (a.mcp) {
2821
+ const launch = opts.launch ?? detectServeLaunch();
2822
+ upsertMcp(path10.join(opts.root, a.mcp.file), a.mcp, launch);
2823
+ wrote.push(a.mcp.file);
2824
+ note = launch.note;
2825
+ } else {
2826
+ skipped.push("mcp (host-specific setup)");
2827
+ }
2828
+ if (a.nudge && opts.hook !== false) {
2829
+ writeNudge(path10.join(opts.root, a.nudge.file), a.nudge, opts.smallRepo, a.id);
2830
+ wrote.push(a.nudge.file);
2831
+ } else if (a.nudge) {
2832
+ skipped.push("nudge (--no-hook)");
2833
+ }
2834
+ return { wrote, skipped, note };
2835
+ }
2836
+ function uninstallAssistant(a, root, purge) {
2837
+ const removed = [];
2838
+ if (a.mcp) {
2839
+ const file = path10.join(root, a.mcp.file);
2840
+ if (removeMcp(file, a.mcp)) removed.push(a.mcp.file);
2841
+ }
2842
+ if (a.nudge) {
2843
+ const file = path10.join(root, a.nudge.file);
2844
+ if (a.nudge.kind === "block") {
2845
+ if (removeBlock(file)) removed.push(a.nudge.file);
2846
+ } else if (fs10.existsSync(file)) {
2847
+ fs10.rmSync(file);
2848
+ removed.push(a.nudge.file);
2849
+ }
2850
+ }
2851
+ if (purge && a.skill) {
2852
+ const file = path10.join(root, a.skill);
2853
+ if (fs10.existsSync(file)) {
2854
+ fs10.rmSync(file);
2855
+ removed.push(a.skill);
2856
+ }
2857
+ }
2858
+ return removed;
2859
+ }
2860
+ function writeFileEnsured(file, content) {
2861
+ fs10.mkdirSync(path10.dirname(file), { recursive: true });
2862
+ fs10.writeFileSync(file, content);
2863
+ }
2864
+ function writeNavigationConfig(root) {
2865
+ ensureVibgrateGitignore(root);
2866
+ const rel2 = path10.join(".vibgrate", "mcp-navigation.json");
2867
+ const doc = {
2868
+ _readme: "Deferred-loading config for agents embedding `vg serve` via the Claude API (defer_loading + tool-search). The hot navigation core stays in context; the rest load on demand \u2014 ~350-450 schema tokens/step vs ~1,881 for the full set. Hosts without defer_loading ignore this and serve the whole (already optimized) tool set. See docs/graph/VG-NAVIGATION-PROFILE.md.",
2869
+ hot_core: [...HOT_TOOLS],
2870
+ deferred: deferredToolNames(),
2871
+ toolset: navigationToolsetConfig()
2872
+ };
2873
+ writeFileEnsured(path10.join(root, rel2), `${JSON.stringify(doc, null, 2)}
2874
+ `);
2875
+ return rel2;
2876
+ }
2877
+ function upsertMcp(file, target, launch) {
2878
+ const config = readJson(file);
2879
+ const entry = mcpServerEntry(launch);
2880
+ const value = target.vscode ? { type: "stdio", ...entry } : entry;
2881
+ const bag = config[target.key] && typeof config[target.key] === "object" ? config[target.key] : {};
2882
+ bag.vg = value;
2883
+ config[target.key] = bag;
2884
+ writeFileEnsured(file, `${JSON.stringify(config, null, 2)}
2885
+ `);
2886
+ }
2887
+ function removeMcp(file, target) {
2888
+ if (!fs10.existsSync(file)) return false;
2889
+ const config = readJson(file);
2890
+ const bag = config[target.key];
2891
+ if (!bag || !(bag.vg !== void 0)) return false;
2892
+ delete bag.vg;
2893
+ writeFileEnsured(file, `${JSON.stringify(config, null, 2)}
2894
+ `);
2895
+ return true;
2896
+ }
2897
+ var SMALL_REPO_FILES = 150;
2898
+ function refreshInstalledInstructions(root, smallRepo) {
2899
+ const out = [];
2900
+ const staleVersion = (text, legacySignature) => {
2901
+ const v = installedContentVersion(text);
2902
+ if (v !== null) return v < INSTALL_CONTENT_VERSION ? v : null;
2903
+ return text.includes(legacySignature) ? 0 : null;
2904
+ };
2905
+ for (const a of ASSISTANTS) {
2906
+ if (a.skill) {
2907
+ const file = path10.join(root, a.skill);
2908
+ if (fs10.existsSync(file)) {
2909
+ const text = readTextSafe(file);
2910
+ const from = text === null ? null : staleVersion(text, "# vg \u2014 the code map");
2911
+ if (from !== null) {
2912
+ writeFileEnsured(file, skillMarkdown(a.id));
2913
+ out.push({ file: a.skill, from, to: INSTALL_CONTENT_VERSION });
2914
+ }
2915
+ }
2916
+ }
2917
+ if (a.nudge) {
2918
+ const file = path10.join(root, a.nudge.file);
2919
+ if (!fs10.existsSync(file)) continue;
2920
+ const text = readTextSafe(file);
2921
+ if (text === null) continue;
2922
+ if (a.nudge.kind === "block") {
2923
+ const block = new RegExp(`${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}`).exec(text);
2924
+ if (!block) continue;
2925
+ const from = staleVersion(block[0], "## Code navigation (vg)");
2926
+ if (from !== null) {
2927
+ writeNudge(file, a.nudge, smallRepo, a.id);
2928
+ out.push({ file: a.nudge.file, from, to: INSTALL_CONTENT_VERSION });
2929
+ }
2930
+ } else {
2931
+ const from = staleVersion(text, "## Code navigation (vg)");
2932
+ if (from !== null) {
2933
+ writeNudge(file, a.nudge, smallRepo, a.id);
2934
+ out.push({ file: a.nudge.file, from, to: INSTALL_CONTENT_VERSION });
2935
+ }
2936
+ }
2937
+ }
2938
+ }
2939
+ return out;
2940
+ }
2941
+ function readTextSafe(file) {
2942
+ try {
2943
+ return fs10.readFileSync(file, "utf8");
2944
+ } catch {
2945
+ return null;
2946
+ }
2947
+ }
2948
+ function writeNudge(file, target, smallRepo, client) {
2949
+ if (target.kind === "file") {
2950
+ const body = file.endsWith(".mdc") ? `---
2951
+ description: vg code graph
2952
+ alwaysApply: true
2953
+ ---
2954
+ ${stripMarkers(nudgeMarkdown(smallRepo, client))}` : stripMarkers(nudgeMarkdown(smallRepo, client));
2955
+ writeFileEnsured(file, `${body}
2956
+ `);
2957
+ return;
2958
+ }
2959
+ upsertBlock(file, nudgeMarkdown(smallRepo, client));
2960
+ }
2961
+ function upsertBlock(file, block) {
2962
+ fs10.mkdirSync(path10.dirname(file), { recursive: true });
2963
+ let existing = fs10.existsSync(file) ? fs10.readFileSync(file, "utf8") : "";
2964
+ const re = new RegExp(`${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}`);
2965
+ if (re.test(existing)) existing = existing.replace(re, block);
2966
+ else existing = existing.length ? `${existing.replace(/\s*$/, "")}
2967
+
2968
+ ${block}
2969
+ ` : `${block}
2970
+ `;
2971
+ fs10.writeFileSync(file, existing);
2972
+ }
2973
+ function removeBlock(file) {
2974
+ if (!fs10.existsSync(file)) return false;
2975
+ const existing = fs10.readFileSync(file, "utf8");
2976
+ const re = new RegExp(`\\n*${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}\\n*`);
2977
+ if (!re.test(existing)) return false;
2978
+ fs10.writeFileSync(file, existing.replace(re, "\n"));
2979
+ return true;
2980
+ }
2981
+ function readJson(file) {
2982
+ if (!fs10.existsSync(file)) return {};
2983
+ try {
2984
+ const parsed = JSON.parse(fs10.readFileSync(file, "utf8"));
2985
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
2986
+ return parsed;
2987
+ }
2988
+ throw new Error("not a JSON object");
2989
+ } catch {
2990
+ throw new CliError(
2991
+ `${file} exists but is not valid JSON \u2014 fix or remove it, then rerun. Refusing to overwrite it (that would lose your existing entries).`,
2992
+ ExitCode.ERROR
2993
+ );
2994
+ }
2995
+ }
2996
+ function stripMarkers(s) {
2997
+ return s.replace(NUDGE_BEGIN, "").replace(NUDGE_END, "").trim();
2998
+ }
2999
+ function escapeRe(s) {
3000
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3001
+ }
3002
+ var REFRESH_LOCK_STALE_MS = 10 * 60 * 1e3;
3003
+ function refreshLockPath(root) {
3004
+ return path10.join(cacheDir(root), "refresh.lock");
3005
+ }
3006
+ async function refreshIfStale(root, opts = {}) {
3007
+ const probe = probeFreshness(root);
3008
+ if (!probe) return { status: "no-snapshot" };
3009
+ if (!hasDrift(probe.drift)) return { status: "fresh" };
3010
+ const lock = refreshLockPath(root);
3011
+ if (!acquireLock(lock, REFRESH_LOCK_STALE_MS)) return { status: "locked" };
3012
+ const start = process.hrtime.bigint();
3013
+ try {
3014
+ const { scope } = probe;
3015
+ const result = await buildGraph({
3016
+ root,
3017
+ only: scope.only,
3018
+ exclude: scope.exclude,
3019
+ paths: scope.paths,
3020
+ deep: scope.deep,
3021
+ noGround: scope.noGround,
3022
+ scip: scope.scip,
3023
+ noScip: scope.noScip,
3024
+ noTsc: scope.noTsc,
3025
+ cluster: scope.cluster,
3026
+ grammarsDir: scope.grammarsDir,
3027
+ inline: opts.inline,
3028
+ jobs: opts.jobs
3029
+ });
3030
+ const wrote = result.graph.provenance.corpusHash !== probe.corpusHash;
3031
+ if (wrote) {
3032
+ const dir = vibgrateDir(root);
3033
+ writeArtifacts(result.graph, {
3034
+ root,
3035
+ graphPath: opts.graphPath,
3036
+ report: fs10.existsSync(path10.join(dir, "GRAPH_REPORT.md")),
3037
+ html: fs10.existsSync(path10.join(dir, "graph.html"))
3038
+ });
3039
+ }
3040
+ writeSnapshot(root, result.graph.provenance.corpusHash, result.fileStats, scope);
3041
+ const ms = Number((process.hrtime.bigint() - start) / 1000000n);
3042
+ return {
3043
+ status: "refreshed",
3044
+ drift: probe.drift,
3045
+ ms,
3046
+ reparsed: result.reparsed,
3047
+ totalFiles: result.totalFiles,
3048
+ wrote
3049
+ };
3050
+ } catch (err) {
3051
+ return { status: "error", message: err.message };
3052
+ } finally {
3053
+ releaseLock(lock);
3054
+ }
3055
+ }
3056
+
3057
+ export { ASSISTANTS, CliError, ExitCode, ResourceLimitError, SKIP_DIRS, SKIP_FILES, SMALL_REPO_FILES, UsageError, analyze, applyCoverage, assistantById, buildFacts, buildGraph, decodeScipIndex, detectAssistants, detectServeLaunch, discover, driftCount, epistemicBreakdown, groundGraph, hasDrift, installAssistant, loadCoverage, loadSnapshot, probeFreshness, refreshIfStale, refreshInstalledInstructions, resolveLimits, scipEdges, uninstallAssistant, usageError, verifyDeterminism, writeNavigationConfig, writeSnapshot };
3058
+ //# sourceMappingURL=chunk-WDJNUC6V.js.map
3059
+ //# sourceMappingURL=chunk-WDJNUC6V.js.map