@vibgrate/cli 2026.814.2 → 2026.817.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/dist/agent-CUYQKC6S.js +26 -0
  2. package/dist/{agent-5ANHRYEI.js.map → agent-CUYQKC6S.js.map} +1 -1
  3. package/dist/{approvals-T2KXPJLV.js → approvals-IPMUDEDV.js} +5 -5
  4. package/dist/{approvals-T2KXPJLV.js.map → approvals-IPMUDEDV.js.map} +1 -1
  5. package/dist/baseline-NZFGLMER.js +9 -0
  6. package/dist/{baseline-WVD6P5IO.js.map → baseline-NZFGLMER.js.map} +1 -1
  7. package/dist/{chunk-TFAXW7VM.js → chunk-2BURPMYA.js} +3 -3
  8. package/dist/{chunk-TFAXW7VM.js.map → chunk-2BURPMYA.js.map} +1 -1
  9. package/dist/{chunk-X76VPOYH.js → chunk-2GSMCOIC.js} +4 -4
  10. package/dist/{chunk-X76VPOYH.js.map → chunk-2GSMCOIC.js.map} +1 -1
  11. package/dist/{chunk-KGPX4EUN.js → chunk-2TJ5EY4I.js} +4 -4
  12. package/dist/{chunk-KGPX4EUN.js.map → chunk-2TJ5EY4I.js.map} +1 -1
  13. package/dist/{chunk-BH54SLGZ.js → chunk-3GZSDY35.js} +4 -4
  14. package/dist/{chunk-BH54SLGZ.js.map → chunk-3GZSDY35.js.map} +1 -1
  15. package/dist/{chunk-REIGWTTH.js → chunk-3HNPVXUK.js} +3 -3
  16. package/dist/{chunk-REIGWTTH.js.map → chunk-3HNPVXUK.js.map} +1 -1
  17. package/dist/{chunk-7NMXE6KG.js → chunk-45AKSS4A.js} +4 -4
  18. package/dist/{chunk-7NMXE6KG.js.map → chunk-45AKSS4A.js.map} +1 -1
  19. package/dist/{chunk-FOW5IF73.js → chunk-4W5SYK2M.js} +11 -34
  20. package/dist/chunk-4W5SYK2M.js.map +1 -0
  21. package/dist/{chunk-UTNNGKKX.js → chunk-6NOGNOE4.js} +4 -4
  22. package/dist/chunk-6NOGNOE4.js.map +1 -0
  23. package/dist/{chunk-N3XFY3PX.js → chunk-74UBGF4P.js} +3 -3
  24. package/dist/{chunk-N3XFY3PX.js.map → chunk-74UBGF4P.js.map} +1 -1
  25. package/dist/{chunk-YNV5M3JW.js → chunk-A7EKFLAN.js} +8 -8
  26. package/dist/{chunk-YNV5M3JW.js.map → chunk-A7EKFLAN.js.map} +1 -1
  27. package/dist/{chunk-N6ZMZ5AI.js → chunk-ANYL7SUI.js} +349 -46
  28. package/dist/chunk-ANYL7SUI.js.map +1 -0
  29. package/dist/{chunk-75LJM5XD.js → chunk-ENFSSUNK.js} +7 -3
  30. package/dist/chunk-ENFSSUNK.js.map +1 -0
  31. package/dist/{chunk-3LSG7Z4I.js → chunk-EZ7UC5Y5.js} +25 -4
  32. package/dist/chunk-EZ7UC5Y5.js.map +1 -0
  33. package/dist/{chunk-WWGLVF7D.js → chunk-FD2HPGX2.js} +8 -8
  34. package/dist/{chunk-WWGLVF7D.js.map → chunk-FD2HPGX2.js.map} +1 -1
  35. package/dist/{chunk-OWSFZQOP.js → chunk-FJG5AXKZ.js} +3 -3
  36. package/dist/{chunk-OWSFZQOP.js.map → chunk-FJG5AXKZ.js.map} +1 -1
  37. package/dist/chunk-FMS6G67U.js +467 -0
  38. package/dist/chunk-FMS6G67U.js.map +1 -0
  39. package/dist/{chunk-WHJMS7XC.js → chunk-I5LP55B6.js} +3 -3
  40. package/dist/{chunk-WHJMS7XC.js.map → chunk-I5LP55B6.js.map} +1 -1
  41. package/dist/{chunk-JCN6MSXQ.js → chunk-JHLCKMKF.js} +16 -29
  42. package/dist/chunk-JHLCKMKF.js.map +1 -0
  43. package/dist/{chunk-IHLLWBZU.js → chunk-K267DHCF.js} +6 -6
  44. package/dist/{chunk-IHLLWBZU.js.map → chunk-K267DHCF.js.map} +1 -1
  45. package/dist/{chunk-WQLZSVNF.js → chunk-MIYXVY57.js} +7 -6
  46. package/dist/chunk-MIYXVY57.js.map +1 -0
  47. package/dist/chunk-MS6V5XKB.js +33 -0
  48. package/dist/chunk-MS6V5XKB.js.map +1 -0
  49. package/dist/{chunk-5GLQNMTI.js → chunk-NHB53JTV.js} +6 -6
  50. package/dist/{chunk-5GLQNMTI.js.map → chunk-NHB53JTV.js.map} +1 -1
  51. package/dist/{chunk-DSUGC7F5.js → chunk-NKRWKCME.js} +4 -4
  52. package/dist/{chunk-DSUGC7F5.js.map → chunk-NKRWKCME.js.map} +1 -1
  53. package/dist/{chunk-7QDHQLUS.js → chunk-NQUWC2IA.js} +13 -11
  54. package/dist/chunk-NQUWC2IA.js.map +1 -0
  55. package/dist/{chunk-NKFXXP6W.js → chunk-OWSXI3PJ.js} +8 -6
  56. package/dist/chunk-OWSXI3PJ.js.map +1 -0
  57. package/dist/chunk-OZ3IFXRD.js +22 -0
  58. package/dist/chunk-OZ3IFXRD.js.map +1 -0
  59. package/dist/chunk-PV76EO4G.js +6 -0
  60. package/dist/{chunk-2H2U3DC7.js.map → chunk-PV76EO4G.js.map} +1 -1
  61. package/dist/{chunk-2U37PFB2.js → chunk-QPXVTQ7O.js} +3 -3
  62. package/dist/{chunk-2U37PFB2.js.map → chunk-QPXVTQ7O.js.map} +1 -1
  63. package/dist/{chunk-TNROG5CO.js → chunk-RJCRWQNS.js} +1855 -175
  64. package/dist/chunk-RJCRWQNS.js.map +1 -0
  65. package/dist/{chunk-54RGT2T5.js → chunk-RQAKBRAV.js} +3 -3
  66. package/dist/{chunk-54RGT2T5.js.map → chunk-RQAKBRAV.js.map} +1 -1
  67. package/dist/{chunk-YQNTIBKS.js → chunk-UBEVY5UT.js} +6 -6
  68. package/dist/{chunk-YQNTIBKS.js.map → chunk-UBEVY5UT.js.map} +1 -1
  69. package/dist/{chunk-IHXNOY3R.js → chunk-YL4OIUA5.js} +12 -12
  70. package/dist/{chunk-IHXNOY3R.js.map → chunk-YL4OIUA5.js.map} +1 -1
  71. package/dist/{chunk-TVDYEHIM.js → chunk-ZSECAGSU.js} +3 -3
  72. package/dist/{chunk-TVDYEHIM.js.map → chunk-ZSECAGSU.js.map} +1 -1
  73. package/dist/cli.js +416 -717
  74. package/dist/cli.js.map +1 -1
  75. package/dist/daemon-J2HX4GY3.js +25 -0
  76. package/dist/daemon-J2HX4GY3.js.map +1 -0
  77. package/dist/embed-worker-main.js +4 -4
  78. package/dist/ensure-map-5C6KG66X.js +32 -0
  79. package/dist/{ensure-map-HQ7A5NDL.js.map → ensure-map-5C6KG66X.js.map} +1 -1
  80. package/dist/graph-backend-HH5IIQ55.js +14 -0
  81. package/dist/{graph-backend-W2TNTMY7.js.map → graph-backend-HH5IIQ55.js.map} +1 -1
  82. package/dist/index.d.ts +9 -6
  83. package/dist/index.js +18 -16
  84. package/dist/index.js.map +1 -1
  85. package/dist/{interactive-EW2VJ3BZ.js → interactive-FXPAZAUJ.js} +33 -31
  86. package/dist/interactive-FXPAZAUJ.js.map +1 -0
  87. package/dist/load-OR2XVGAV.js +10 -0
  88. package/dist/{load-64RK5O6C.js.map → load-OR2XVGAV.js.map} +1 -1
  89. package/dist/local-runtime-6SNWKSA7.js +6 -0
  90. package/dist/{local-runtime-2ED33KK6.js.map → local-runtime-6SNWKSA7.js.map} +1 -1
  91. package/dist/model-manager-SXHZ5VD4.js +10 -0
  92. package/dist/{model-manager-GJ2YSTSK.js.map → model-manager-SXHZ5VD4.js.map} +1 -1
  93. package/dist/parse-worker.d.ts +1 -1
  94. package/dist/parse-worker.js +1 -1
  95. package/dist/{run-outcome-6QNQIHPV.js → run-outcome-M4546HLJ.js} +6 -6
  96. package/dist/{run-outcome-6QNQIHPV.js.map → run-outcome-M4546HLJ.js.map} +1 -1
  97. package/dist/runtime-session-6KKMFS6Q.js +21 -0
  98. package/dist/{runtime-session-RJHH3KMF.js.map → runtime-session-6KKMFS6Q.js.map} +1 -1
  99. package/dist/session-HCFJUSKN.js +16 -0
  100. package/dist/{session-4BGYASOH.js.map → session-HCFJUSKN.js.map} +1 -1
  101. package/dist/{stream-json-GY5N7WZT.js → stream-json-4DL65D6U.js} +16 -16
  102. package/dist/{stream-json-GY5N7WZT.js.map → stream-json-4DL65D6U.js.map} +1 -1
  103. package/dist/{types-DLH-Ritr.d.ts → types-8SYlXhzm.d.ts} +57 -6
  104. package/dist/version-UPIC3F4P.js +4 -0
  105. package/dist/{version-DPAMQW6M.js.map → version-UPIC3F4P.js.map} +1 -1
  106. package/dist/{vg-mcp-bridge-IGHJKKSL.js → vg-mcp-bridge-FZGX6DYO.js} +10 -10
  107. package/dist/{vg-mcp-bridge-IGHJKKSL.js.map → vg-mcp-bridge-FZGX6DYO.js.map} +1 -1
  108. package/dist/{vgd-ORPA5C2K.js → vgd-QR26FAZD.js} +9 -9
  109. package/dist/vgd-QR26FAZD.js.map +1 -0
  110. package/grammars/tree-sitter-hcl.wasm +0 -0
  111. package/package.json +3 -2
  112. package/dist/agent-5ANHRYEI.js +0 -26
  113. package/dist/baseline-WVD6P5IO.js +0 -9
  114. package/dist/chunk-2H2U3DC7.js +0 -6
  115. package/dist/chunk-3LSG7Z4I.js.map +0 -1
  116. package/dist/chunk-75LJM5XD.js.map +0 -1
  117. package/dist/chunk-7QDHQLUS.js.map +0 -1
  118. package/dist/chunk-FOW5IF73.js.map +0 -1
  119. package/dist/chunk-JCN6MSXQ.js.map +0 -1
  120. package/dist/chunk-N6ZMZ5AI.js.map +0 -1
  121. package/dist/chunk-NKFXXP6W.js.map +0 -1
  122. package/dist/chunk-TNROG5CO.js.map +0 -1
  123. package/dist/chunk-UTNNGKKX.js.map +0 -1
  124. package/dist/chunk-WQLZSVNF.js.map +0 -1
  125. package/dist/ensure-map-HQ7A5NDL.js +0 -30
  126. package/dist/graph-backend-W2TNTMY7.js +0 -14
  127. package/dist/interactive-EW2VJ3BZ.js.map +0 -1
  128. package/dist/load-64RK5O6C.js +0 -10
  129. package/dist/local-runtime-2ED33KK6.js +0 -6
  130. package/dist/model-manager-GJ2YSTSK.js +0 -10
  131. package/dist/runtime-session-RJHH3KMF.js +0 -21
  132. package/dist/session-4BGYASOH.js +0 -16
  133. package/dist/version-DPAMQW6M.js +0 -4
  134. package/dist/vgd-ORPA5C2K.js.map +0 -1
@@ -1,16 +1,17 @@
1
- import { FREE_PACK, navigationToolsetConfig, deferredToolNames, HOT_TOOLS } from './chunk-WWGLVF7D.js';
1
+ import { FREE_PACK, navigationToolsetConfig, deferredToolNames, HOT_TOOLS } from './chunk-FD2HPGX2.js';
2
+ import { CliError, ExitCode } from './chunk-MS6V5XKB.js';
2
3
  import { edgeId, buildModuleResolver, resolve, applyStaticTestLinkage, nodeId } from './chunk-VIWZ4QT7.js';
3
- import { indexFor } from './chunk-KGPX4EUN.js';
4
- import { writeGraphIndex, SCHEMA_VERSION, loadGraph } from './chunk-WQLZSVNF.js';
5
- import { init_cli_invocation, whichOnPath, isInstalledOwnBinary } from './chunk-3LSG7Z4I.js';
6
- import { langById, grammarSetVersion, langForExtension, setGrammarsOverride, parseSource } from './chunk-UTNNGKKX.js';
4
+ import { indexFor } from './chunk-2TJ5EY4I.js';
5
+ import { writeGraphIndex, SCHEMA_VERSION, loadGraph } from './chunk-MIYXVY57.js';
6
+ import { init_cli_invocation, whichOnPath, isInstalledOwnBinary } from './chunk-EZ7UC5Y5.js';
7
+ import { langById, grammarSetVersion, langForExtension, grammarDirs, setGrammarsOverride, parseSource, ensureParserInit } from './chunk-6NOGNOE4.js';
7
8
  import { redactSecrets } from './chunk-RXP66R2E.js';
8
- import { loadCache, cacheDir } from './chunk-2U37PFB2.js';
9
- import { serializeGraph, stableStringify, ensureVibgrateGitignore } from './chunk-75LJM5XD.js';
10
- import { VERSION } from './chunk-2H2U3DC7.js';
9
+ import { loadCache, cacheDir } from './chunk-QPXVTQ7O.js';
10
+ import { serializeGraph, stableStringify, ensureVibgrateGitignore } from './chunk-ENFSSUNK.js';
11
+ import { VERSION } from './chunk-PV76EO4G.js';
11
12
  import { shortId, canonicalize, hashString, hashBytes } from './chunk-JFGYT6BI.js';
12
- import * as fs11 from 'fs';
13
- import * as path11 from 'path';
13
+ import * as fs13 from 'fs';
14
+ import * as path12 from 'path';
14
15
  import ignore from 'ignore';
15
16
  import * as v8 from 'v8';
16
17
  import Graph from 'graphology';
@@ -22,7 +23,10 @@ import * as os from 'os';
22
23
  import { fileURLToPath } from 'url';
23
24
  import ts from 'typescript';
24
25
  import * as crypto from 'crypto';
25
- import * as fs8 from 'fs/promises';
26
+ import YAML from 'yaml';
27
+ import { createRequire } from 'module';
28
+ import { Parser, Language } from 'web-tree-sitter';
29
+ import * as fs10 from 'fs/promises';
26
30
 
27
31
  var SKIP_DIRS = /* @__PURE__ */ new Set([
28
32
  // Version control, IDE & tool metadata
@@ -187,19 +191,19 @@ var SKIP_FILES = /* @__PURE__ */ new Set([
187
191
  "flake.lock"
188
192
  ]);
189
193
  function toPosix(p) {
190
- return p.split(path11.sep).join("/");
194
+ return p.split(path12.sep).join("/");
191
195
  }
192
196
  function buildRootIgnore(root, exclude) {
193
197
  const ig = ignore();
194
- const gitignorePath = path11.join(root, ".gitignore");
195
- if (fs11.existsSync(gitignorePath)) {
196
- ig.add(fs11.readFileSync(gitignorePath, "utf8"));
198
+ const gitignorePath = path12.join(root, ".gitignore");
199
+ if (fs13.existsSync(gitignorePath)) {
200
+ ig.add(fs13.readFileSync(gitignorePath, "utf8"));
197
201
  }
198
202
  if (exclude.length) ig.add(exclude);
199
203
  return ig;
200
204
  }
201
205
  function discover(options) {
202
- const root = path11.resolve(options.root);
206
+ const root = path12.resolve(options.root);
203
207
  const onlyLangs = (options.only ?? []).filter(Boolean);
204
208
  const allowLang = (lang) => onlyLangs.length === 0 || onlyLangs.includes(lang.id);
205
209
  for (const id of onlyLangs) {
@@ -208,27 +212,27 @@ function discover(options) {
208
212
  }
209
213
  }
210
214
  const rootIg = buildRootIgnore(root, options.exclude ?? []);
211
- const scopeAbs = (options.paths && options.paths.length ? options.paths.map((p) => path11.resolve(root, p)) : [root]).filter((p) => fs11.existsSync(p));
215
+ const scopeAbs = (options.paths && options.paths.length ? options.paths.map((p) => path12.resolve(root, p)) : [root]).filter((p) => fs13.existsSync(p));
212
216
  const found = /* @__PURE__ */ new Map();
213
217
  const considerFile = (abs) => {
214
- const rel2 = toPosix(path11.relative(root, abs));
218
+ const rel2 = toPosix(path12.relative(root, abs));
215
219
  if (rel2.startsWith("..")) return;
216
220
  if (rel2 === "" || rootIg.ignores(rel2)) return;
217
- if (SKIP_FILES.has(path11.basename(abs).toLowerCase())) return;
218
- const lang = langForExtension(path11.extname(abs));
221
+ if (SKIP_FILES.has(path12.basename(abs).toLowerCase())) return;
222
+ const lang = langForExtension(path12.extname(abs));
219
223
  if (!lang || !allowLang(lang)) return;
220
224
  found.set(rel2, { rel: rel2, abs, lang });
221
225
  };
222
226
  const walk = (dir) => {
223
227
  let entries;
224
228
  try {
225
- entries = fs11.readdirSync(dir, { withFileTypes: true });
229
+ entries = fs13.readdirSync(dir, { withFileTypes: true });
226
230
  } catch {
227
231
  return;
228
232
  }
229
233
  for (const entry of entries) {
230
- const abs = path11.join(dir, entry.name);
231
- const rel2 = toPosix(path11.relative(root, abs));
234
+ const abs = path12.join(dir, entry.name);
235
+ const rel2 = toPosix(path12.relative(root, abs));
232
236
  if (entry.isDirectory()) {
233
237
  if (isSkippedDirName(entry.name)) continue;
234
238
  if (rel2 && rootIg.ignores(`${rel2}/`)) continue;
@@ -239,7 +243,7 @@ function discover(options) {
239
243
  }
240
244
  };
241
245
  for (const scope of scopeAbs) {
242
- const stat = fs11.statSync(scope);
246
+ const stat = fs13.statSync(scope);
243
247
  if (stat.isDirectory()) walk(scope);
244
248
  else if (stat.isFile()) considerFile(scope);
245
249
  }
@@ -721,14 +725,14 @@ var DEFAULT_PATHS = [
721
725
  ];
722
726
  function loadCoverage(root, explicit) {
723
727
  const candidates = (explicit && explicit.length ? explicit : DEFAULT_PATHS).map(
724
- (p) => path11.resolve(root, p)
728
+ (p) => path12.resolve(root, p)
725
729
  );
726
- const found = candidates.filter((p) => fs11.existsSync(p));
730
+ const found = candidates.filter((p) => fs13.existsSync(p));
727
731
  if (found.length === 0) return null;
728
732
  const map = /* @__PURE__ */ new Map();
729
733
  for (const file of found) {
730
734
  try {
731
- const text = fs11.readFileSync(file, "utf8");
735
+ const text = fs13.readFileSync(file, "utf8");
732
736
  if (file.endsWith(".json")) mergeIstanbul(map, text, root);
733
737
  else mergeLcov(map, text, root);
734
738
  } catch {
@@ -737,8 +741,8 @@ function loadCoverage(root, explicit) {
737
741
  return map.size ? map : null;
738
742
  }
739
743
  function rel(root, p) {
740
- const abs = path11.isAbsolute(p) ? p : path11.resolve(root, p);
741
- return path11.relative(root, abs).split(path11.sep).join("/");
744
+ const abs = path12.isAbsolute(p) ? p : path12.resolve(root, p);
745
+ return path12.relative(root, abs).split(path12.sep).join("/");
742
746
  }
743
747
  function bump(map, file, line, hits) {
744
748
  let lh = map.get(file);
@@ -949,7 +953,7 @@ async function parseInline(files, options) {
949
953
  onProgress?.(0, files.length);
950
954
  for (const file of files) {
951
955
  try {
952
- const source = fs11.readFileSync(file.abs, "utf8");
956
+ const source = fs13.readFileSync(file.abs, "utf8");
953
957
  out.push(await parseSource(file.rel, file.lang.id, source));
954
958
  } catch (err) {
955
959
  out.push(emptyParse(file, `parse failed: ${err.message}`));
@@ -1004,9 +1008,9 @@ function isWorkerOom(err) {
1004
1008
  return e?.code === "ERR_WORKER_OUT_OF_MEMORY" || /out of memory/i.test(e?.message ?? "");
1005
1009
  }
1006
1010
  function resolveWorkerFile() {
1007
- const here = path11.dirname(fileURLToPath(import.meta.url));
1008
- const candidate = path11.join(here, "parse-worker.js");
1009
- return fs11.existsSync(candidate) ? candidate : null;
1011
+ const here = path12.dirname(fileURLToPath(import.meta.url));
1012
+ const candidate = path12.join(here, "parse-worker.js");
1013
+ return fs13.existsSync(candidate) ? candidate : null;
1010
1014
  }
1011
1015
  function chunk(items, buckets) {
1012
1016
  const out = Array.from({ length: Math.min(buckets, items.length || 1) }, () => []);
@@ -1285,12 +1289,12 @@ function enclosing3(nodes, line) {
1285
1289
  return best;
1286
1290
  }
1287
1291
  function normalize2(p) {
1288
- return path11.resolve(p).split(path11.sep).join("/");
1292
+ return path12.resolve(p).split(path12.sep).join("/");
1289
1293
  }
1290
1294
  var TSC_CACHE_VERSION = "vg-tsc-cache/1";
1291
1295
  var AMBIENT_RE = /\bdeclare\s+(global|module)\b/;
1292
1296
  function tscCachePath(root) {
1293
- return path11.join(cacheDir(root), "tsc-cache.json");
1297
+ return path12.join(cacheDir(root), "tsc-cache.json");
1294
1298
  }
1295
1299
  function loadTscCache(root, opts) {
1296
1300
  const file = tscCachePath(root);
@@ -1300,9 +1304,9 @@ function loadTscCache(root, opts) {
1300
1304
  ts: ts.version,
1301
1305
  entries: {}
1302
1306
  };
1303
- if (!opts.disabled && fs11.existsSync(file)) {
1307
+ if (!opts.disabled && fs13.existsSync(file)) {
1304
1308
  try {
1305
- const loaded = JSON.parse(fs11.readFileSync(file, "utf8"));
1309
+ const loaded = JSON.parse(fs13.readFileSync(file, "utf8"));
1306
1310
  if (loaded.version === TSC_CACHE_VERSION && loaded.toolVersion === opts.toolVersion && loaded.ts === ts.version && loaded.entries) {
1307
1311
  data = loaded;
1308
1312
  }
@@ -1333,18 +1337,18 @@ function loadTscCache(root, opts) {
1333
1337
  },
1334
1338
  save() {
1335
1339
  if (opts.disabled) return;
1336
- fs11.mkdirSync(cacheDir(root), { recursive: true });
1337
- fs11.writeFileSync(file, stableStringify(data, 0));
1340
+ fs13.mkdirSync(cacheDir(root), { recursive: true });
1341
+ fs13.writeFileSync(file, stableStringify(data, 0));
1338
1342
  }
1339
1343
  };
1340
1344
  }
1341
1345
  function abs$(root, rel2) {
1342
- return path11.join(root, rel2);
1346
+ return path12.join(root, rel2);
1343
1347
  }
1344
1348
  function detectAmbient(rel2, abs) {
1345
1349
  if (rel2.endsWith(".d.ts")) return true;
1346
1350
  try {
1347
- return AMBIENT_RE.test(fs11.readFileSync(abs, "utf8"));
1351
+ return AMBIENT_RE.test(fs13.readFileSync(abs, "utf8"));
1348
1352
  } catch {
1349
1353
  return true;
1350
1354
  }
@@ -1638,14 +1642,14 @@ var IMPORTANCE = {
1638
1642
  "other-config": 0.08
1639
1643
  };
1640
1644
  function toPosix2(p) {
1641
- return p.split(path11.sep).join("/");
1645
+ return p.split(path12.sep).join("/");
1642
1646
  }
1643
1647
  function buildRootIgnore2(root, exclude) {
1644
1648
  const ig = ignore();
1645
- const gitignorePath = path11.join(root, ".gitignore");
1646
- if (fs11.existsSync(gitignorePath)) {
1649
+ const gitignorePath = path12.join(root, ".gitignore");
1650
+ if (fs13.existsSync(gitignorePath)) {
1647
1651
  try {
1648
- ig.add(fs11.readFileSync(gitignorePath, "utf8"));
1652
+ ig.add(fs13.readFileSync(gitignorePath, "utf8"));
1649
1653
  } catch {
1650
1654
  }
1651
1655
  }
@@ -1654,7 +1658,7 @@ function buildRootIgnore2(root, exclude) {
1654
1658
  }
1655
1659
  function classifyProjectContext(relOrName) {
1656
1660
  const rel2 = relOrName.replace(/\\/g, "/");
1657
- const base = path11.basename(rel2);
1661
+ const base = path12.basename(rel2);
1658
1662
  const baseLower = base.toLowerCase();
1659
1663
  if (/^\.env($|\.)/i.test(base) && !isEnvExampleName(baseLower)) return null;
1660
1664
  if (SKIP_FILES.has(baseLower)) return null;
@@ -1667,7 +1671,7 @@ function classifyProjectContext(relOrName) {
1667
1671
  for (const { re, category } of PATH_PATTERNS) {
1668
1672
  if (re.test(rel2)) return category;
1669
1673
  }
1670
- const ext = path11.extname(baseLower);
1674
+ const ext = path12.extname(baseLower);
1671
1675
  if (DOC_EXTENSIONS.has(ext)) return ext === ".txt" ? "text" : "markdown";
1672
1676
  if (API_EXTENSIONS.has(ext)) return "api-contract";
1673
1677
  if ((ext === ".yaml" || ext === ".yml" || ext === ".json") && /openapi|swagger|asyncapi/i.test(base)) {
@@ -1685,24 +1689,24 @@ function isEnvExampleName(baseLower) {
1685
1689
  return false;
1686
1690
  }
1687
1691
  function discoverDocs(options) {
1688
- const root = path11.resolve(options.root);
1692
+ const root = path12.resolve(options.root);
1689
1693
  const maxFiles = options.maxFiles ?? DOC_MAX_FILES;
1690
1694
  const rootIg = buildRootIgnore2(root, options.exclude ?? []);
1691
- const scopeAbs = (options.paths?.length ? options.paths.map((p) => path11.resolve(root, p)) : [root]).filter(
1692
- (p) => fs11.existsSync(p)
1695
+ const scopeAbs = (options.paths?.length ? options.paths.map((p) => path12.resolve(root, p)) : [root]).filter(
1696
+ (p) => fs13.existsSync(p)
1693
1697
  );
1694
1698
  const found = /* @__PURE__ */ new Map();
1695
1699
  const consider = (abs) => {
1696
1700
  if (found.size >= maxFiles) return;
1697
- const rel2 = toPosix2(path11.relative(root, abs));
1701
+ const rel2 = toPosix2(path12.relative(root, abs));
1698
1702
  if (!rel2 || rel2.startsWith("..")) return;
1699
1703
  if (rootIg.ignores(rel2)) return;
1700
- const base = path11.basename(abs);
1704
+ const base = path12.basename(abs);
1701
1705
  if (SKIP_FILES.has(base.toLowerCase())) return;
1702
1706
  const category = classifyProjectContext(rel2);
1703
1707
  if (!category) return;
1704
1708
  try {
1705
- const st = fs11.statSync(abs);
1709
+ const st = fs13.statSync(abs);
1706
1710
  if (!st.isFile() || st.size > DOC_FILE_MAX_BYTES) return;
1707
1711
  if (st.size === 0) return;
1708
1712
  } catch {
@@ -1714,14 +1718,14 @@ function discoverDocs(options) {
1714
1718
  if (found.size >= maxFiles || depth > 12) return;
1715
1719
  let entries;
1716
1720
  try {
1717
- entries = fs11.readdirSync(dir, { withFileTypes: true });
1721
+ entries = fs13.readdirSync(dir, { withFileTypes: true });
1718
1722
  } catch {
1719
1723
  return;
1720
1724
  }
1721
1725
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
1722
1726
  if (found.size >= maxFiles) return;
1723
- const abs = path11.join(dir, entry.name);
1724
- const rel2 = toPosix2(path11.relative(root, abs));
1727
+ const abs = path12.join(dir, entry.name);
1728
+ const rel2 = toPosix2(path12.relative(root, abs));
1725
1729
  if (entry.isDirectory()) {
1726
1730
  if (isSkippedDirName(entry.name)) continue;
1727
1731
  if (rel2 && rootIg.ignores(`${rel2}/`) && !rel2.startsWith(".github")) continue;
@@ -1733,7 +1737,7 @@ function discoverDocs(options) {
1733
1737
  };
1734
1738
  for (const scope of scopeAbs) {
1735
1739
  try {
1736
- const st = fs11.statSync(scope);
1740
+ const st = fs13.statSync(scope);
1737
1741
  if (st.isDirectory()) walk(scope, 0);
1738
1742
  else if (st.isFile()) consider(scope);
1739
1743
  } catch {
@@ -1744,7 +1748,7 @@ function discoverDocs(options) {
1744
1748
  function prepareDocBody(rel2, raw, category) {
1745
1749
  const scrubbed = redactSecrets(raw);
1746
1750
  if (scrubbed.length <= DOC_BODY_MAX_CHARS) return scrubbed;
1747
- if (category === "manifest" && path11.basename(rel2).toLowerCase() === "package.json") {
1751
+ if (category === "manifest" && path12.basename(rel2).toLowerCase() === "package.json") {
1748
1752
  return summarizePackageJson(scrubbed);
1749
1753
  }
1750
1754
  if (category === "api-contract" && /\.(ya?ml|json)$/i.test(rel2)) {
@@ -1790,14 +1794,14 @@ function documentNodesFromDocs(docs) {
1790
1794
  for (const d of docs) {
1791
1795
  let raw = "";
1792
1796
  try {
1793
- raw = fs11.readFileSync(d.abs, "utf8");
1797
+ raw = fs13.readFileSync(d.abs, "utf8");
1794
1798
  } catch {
1795
1799
  continue;
1796
1800
  }
1797
1801
  const category = d.category ?? d.kind;
1798
1802
  const scrubbed = prepareDocBody(d.rel, raw, category);
1799
1803
  if (!scrubbed.trim()) continue;
1800
- const name = path11.basename(d.rel);
1804
+ const name = path12.basename(d.rel);
1801
1805
  const qualifiedName = d.rel.replace(/\\/g, "/");
1802
1806
  const id = nodeId({
1803
1807
  kind: "document",
@@ -1826,12 +1830,1703 @@ function documentNodesFromDocs(docs) {
1826
1830
  }
1827
1831
  return nodes.sort((a, b) => a.file.localeCompare(b.file) || a.id.localeCompare(b.id));
1828
1832
  }
1833
+
1834
+ // src/engine/toolchain/util.ts
1835
+ var TOOLCHAIN_FILE_MAX_BYTES = 2 * 1024 * 1024;
1836
+ var TOOLCHAIN_NODES_PER_FILE_MAX = 2e3;
1837
+ var DOC_MAX_CHARS = 240;
1838
+ var LineIndex = class {
1839
+ /** Byte offset at which each line starts. */
1840
+ starts;
1841
+ constructor(source) {
1842
+ const starts = [0];
1843
+ for (let i = 0; i < source.length; i++) {
1844
+ if (source.charCodeAt(i) === 10) starts.push(i + 1);
1845
+ }
1846
+ this.starts = starts;
1847
+ }
1848
+ /** 1-based line number containing `offset`. */
1849
+ lineAt(offset) {
1850
+ if (offset <= 0) return 1;
1851
+ let lo = 0;
1852
+ let hi = this.starts.length - 1;
1853
+ while (lo < hi) {
1854
+ const mid = lo + hi + 1 >> 1;
1855
+ if (this.starts[mid] <= offset) lo = mid;
1856
+ else hi = mid - 1;
1857
+ }
1858
+ return lo + 1;
1859
+ }
1860
+ /** Span covering the byte range `[start, end)`. */
1861
+ span(start, end) {
1862
+ const s = this.lineAt(start);
1863
+ const e = this.lineAt(Math.max(start, end - 1));
1864
+ return { start: s, end: Math.max(s, e) };
1865
+ }
1866
+ get lineCount() {
1867
+ return this.starts.length;
1868
+ }
1869
+ };
1870
+ function safeDoc(text) {
1871
+ if (!text) return void 0;
1872
+ const collapsed = text.replace(/\s+/g, " ").trim();
1873
+ if (!collapsed) return void 0;
1874
+ const redacted = redactSecrets(collapsed);
1875
+ return redacted.length > DOC_MAX_CHARS ? `${redacted.slice(0, DOC_MAX_CHARS - 1)}\u2026` : redacted;
1876
+ }
1877
+ function parseImageRef(raw) {
1878
+ const value = raw.trim();
1879
+ if (!value || /\s/.test(value)) return null;
1880
+ if (value.includes("$") || value.includes("{{")) return null;
1881
+ const atIdx = value.lastIndexOf("@");
1882
+ let digest;
1883
+ let rest = value;
1884
+ if (atIdx > 0) {
1885
+ digest = value.slice(atIdx + 1);
1886
+ rest = value.slice(0, atIdx);
1887
+ }
1888
+ let tag;
1889
+ const colonIdx = rest.lastIndexOf(":");
1890
+ if (colonIdx > 0 && colonIdx > rest.lastIndexOf("/")) {
1891
+ tag = rest.slice(colonIdx + 1);
1892
+ rest = rest.slice(0, colonIdx);
1893
+ }
1894
+ if (!rest) return null;
1895
+ return { raw: value, repository: rest, tag, digest };
1896
+ }
1897
+ function imageIdentity(ref) {
1898
+ if (ref.digest) return `${ref.repository}@${ref.digest}`;
1899
+ return ref.tag ? `${ref.repository}:${ref.tag}` : ref.repository;
1900
+ }
1901
+ function toPosix3(rel2) {
1902
+ return rel2.replace(/\\/g, "/");
1903
+ }
1904
+ function resolveRelative(fromFileRel, target) {
1905
+ const base = toPosix3(fromFileRel).split("/").slice(0, -1);
1906
+ const parts = toPosix3(target).split("/");
1907
+ const out = [...base];
1908
+ for (const part of parts) {
1909
+ if (!part || part === ".") continue;
1910
+ if (part === "..") {
1911
+ if (!out.length) return null;
1912
+ out.pop();
1913
+ continue;
1914
+ }
1915
+ out.push(part);
1916
+ }
1917
+ const joined = out.join("/");
1918
+ return joined || null;
1919
+ }
1920
+ function parseYamlStream(rel2, source) {
1921
+ const warnings = [];
1922
+ let parsed;
1923
+ try {
1924
+ parsed = YAML.parseAllDocuments(source, {
1925
+ // Parsing itself does not expand aliases — the bomb only detonates on
1926
+ // materialisation, so the alias budget is applied at `toJS` below.
1927
+ logLevel: "silent",
1928
+ uniqueKeys: false
1929
+ });
1930
+ } catch (err) {
1931
+ warnings.push(`${rel2}: YAML parse failed (${err.message})`);
1932
+ return { docs: [], warnings };
1933
+ }
1934
+ const docs = [];
1935
+ parsed.forEach((ast, index) => {
1936
+ if (ast.errors.length) {
1937
+ warnings.push(`${rel2}: YAML document ${index + 1} has errors \u2014 skipped`);
1938
+ return;
1939
+ }
1940
+ let value;
1941
+ try {
1942
+ value = ast.toJS({ maxAliasCount: 100 });
1943
+ } catch (err) {
1944
+ warnings.push(`${rel2}: YAML document ${index + 1} could not be materialised (${err.message})`);
1945
+ return;
1946
+ }
1947
+ if (value === null || value === void 0) return;
1948
+ docs.push({ value, ast, index });
1949
+ });
1950
+ return { docs, warnings };
1951
+ }
1952
+ function spanAt(doc, lines, path13) {
1953
+ const node = path13.length ? safeGetIn(doc.ast, path13) : doc.ast.contents;
1954
+ const range = nodeRange(node) ?? nodeRange(doc.ast.contents);
1955
+ if (!range) return { start: 1, end: 1 };
1956
+ return lines.span(range[0], range[2] ?? range[1]);
1957
+ }
1958
+ function safeGetIn(ast, path13) {
1959
+ try {
1960
+ return ast.getIn(path13, true);
1961
+ } catch {
1962
+ return void 0;
1963
+ }
1964
+ }
1965
+ function nodeRange(node) {
1966
+ const range = node?.range;
1967
+ return Array.isArray(range) ? range : null;
1968
+ }
1969
+ function getPath(value, path13) {
1970
+ let current = value;
1971
+ for (const key of path13) {
1972
+ if (current === null || typeof current !== "object") return void 0;
1973
+ current = current[key];
1974
+ }
1975
+ return current;
1976
+ }
1977
+ function getString(value, path13) {
1978
+ const found = getPath(value, path13);
1979
+ if (typeof found === "string") return found;
1980
+ if (typeof found === "number" || typeof found === "boolean") return String(found);
1981
+ return void 0;
1982
+ }
1983
+ function getArray(value, path13) {
1984
+ const found = getPath(value, path13);
1985
+ return Array.isArray(found) ? found : [];
1986
+ }
1987
+ function getRecordEntries(value, path13) {
1988
+ const found = getPath(value, path13);
1989
+ if (!found || typeof found !== "object" || Array.isArray(found)) return [];
1990
+ return Object.entries(found).sort(([a], [b]) => a.localeCompare(b, "en"));
1991
+ }
1992
+ function looksTemplated(source) {
1993
+ return /\{\{-?\s*(if|range|with|end|define|template|include|toYaml|\.Values|\.Release|\.Chart)/.test(
1994
+ source
1995
+ );
1996
+ }
1997
+
1998
+ // src/engine/toolchain/compose.ts
1999
+ var DEFAULT_DOCKERFILE = "Dockerfile";
2000
+ var composeExtractor = {
2001
+ format: "compose",
2002
+ matches(rel2, category) {
2003
+ if (category === "compose") return true;
2004
+ return /(^|\/)(docker-)?compose([.-][\w.-]+)?\.ya?ml$/i.test(rel2);
2005
+ },
2006
+ extract(rel2, source) {
2007
+ const { docs, warnings } = parseYamlStream(rel2, source);
2008
+ const lines = new LineIndex(source);
2009
+ const nodes = [];
2010
+ const edges = [];
2011
+ const doc = docs[0];
2012
+ if (!doc) return { nodes, edges, warnings };
2013
+ const services = getRecordEntries(doc.value, ["services"]);
2014
+ const declared = new Set(services.map(([name]) => `service:${name}`));
2015
+ for (const [name, service] of services) {
2016
+ if (nodes.length >= TOOLCHAIN_NODES_PER_FILE_MAX) {
2017
+ warnings.push(`${rel2}: stopped at ${TOOLCHAIN_NODES_PER_FILE_MAX} nodes`);
2018
+ break;
2019
+ }
2020
+ const address = `service:${name}`;
2021
+ const image = getString(service, ["image"]);
2022
+ const ports = getArray(service, ["ports"]).map(String);
2023
+ nodes.push({
2024
+ kind: "resource",
2025
+ name,
2026
+ qualifiedName: address,
2027
+ span: spanAt(doc, lines, ["services", name]),
2028
+ signature: "compose.service",
2029
+ doc: safeDoc(
2030
+ [image && `image ${image}`, ports.length && `ports ${ports.join(", ")}`].filter(Boolean).join(", ")
2031
+ ),
2032
+ importance: 0.5
2033
+ });
2034
+ if (image) {
2035
+ const ref = parseImageRef(image);
2036
+ if (ref) {
2037
+ nodes.push({
2038
+ kind: "image",
2039
+ name: ref.repository.split("/").pop() ?? ref.repository,
2040
+ qualifiedName: `image:${ref.raw}`,
2041
+ span: spanAt(doc, lines, ["services", name, "image"]),
2042
+ signature: "compose.image",
2043
+ doc: safeDoc(ref.tag ? `${ref.repository} tag ${ref.tag}` : ref.repository),
2044
+ importance: 0.3
2045
+ });
2046
+ edges.push({ kind: "builds_from", from: address, to: `image:${ref.raw}`, confidence: 1 });
2047
+ }
2048
+ }
2049
+ const dockerfileRel = composeDockerfilePath(rel2, service);
2050
+ if (dockerfileRel) {
2051
+ nodes.push({
2052
+ kind: "image",
2053
+ name: dockerfileRel.split("/").pop() ?? dockerfileRel,
2054
+ qualifiedName: `dockerfile:${dockerfileRel}`,
2055
+ span: spanAt(doc, lines, ["services", name, "build"]),
2056
+ signature: "compose.build",
2057
+ doc: safeDoc(`builds from ${dockerfileRel}`),
2058
+ importance: 0.4
2059
+ });
2060
+ edges.push({
2061
+ kind: "builds_from",
2062
+ from: address,
2063
+ to: `dockerfile:${dockerfileRel}`,
2064
+ confidence: 1
2065
+ });
2066
+ }
2067
+ for (const target of composeDependsOn(service)) {
2068
+ const to = `service:${target}`;
2069
+ if (declared.has(to)) edges.push({ kind: "depends_on", from: address, to, confidence: 1 });
2070
+ }
2071
+ for (const port of ports) {
2072
+ edges.push({ kind: "exposes", from: address, to: `port:${port}`, confidence: 1 });
2073
+ }
2074
+ }
2075
+ const portTargets = new Set(
2076
+ edges.filter((e) => e.kind === "exposes").map((e) => e.to)
2077
+ );
2078
+ for (const target of [...portTargets].sort()) {
2079
+ nodes.push({
2080
+ kind: "property",
2081
+ name: target.slice("port:".length),
2082
+ qualifiedName: target,
2083
+ span: { start: 1, end: 1 },
2084
+ signature: "compose.port",
2085
+ importance: 0.1
2086
+ });
2087
+ }
2088
+ return { nodes, edges, warnings };
2089
+ }
2090
+ };
2091
+ function composeDockerfilePath(rel2, service) {
2092
+ const build = getPath(service, ["build"]);
2093
+ if (build === void 0 || build === null) return null;
2094
+ if (typeof build === "string") {
2095
+ return resolveRelative(rel2, `${build}/${DEFAULT_DOCKERFILE}`);
2096
+ }
2097
+ if (typeof build !== "object") return null;
2098
+ const context = getString(build, ["context"]) ?? ".";
2099
+ const dockerfile = getString(build, ["dockerfile"]) ?? DEFAULT_DOCKERFILE;
2100
+ return resolveRelative(rel2, `${context}/${dockerfile}`);
2101
+ }
2102
+ function composeDependsOn(service) {
2103
+ const raw = getPath(service, ["depends_on"]);
2104
+ if (Array.isArray(raw)) {
2105
+ return raw.filter((v) => typeof v === "string").sort();
2106
+ }
2107
+ if (raw && typeof raw === "object") {
2108
+ return Object.keys(raw).sort();
2109
+ }
2110
+ return [];
2111
+ }
2112
+
2113
+ // src/engine/toolchain/dockerfile.ts
2114
+ var INSTRUCTION = /^([A-Za-z][A-Za-z0-9_]*)\s+([\s\S]*)$/;
2115
+ function logicalLines(source) {
2116
+ const rawLines = source.split(/\r?\n/);
2117
+ let escapeChar = "\\";
2118
+ for (const line of rawLines) {
2119
+ const trimmed = line.trim();
2120
+ if (!trimmed) continue;
2121
+ if (!trimmed.startsWith("#")) break;
2122
+ const directive = /^#\s*escape\s*=\s*(\S)/i.exec(trimmed);
2123
+ if (directive) escapeChar = directive[1];
2124
+ }
2125
+ const out = [];
2126
+ let buffer = null;
2127
+ let startLine = 0;
2128
+ for (let i = 0; i < rawLines.length; i++) {
2129
+ const raw = rawLines[i];
2130
+ const trimmed = raw.trim();
2131
+ if (!trimmed || trimmed.startsWith("#")) continue;
2132
+ const continues = trimmed.endsWith(escapeChar);
2133
+ const fragment = continues ? trimmed.slice(0, -1).trimEnd() : trimmed;
2134
+ if (buffer === null) {
2135
+ buffer = fragment;
2136
+ startLine = i + 1;
2137
+ } else {
2138
+ buffer += ` ${fragment}`;
2139
+ }
2140
+ if (!continues) {
2141
+ const match = INSTRUCTION.exec(buffer);
2142
+ if (match) {
2143
+ out.push({
2144
+ instruction: match[1].toUpperCase(),
2145
+ args: match[2].trim(),
2146
+ startLine,
2147
+ endLine: i + 1
2148
+ });
2149
+ }
2150
+ buffer = null;
2151
+ }
2152
+ }
2153
+ if (buffer !== null) {
2154
+ const match = INSTRUCTION.exec(buffer);
2155
+ if (match) {
2156
+ out.push({
2157
+ instruction: match[1].toUpperCase(),
2158
+ args: match[2].trim(),
2159
+ startLine,
2160
+ endLine: rawLines.length
2161
+ });
2162
+ }
2163
+ }
2164
+ return out;
2165
+ }
2166
+ function parseFrom(args) {
2167
+ const tokens = args.split(/\s+/).filter(Boolean);
2168
+ const positional = [];
2169
+ for (const token of tokens) {
2170
+ if (token.startsWith("--")) continue;
2171
+ positional.push(token);
2172
+ }
2173
+ if (!positional.length) return null;
2174
+ const image = positional[0];
2175
+ const asIdx = positional.findIndex((t) => t.toUpperCase() === "AS");
2176
+ const stage = asIdx > 0 && positional[asIdx + 1] ? positional[asIdx + 1] : void 0;
2177
+ return { image, stage };
2178
+ }
2179
+ function parseCopyFrom(args) {
2180
+ const match = /--from=(\S+)/i.exec(args);
2181
+ return match ? match[1] : null;
2182
+ }
2183
+ function parseExpose(args) {
2184
+ return args.split(/\s+/).filter(Boolean).filter((token) => !token.startsWith("--"));
2185
+ }
2186
+ function copySourcePaths(args, hasFromFlag) {
2187
+ const tokens = args.split(/\s+/).filter(Boolean).filter((t) => !t.startsWith("--"));
2188
+ if (tokens.length < 2) return [];
2189
+ const sources = tokens.slice(0, -1);
2190
+ return sources.filter(
2191
+ (source) => source !== "." && !source.includes("*") && !source.includes("?") && !source.includes("[") && !source.includes("$") && !/^https?:\/\//i.test(source)
2192
+ ).map((source) => source.replace(/^\.\//, "")).sort();
2193
+ }
2194
+ var dockerfileExtractor = {
2195
+ format: "dockerfile",
2196
+ matches(rel2, category) {
2197
+ if (category === "docker") return true;
2198
+ const base = toPosix3(rel2).split("/").pop()?.toLowerCase() ?? "";
2199
+ return base === "dockerfile" || base === "containerfile" || base.startsWith("dockerfile.");
2200
+ },
2201
+ extract(rel2, source) {
2202
+ const lines = new LineIndex(source);
2203
+ const nodes = [];
2204
+ const edges = [];
2205
+ const warnings = [];
2206
+ const posixRel = toPosix3(rel2);
2207
+ const fileAddress = `dockerfile:${posixRel}`;
2208
+ nodes.push({
2209
+ kind: "image",
2210
+ name: posixRel.split("/").pop() ?? posixRel,
2211
+ qualifiedName: fileAddress,
2212
+ span: { start: 1, end: Math.max(1, lines.lineCount) },
2213
+ signature: "dockerfile",
2214
+ importance: 0.5
2215
+ });
2216
+ const stages = /* @__PURE__ */ new Map();
2217
+ const copySources = /* @__PURE__ */ new Map();
2218
+ let stageIndex = 0;
2219
+ let currentStage = null;
2220
+ for (const line of logicalLines(source)) {
2221
+ if (nodes.length >= TOOLCHAIN_NODES_PER_FILE_MAX) {
2222
+ warnings.push(`${rel2}: stopped at ${TOOLCHAIN_NODES_PER_FILE_MAX} nodes`);
2223
+ break;
2224
+ }
2225
+ switch (line.instruction) {
2226
+ case "FROM": {
2227
+ const from = parseFrom(line.args);
2228
+ if (!from) break;
2229
+ const stageName = from.stage ?? String(stageIndex);
2230
+ const stageAddress = `${fileAddress}#${stageName}`;
2231
+ stages.set(stageName.toLowerCase(), stageAddress);
2232
+ currentStage = stageAddress;
2233
+ stageIndex++;
2234
+ nodes.push({
2235
+ kind: "image",
2236
+ name: from.stage ?? `stage ${stageIndex - 1}`,
2237
+ qualifiedName: stageAddress,
2238
+ span: { start: line.startLine, end: line.endLine },
2239
+ signature: "dockerfile.stage",
2240
+ doc: safeDoc(`from ${from.image}`),
2241
+ importance: 0.4
2242
+ });
2243
+ edges.push({ kind: "contains", from: fileAddress, to: stageAddress, confidence: 1 });
2244
+ const baseStage = stages.get(from.image.toLowerCase());
2245
+ if (baseStage && baseStage !== stageAddress) {
2246
+ edges.push({
2247
+ kind: "builds_from",
2248
+ from: stageAddress,
2249
+ to: baseStage,
2250
+ confidence: 1
2251
+ });
2252
+ } else {
2253
+ const ref = parseImageRef(from.image);
2254
+ if (ref) {
2255
+ const imageAddress = `image:${ref.raw}`;
2256
+ nodes.push({
2257
+ kind: "image",
2258
+ name: ref.repository.split("/").pop() ?? ref.repository,
2259
+ qualifiedName: imageAddress,
2260
+ span: { start: line.startLine, end: line.endLine },
2261
+ signature: "dockerfile.base",
2262
+ doc: safeDoc(ref.tag ? `${ref.repository} tag ${ref.tag}` : ref.repository),
2263
+ importance: 0.3
2264
+ });
2265
+ edges.push({
2266
+ kind: "builds_from",
2267
+ from: stageAddress,
2268
+ to: imageAddress,
2269
+ confidence: 1
2270
+ });
2271
+ }
2272
+ }
2273
+ break;
2274
+ }
2275
+ case "COPY":
2276
+ case "ADD": {
2277
+ const fromRef = parseCopyFrom(line.args);
2278
+ if (fromRef) {
2279
+ if (!currentStage) break;
2280
+ const target = stages.get(fromRef.toLowerCase());
2281
+ if (target && target !== currentStage) {
2282
+ edges.push({ kind: "depends_on", from: currentStage, to: target, confidence: 1 });
2283
+ }
2284
+ break;
2285
+ }
2286
+ if (!currentStage) break;
2287
+ const forStage = copySources.get(currentStage) ?? /* @__PURE__ */ new Set();
2288
+ for (const source2 of copySourcePaths(line.args)) {
2289
+ const relativeToDockerfile = resolveRelative(posixRel, source2);
2290
+ if (relativeToDockerfile) forStage.add(relativeToDockerfile);
2291
+ forStage.add(toPosix3(source2).replace(/^\.\//, ""));
2292
+ }
2293
+ if (forStage.size) copySources.set(currentStage, forStage);
2294
+ break;
2295
+ }
2296
+ case "EXPOSE": {
2297
+ if (!currentStage) break;
2298
+ for (const port of parseExpose(line.args)) {
2299
+ const portAddress = `port:${port}`;
2300
+ nodes.push({
2301
+ kind: "property",
2302
+ name: port,
2303
+ qualifiedName: portAddress,
2304
+ span: { start: line.startLine, end: line.endLine },
2305
+ signature: "dockerfile.expose",
2306
+ importance: 0.1
2307
+ });
2308
+ edges.push({ kind: "exposes", from: currentStage, to: portAddress, confidence: 1 });
2309
+ }
2310
+ break;
2311
+ }
2312
+ }
2313
+ }
2314
+ const copySourcesByStage = {};
2315
+ for (const stage of [...copySources.keys()].sort()) {
2316
+ copySourcesByStage[stage] = [...copySources.get(stage) ?? []].sort();
2317
+ }
2318
+ return { nodes, edges, warnings, linkHints: { copySourcesByStage } };
2319
+ }
2320
+ };
2321
+
2322
+ // src/engine/toolchain/helm.ts
2323
+ function collectValuesImages(value, prefix, out) {
2324
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
2325
+ const repository = getString(value, ["repository"]);
2326
+ const tag = getString(value, ["tag"]);
2327
+ const registry = getString(value, ["registry"]);
2328
+ if (repository) {
2329
+ const full = [registry, repository].filter(Boolean).join("/");
2330
+ const raw = tag ? `${full}:${tag}` : full;
2331
+ if (!out.has(raw)) out.set(raw, prefix);
2332
+ }
2333
+ const direct = getString(value, ["image"]);
2334
+ if (direct && parseImageRef(direct)) {
2335
+ if (!out.has(direct)) out.set(direct, [...prefix, "image"]);
2336
+ }
2337
+ for (const [key, child] of getRecordEntries(value, [])) {
2338
+ if (key === "repository" || key === "tag" || key === "registry") continue;
2339
+ collectValuesImages(child, [...prefix, key], out);
2340
+ }
2341
+ }
2342
+ var helmExtractor = {
2343
+ format: "helm",
2344
+ matches(rel2) {
2345
+ const base = toPosix3(rel2).split("/").pop()?.toLowerCase() ?? "";
2346
+ if (base === "chart.yaml" || base === "chart.yml") return true;
2347
+ return /(^|\/)values([.-][\w.-]+)?\.ya?ml$/i.test(rel2) && /(^|\/)charts?\//i.test(rel2);
2348
+ },
2349
+ extract(rel2, source) {
2350
+ const { docs, warnings } = parseYamlStream(rel2, source);
2351
+ const lines = new LineIndex(source);
2352
+ const nodes = [];
2353
+ const edges = [];
2354
+ const doc = docs[0];
2355
+ if (!doc) return { nodes, edges, warnings };
2356
+ const base = toPosix3(rel2).split("/").pop()?.toLowerCase() ?? "";
2357
+ const isChartFile = base === "chart.yaml" || base === "chart.yml";
2358
+ if (isChartFile) {
2359
+ const name = getString(doc.value, ["name"]);
2360
+ if (!name) return { nodes, edges, warnings };
2361
+ const version = getString(doc.value, ["version"]);
2362
+ const appVersion = getString(doc.value, ["appVersion"]);
2363
+ const address = `chart:${name}`;
2364
+ nodes.push({
2365
+ kind: "chart",
2366
+ name,
2367
+ qualifiedName: address,
2368
+ span: spanAt(doc, lines, []),
2369
+ signature: "helm.chart",
2370
+ doc: safeDoc(
2371
+ [version && `version ${version}`, appVersion && `app ${appVersion}`].filter(Boolean).join(", ")
2372
+ ),
2373
+ importance: 0.7
2374
+ });
2375
+ for (const dependency of getArray(doc.value, ["dependencies"])) {
2376
+ const depName = getString(dependency, ["name"]);
2377
+ if (!depName) continue;
2378
+ const depVersion = getString(dependency, ["version"]);
2379
+ const depAddress = `chart:${depName}`;
2380
+ nodes.push({
2381
+ kind: "chart",
2382
+ name: depName,
2383
+ qualifiedName: depAddress,
2384
+ span: spanAt(doc, lines, ["dependencies"]),
2385
+ signature: "helm.dependency",
2386
+ doc: safeDoc(depVersion ? `version ${depVersion}` : void 0),
2387
+ importance: 0.4
2388
+ });
2389
+ edges.push({ kind: "depends_on", from: address, to: depAddress, confidence: 1 });
2390
+ }
2391
+ return { nodes, edges, warnings };
2392
+ }
2393
+ const images = /* @__PURE__ */ new Map();
2394
+ collectValuesImages(doc.value, [], images);
2395
+ for (const raw of [...images.keys()].sort()) {
2396
+ const ref = parseImageRef(raw);
2397
+ if (!ref) continue;
2398
+ nodes.push({
2399
+ kind: "image",
2400
+ name: ref.repository.split("/").pop() ?? ref.repository,
2401
+ qualifiedName: `image:${ref.raw}`,
2402
+ span: spanAt(doc, lines, images.get(raw) ?? []),
2403
+ signature: "helm.values.image",
2404
+ doc: safeDoc(ref.tag ? `${ref.repository} tag ${ref.tag}` : ref.repository),
2405
+ importance: 0.3
2406
+ });
2407
+ }
2408
+ return { nodes, edges, warnings };
2409
+ }
2410
+ };
2411
+
2412
+ // src/engine/toolchain/kubernetes.ts
2413
+ var WORKLOAD_TEMPLATE_PATHS = {
2414
+ Deployment: ["spec", "template"],
2415
+ StatefulSet: ["spec", "template"],
2416
+ DaemonSet: ["spec", "template"],
2417
+ ReplicaSet: ["spec", "template"],
2418
+ ReplicationController: ["spec", "template"],
2419
+ Job: ["spec", "template"],
2420
+ CronJob: ["spec", "jobTemplate", "spec", "template"],
2421
+ Pod: []
2422
+ };
2423
+ var CONTAINER_FIELDS = ["initContainers", "containers", "ephemeralContainers"];
2424
+ function isKubernetesDoc(value) {
2425
+ return typeof getString(value, ["apiVersion"]) === "string" && typeof getString(value, ["kind"]) === "string";
2426
+ }
2427
+ function objectAddress(value) {
2428
+ const kind = getString(value, ["kind"]);
2429
+ const name = getString(value, ["metadata", "name"]);
2430
+ if (!kind || !name) return null;
2431
+ const namespace = getString(value, ["metadata", "namespace"]) ?? "default";
2432
+ return `${namespace}/${kind}/${name}`;
2433
+ }
2434
+ function podSpecPath(kind) {
2435
+ const templatePath = WORKLOAD_TEMPLATE_PATHS[kind];
2436
+ if (!templatePath) return null;
2437
+ return [...templatePath, "spec"];
2438
+ }
2439
+ var kubernetesExtractor = {
2440
+ format: "kubernetes",
2441
+ matches(rel2, category, head) {
2442
+ if (!/\.ya?ml$/i.test(rel2)) return false;
2443
+ if (looksTemplated(head)) return false;
2444
+ if (/(^|\/)templates\//i.test(rel2)) return false;
2445
+ return /(^|\n)apiVersion:\s*\S/.test(head) && /(^|\n)kind:\s*\S/.test(head);
2446
+ },
2447
+ extract(rel2, source) {
2448
+ const { docs, warnings } = parseYamlStream(rel2, source);
2449
+ const lines = new LineIndex(source);
2450
+ const nodes = [];
2451
+ const edges = [];
2452
+ const workloadLabels = /* @__PURE__ */ new Map();
2453
+ for (const doc of docs) {
2454
+ if (nodes.length >= TOOLCHAIN_NODES_PER_FILE_MAX) {
2455
+ warnings.push(`${rel2}: stopped at ${TOOLCHAIN_NODES_PER_FILE_MAX} nodes`);
2456
+ break;
2457
+ }
2458
+ if (!isKubernetesDoc(doc.value)) continue;
2459
+ const address = objectAddress(doc.value);
2460
+ if (!address) continue;
2461
+ const kind = getString(doc.value, ["kind"]);
2462
+ const name = getString(doc.value, ["metadata", "name"]);
2463
+ const podPath = podSpecPath(kind);
2464
+ const isWorkload = podPath !== null;
2465
+ nodes.push({
2466
+ kind: isWorkload ? "workload" : "resource",
2467
+ name,
2468
+ qualifiedName: address,
2469
+ span: spanAt(doc, lines, []),
2470
+ signature: `k8s.${kind}`,
2471
+ doc: safeDoc(describeObject(doc.value, kind)),
2472
+ importance: isWorkload ? 0.6 : 0.4
2473
+ });
2474
+ if (isWorkload) {
2475
+ const labels = readLabels(doc.value, [...WORKLOAD_TEMPLATE_PATHS[kind] ?? [], "metadata", "labels"]);
2476
+ if (Object.keys(labels).length) workloadLabels.set(address, labels);
2477
+ extractContainers(doc, lines, address, podPath, nodes, edges);
2478
+ }
2479
+ for (const reference of referencedConfig(doc.value, podPath)) {
2480
+ edges.push({ kind: "mounts", from: address, to: reference, confidence: 1 });
2481
+ }
2482
+ }
2483
+ for (const doc of docs) {
2484
+ if (getString(doc.value, ["kind"]) !== "Service") continue;
2485
+ const from = objectAddress(doc.value);
2486
+ if (!from) continue;
2487
+ const selector = readLabels(doc.value, ["spec", "selector"]);
2488
+ if (!Object.keys(selector).length) continue;
2489
+ for (const [address, labels] of [...workloadLabels].sort(([a], [b]) => a.localeCompare(b, "en"))) {
2490
+ if (isSubset(selector, labels)) {
2491
+ edges.push({ kind: "exposes", from, to: address, confidence: 1 });
2492
+ }
2493
+ }
2494
+ }
2495
+ return { nodes, edges, warnings };
2496
+ }
2497
+ };
2498
+ function extractContainers(doc, lines, workloadAddress, podPath, nodes, edges) {
2499
+ const seenImages = /* @__PURE__ */ new Set();
2500
+ for (const field of CONTAINER_FIELDS) {
2501
+ const containers = getArray(doc.value, [...podPath, field]);
2502
+ containers.forEach((container, index) => {
2503
+ const image = getString(container, ["image"]);
2504
+ if (!image) return;
2505
+ const ref = parseImageRef(image);
2506
+ if (!ref) return;
2507
+ if (seenImages.has(ref.raw)) return;
2508
+ seenImages.add(ref.raw);
2509
+ nodes.push({
2510
+ kind: "image",
2511
+ name: ref.repository.split("/").pop() ?? ref.repository,
2512
+ qualifiedName: `image:${ref.raw}`,
2513
+ span: spanAt(doc, lines, [...podPath, field, index, "image"]),
2514
+ signature: "k8s.image",
2515
+ doc: safeDoc(ref.tag ? `${ref.repository} tag ${ref.tag}` : ref.repository),
2516
+ importance: 0.3
2517
+ });
2518
+ edges.push({
2519
+ kind: "builds_from",
2520
+ from: workloadAddress,
2521
+ to: `image:${ref.raw}`,
2522
+ confidence: 1
2523
+ });
2524
+ });
2525
+ }
2526
+ }
2527
+ function referencedConfig(value, podPath) {
2528
+ if (!podPath) return [];
2529
+ const out = /* @__PURE__ */ new Set();
2530
+ for (const volume of getArray(value, [...podPath, "volumes"])) {
2531
+ const configMap = getString(volume, ["configMap", "name"]);
2532
+ if (configMap) out.add(`ConfigMap/${configMap}`);
2533
+ const secret = getString(volume, ["secret", "secretName"]);
2534
+ if (secret) out.add(`Secret/${secret}`);
2535
+ const claim = getString(volume, ["persistentVolumeClaim", "claimName"]);
2536
+ if (claim) out.add(`PersistentVolumeClaim/${claim}`);
2537
+ }
2538
+ for (const field of CONTAINER_FIELDS) {
2539
+ for (const container of getArray(value, [...podPath, field])) {
2540
+ for (const envFrom of getArray(container, ["envFrom"])) {
2541
+ const configMap = getString(envFrom, ["configMapRef", "name"]);
2542
+ if (configMap) out.add(`ConfigMap/${configMap}`);
2543
+ const secret = getString(envFrom, ["secretRef", "name"]);
2544
+ if (secret) out.add(`Secret/${secret}`);
2545
+ }
2546
+ for (const env of getArray(container, ["env"])) {
2547
+ const configMap = getString(env, ["valueFrom", "configMapKeyRef", "name"]);
2548
+ if (configMap) out.add(`ConfigMap/${configMap}`);
2549
+ const secret = getString(env, ["valueFrom", "secretKeyRef", "name"]);
2550
+ if (secret) out.add(`Secret/${secret}`);
2551
+ }
2552
+ }
2553
+ }
2554
+ return [...out].sort();
2555
+ }
2556
+ function readLabels(value, path13) {
2557
+ const out = {};
2558
+ for (const [key, raw] of getRecordEntries(value, path13)) {
2559
+ if (typeof raw === "string") out[key] = raw;
2560
+ else if (typeof raw === "number" || typeof raw === "boolean") out[key] = String(raw);
2561
+ }
2562
+ return out;
2563
+ }
2564
+ function isSubset(selector, labels) {
2565
+ const keys = Object.keys(selector);
2566
+ if (!keys.length) return false;
2567
+ return keys.every((key) => labels[key] === selector[key]);
2568
+ }
2569
+ function describeObject(value, kind) {
2570
+ const parts = [];
2571
+ const replicas = getPath(value, ["spec", "replicas"]);
2572
+ if (typeof replicas === "number") parts.push(`${replicas} replica${replicas === 1 ? "" : "s"}`);
2573
+ const podPath = podSpecPath(kind);
2574
+ if (podPath) {
2575
+ const count = CONTAINER_FIELDS.reduce(
2576
+ (sum, field) => sum + getArray(value, [...podPath, field]).length,
2577
+ 0
2578
+ );
2579
+ if (count) parts.push(`${count} container${count === 1 ? "" : "s"}`);
2580
+ }
2581
+ if (kind === "Service") {
2582
+ const type = getString(value, ["spec", "type"]);
2583
+ if (type) parts.push(type);
2584
+ const ports = getArray(value, ["spec", "ports"]).map((p) => getString(p, ["port"])).filter(Boolean);
2585
+ if (ports.length) parts.push(`port ${ports.join(", ")}`);
2586
+ }
2587
+ return parts.join(", ");
2588
+ }
2589
+ var require2 = createRequire(import.meta.url);
2590
+ var TOOLCHAIN_GRAMMARS = {
2591
+ hcl: { wasm: "tree-sitter-hcl", pkg: "@tree-sitter-grammars/tree-sitter-hcl" }
2592
+ };
2593
+ var languageCache = /* @__PURE__ */ new Map();
2594
+ function toolchainGrammarDirs(pkg) {
2595
+ const dirs = [...grammarDirs()];
2596
+ try {
2597
+ dirs.push(path12.dirname(require2.resolve(`${pkg}/package.json`)));
2598
+ } catch {
2599
+ }
2600
+ return dirs;
2601
+ }
2602
+ function resolveToolchainGrammar(id) {
2603
+ const spec = TOOLCHAIN_GRAMMARS[id];
2604
+ if (!spec) return null;
2605
+ for (const dir of toolchainGrammarDirs(spec.pkg)) {
2606
+ const candidate = path12.join(dir, `${spec.wasm}.wasm`);
2607
+ if (fs13.existsSync(candidate)) return candidate;
2608
+ }
2609
+ return null;
2610
+ }
2611
+ async function loadToolchainLanguage(id) {
2612
+ const cached = languageCache.get(id);
2613
+ if (cached) return cached;
2614
+ const wasmPath = resolveToolchainGrammar(id);
2615
+ if (!wasmPath) return null;
2616
+ try {
2617
+ await ensureParserInit();
2618
+ const language = await Language.load(fs13.readFileSync(wasmPath));
2619
+ languageCache.set(id, language);
2620
+ return language;
2621
+ } catch {
2622
+ return null;
2623
+ }
2624
+ }
2625
+ async function toolchainParser(id) {
2626
+ const language = await loadToolchainLanguage(id);
2627
+ if (!language) return null;
2628
+ const parser = new Parser();
2629
+ parser.setLanguage(language);
2630
+ return parser;
2631
+ }
2632
+ function toolchainGrammarSetVersion() {
2633
+ const parts = [];
2634
+ for (const id of Object.keys(TOOLCHAIN_GRAMMARS).sort()) {
2635
+ const { pkg } = TOOLCHAIN_GRAMMARS[id];
2636
+ let version = "bundled";
2637
+ try {
2638
+ const json = require2.resolve(`${pkg}/package.json`);
2639
+ version = JSON.parse(fs13.readFileSync(json, "utf8")).version;
2640
+ } catch {
2641
+ }
2642
+ parts.push(`${pkg}@${version}`);
2643
+ }
2644
+ return parts.join(",");
2645
+ }
2646
+
2647
+ // src/engine/toolchain/terraform.ts
2648
+ var BUILTIN_REF_PREFIXES = /* @__PURE__ */ new Set([
2649
+ "count",
2650
+ "each",
2651
+ "self",
2652
+ "path",
2653
+ "terraform"
2654
+ ]);
2655
+ var BLOCK_SPECS = {
2656
+ resource: {
2657
+ kind: "resource",
2658
+ labels: 2,
2659
+ address: ([type, name]) => `${type}.${name}`,
2660
+ signature: "terraform.resource",
2661
+ importance: 0.5
2662
+ },
2663
+ data: {
2664
+ kind: "resource",
2665
+ labels: 2,
2666
+ address: ([type, name]) => `data.${type}.${name}`,
2667
+ signature: "terraform.data",
2668
+ importance: 0.3
2669
+ },
2670
+ module: {
2671
+ kind: "module",
2672
+ labels: 1,
2673
+ address: ([name]) => `module.${name}`,
2674
+ signature: "terraform.module",
2675
+ importance: 0.6
2676
+ },
2677
+ variable: {
2678
+ kind: "property",
2679
+ labels: 1,
2680
+ address: ([name]) => `var.${name}`,
2681
+ signature: "terraform.variable",
2682
+ importance: 0.2
2683
+ },
2684
+ output: {
2685
+ kind: "property",
2686
+ labels: 1,
2687
+ address: ([name]) => `output.${name}`,
2688
+ signature: "terraform.output",
2689
+ importance: 0.3
2690
+ },
2691
+ provider: {
2692
+ kind: "package",
2693
+ labels: 1,
2694
+ address: ([name]) => `provider.${name}`,
2695
+ signature: "terraform.provider",
2696
+ importance: 0.4
2697
+ }
2698
+ };
2699
+ function stringLitValue(node) {
2700
+ for (const child of node.namedChildren) {
2701
+ if (child?.type === "template_literal") return child.text;
2702
+ }
2703
+ return "";
2704
+ }
2705
+ function topLevelBlocks(root) {
2706
+ const out = [];
2707
+ for (const bodyCandidate of root.namedChildren) {
2708
+ if (!bodyCandidate) continue;
2709
+ const body = bodyCandidate.type === "body" ? bodyCandidate : null;
2710
+ if (!body) continue;
2711
+ for (const child of body.namedChildren) {
2712
+ if (child?.type === "block") out.push(child);
2713
+ }
2714
+ }
2715
+ return out;
2716
+ }
2717
+ function blockBody(block) {
2718
+ for (const child of block.namedChildren) {
2719
+ if (child?.type === "body") return child;
2720
+ }
2721
+ return null;
2722
+ }
2723
+ function attributes(body) {
2724
+ const out = [];
2725
+ for (const child of body.namedChildren) {
2726
+ if (child?.type !== "attribute") continue;
2727
+ let name = "";
2728
+ let expr = null;
2729
+ for (const part of child.namedChildren) {
2730
+ if (!part) continue;
2731
+ if (!name && part.type === "identifier") name = part.text;
2732
+ else if (part.type === "expression") expr = part;
2733
+ }
2734
+ if (name) out.push({ name, expr, node: child });
2735
+ }
2736
+ return out;
2737
+ }
2738
+ function nestedBlocks(body) {
2739
+ const out = [];
2740
+ for (const child of body.namedChildren) {
2741
+ if (child?.type === "block") out.push(child);
2742
+ }
2743
+ return out;
2744
+ }
2745
+ function blockHead(block) {
2746
+ let type = "";
2747
+ const labels = [];
2748
+ for (const child of block.namedChildren) {
2749
+ if (!child) continue;
2750
+ if (!type && child.type === "identifier") type = child.text;
2751
+ else if (child.type === "string_lit") labels.push(stringLitValue(child));
2752
+ else if (child.type === "body") break;
2753
+ }
2754
+ return { type, labels };
2755
+ }
2756
+ function referencedAddresses(expr, out) {
2757
+ if (!expr) return;
2758
+ const stack = [expr];
2759
+ while (stack.length) {
2760
+ const node = stack.pop();
2761
+ if (!node) continue;
2762
+ if (node.type === "variable_expr") {
2763
+ const root = node.text;
2764
+ const parts = [root];
2765
+ let sibling = node.nextNamedSibling;
2766
+ while (sibling && sibling.type === "get_attr") {
2767
+ const id = sibling.namedChildren.find((c) => c?.type === "identifier");
2768
+ if (!id) break;
2769
+ parts.push(id.text);
2770
+ sibling = sibling.nextNamedSibling;
2771
+ }
2772
+ const address = terraformAddress(parts);
2773
+ if (address) out.add(address);
2774
+ continue;
2775
+ }
2776
+ for (const child of node.namedChildren) {
2777
+ if (child) stack.push(child);
2778
+ }
2779
+ }
2780
+ }
2781
+ function terraformAddress(parts) {
2782
+ const [head, ...rest] = parts;
2783
+ if (!head || BUILTIN_REF_PREFIXES.has(head)) return null;
2784
+ if (head === "var" || head === "local") return rest.length ? `${head}.${rest[0]}` : null;
2785
+ if (head === "module") return rest.length ? `module.${rest[0]}` : null;
2786
+ if (head === "data") return rest.length >= 2 ? `data.${rest[0]}.${rest[1]}` : null;
2787
+ return rest.length ? `${head}.${rest[0]}` : null;
2788
+ }
2789
+ function extractRequiredProviders(terraformBlock, lines, nodes, seen) {
2790
+ const body = blockBody(terraformBlock);
2791
+ if (!body) return;
2792
+ for (const nested of nestedBlocks(body)) {
2793
+ const { type } = blockHead(nested);
2794
+ if (type !== "required_providers") continue;
2795
+ const inner = blockBody(nested);
2796
+ if (!inner) continue;
2797
+ for (const attr of attributes(inner)) {
2798
+ let source;
2799
+ let version;
2800
+ const objectAttrs = attr.expr ? collectObjectAttributes(attr.expr) : [];
2801
+ if (objectAttrs.length) {
2802
+ for (const { key, value } of objectAttrs) {
2803
+ if (key === "source") source = value;
2804
+ else if (key === "version") version = value;
2805
+ }
2806
+ } else if (attr.expr) {
2807
+ const literal = findFirst(attr.expr, "string_lit");
2808
+ if (literal) {
2809
+ version = stringLitValue(literal);
2810
+ source = `hashicorp/${attr.name}`;
2811
+ }
2812
+ }
2813
+ const address = `provider.${attr.name}`;
2814
+ if (seen.has(address)) continue;
2815
+ seen.add(address);
2816
+ nodes.push({
2817
+ kind: "package",
2818
+ name: attr.name,
2819
+ qualifiedName: address,
2820
+ span: lines.span(attr.node.startIndex, attr.node.endIndex),
2821
+ signature: "terraform.required_provider",
2822
+ doc: safeDoc(
2823
+ [source && `source ${source}`, version && `version ${version}`].filter(Boolean).join(", ")
2824
+ ),
2825
+ importance: 0.4
2826
+ });
2827
+ }
2828
+ }
2829
+ }
2830
+ function collectObjectAttributes(expr) {
2831
+ const out = [];
2832
+ const stack = [expr];
2833
+ while (stack.length) {
2834
+ const node = stack.pop();
2835
+ if (!node) continue;
2836
+ if (node.type === "object_elem") {
2837
+ const key = node.namedChildren.find((c) => c?.type === "expression");
2838
+ const rest = node.namedChildren.filter((c) => c?.type === "expression");
2839
+ const valueNode = rest.length > 1 ? rest[1] : null;
2840
+ const keyText = key ? plainText(key) : "";
2841
+ const valueText = valueNode ? plainText(valueNode) : "";
2842
+ if (keyText) out.push({ key: keyText, value: valueText });
2843
+ continue;
2844
+ }
2845
+ for (const child of node.namedChildren) {
2846
+ if (child) stack.push(child);
2847
+ }
2848
+ }
2849
+ return out;
2850
+ }
2851
+ function plainText(expr) {
2852
+ const lit = findFirst(expr, "string_lit");
2853
+ if (lit) return stringLitValue(lit);
2854
+ return expr.text.trim();
2855
+ }
2856
+ function findFirst(node, type) {
2857
+ const stack = [node];
2858
+ while (stack.length) {
2859
+ const current = stack.pop();
2860
+ if (!current) continue;
2861
+ if (current.type === type) return current;
2862
+ for (const child of current.namedChildren) {
2863
+ if (child) stack.push(child);
2864
+ }
2865
+ }
2866
+ return null;
2867
+ }
2868
+ var terraformExtractor = {
2869
+ format: "terraform",
2870
+ matches(rel2) {
2871
+ const lower = rel2.toLowerCase();
2872
+ return lower.endsWith(".tf") || lower.endsWith(".tofu");
2873
+ },
2874
+ async extract(rel2, source) {
2875
+ const parser = await toolchainParser("hcl");
2876
+ if (!parser) {
2877
+ return {
2878
+ nodes: [],
2879
+ edges: [],
2880
+ warnings: [`${rel2}: HCL grammar unavailable \u2014 no Terraform structure extracted`]
2881
+ };
2882
+ }
2883
+ const tree = parser.parse(source);
2884
+ if (!tree) return { nodes: [], edges: [], warnings: [`${rel2}: HCL parse produced no tree`] };
2885
+ const lines = new LineIndex(source);
2886
+ const nodes = [];
2887
+ const edges = [];
2888
+ const warnings = [];
2889
+ const declared = /* @__PURE__ */ new Set();
2890
+ const pendingEdges = [];
2891
+ if (tree.rootNode.hasError) {
2892
+ warnings.push(`${rel2}: HCL parse recovered from a syntax error \u2014 extraction may be partial`);
2893
+ }
2894
+ for (const block of topLevelBlocks(tree.rootNode)) {
2895
+ if (nodes.length >= TOOLCHAIN_NODES_PER_FILE_MAX) {
2896
+ warnings.push(`${rel2}: stopped at ${TOOLCHAIN_NODES_PER_FILE_MAX} nodes`);
2897
+ break;
2898
+ }
2899
+ const { type, labels } = blockHead(block);
2900
+ if (type === "terraform") {
2901
+ extractRequiredProviders(block, lines, nodes, declared);
2902
+ continue;
2903
+ }
2904
+ const spec = BLOCK_SPECS[type];
2905
+ if (!spec || labels.length < spec.labels) continue;
2906
+ const address = spec.address(labels);
2907
+ if (declared.has(address)) continue;
2908
+ declared.add(address);
2909
+ const body = blockBody(block);
2910
+ const attrs = body ? attributes(body) : [];
2911
+ const summaryParts = [];
2912
+ for (const attr of attrs) {
2913
+ if (attr.name === "source" || attr.name === "version" || attr.name === "description") {
2914
+ const value = attr.expr ? plainText(attr.expr) : "";
2915
+ if (value) summaryParts.push(`${attr.name} ${value}`);
2916
+ }
2917
+ }
2918
+ nodes.push({
2919
+ kind: spec.kind,
2920
+ name: labels[labels.length - 1] ?? type,
2921
+ qualifiedName: address,
2922
+ span: lines.span(block.startIndex, block.endIndex),
2923
+ signature: spec.signature,
2924
+ doc: safeDoc(summaryParts.join(", ")),
2925
+ importance: spec.importance
2926
+ });
2927
+ if (!body) continue;
2928
+ const references = /* @__PURE__ */ new Set();
2929
+ for (const attr of attrs) {
2930
+ referencedAddresses(attr.expr, references);
2931
+ }
2932
+ const nestedStack = nestedBlocks(body);
2933
+ while (nestedStack.length) {
2934
+ const nested = nestedStack.pop();
2935
+ if (!nested) continue;
2936
+ const innerBody = blockBody(nested);
2937
+ if (!innerBody) continue;
2938
+ for (const attr of attributes(innerBody)) referencedAddresses(attr.expr, references);
2939
+ nestedStack.push(...nestedBlocks(innerBody));
2940
+ }
2941
+ references.delete(address);
2942
+ for (const target of [...references].sort()) {
2943
+ pendingEdges.push({ kind: "depends_on", from: address, to: target, confidence: 1 });
2944
+ }
2945
+ }
2946
+ for (const edge of pendingEdges) {
2947
+ if (declared.has(edge.to)) edges.push(edge);
2948
+ }
2949
+ return { nodes, edges, warnings };
2950
+ }
2951
+ };
2952
+
2953
+ // src/engine/toolchain/workflows.ts
2954
+ var MAX_STEPS_PER_JOB = 100;
2955
+ var GITLAB_RESERVED = /* @__PURE__ */ new Set([
2956
+ "stages",
2957
+ "variables",
2958
+ "default",
2959
+ "include",
2960
+ "workflow",
2961
+ "image",
2962
+ "services",
2963
+ "before_script",
2964
+ "after_script",
2965
+ "cache",
2966
+ "pages"
2967
+ ]);
2968
+ function isGitHubWorkflowPath(rel2) {
2969
+ return /(^|\/)\.github\/workflows\/[^/]+\.ya?ml$/i.test(toPosix3(rel2));
2970
+ }
2971
+ function isGitLabCiPath(rel2) {
2972
+ const base = toPosix3(rel2).split("/").pop()?.toLowerCase() ?? "";
2973
+ return base === ".gitlab-ci.yml" || base === ".gitlab-ci.yaml";
2974
+ }
2975
+ function imagesBuiltBy(script) {
2976
+ const out = /* @__PURE__ */ new Set();
2977
+ const buildTag = /\b(?:docker|podman)\s+(?:buildx\s+)?build\b[^\n]*?(?:-t|--tag)[=\s]+(\S+)/g;
2978
+ for (const match of script.matchAll(buildTag)) out.add(match[1]);
2979
+ const push2 = /\b(?:docker|podman)\s+push\s+(\S+)/g;
2980
+ for (const match of script.matchAll(push2)) out.add(match[1]);
2981
+ return [...out].filter((tag) => !tag.includes("${") && !tag.includes("{{")).sort();
2982
+ }
2983
+ function terraformAppliedDirs(script, workingDirectory) {
2984
+ const out = /* @__PURE__ */ new Set();
2985
+ const apply = /\b(?:terraform|tofu)\s+(?:-chdir=(\S+)\s+)?(?:apply|destroy)\b/g;
2986
+ for (const match of script.matchAll(apply)) {
2987
+ out.add(match[1] ?? workingDirectory ?? ".");
2988
+ }
2989
+ return [...out].sort();
2990
+ }
2991
+ function collectSecretReferences(value, out) {
2992
+ const stack = [value];
2993
+ while (stack.length) {
2994
+ const current = stack.pop();
2995
+ if (typeof current === "string") {
2996
+ for (const match of current.matchAll(/secrets\.([A-Za-z_][A-Za-z0-9_]*)/g)) {
2997
+ out.add(`secret:${match[1]}`);
2998
+ }
2999
+ continue;
3000
+ }
3001
+ if (Array.isArray(current)) {
3002
+ stack.push(...current);
3003
+ continue;
3004
+ }
3005
+ if (current && typeof current === "object") {
3006
+ stack.push(...Object.values(current));
3007
+ }
3008
+ }
3009
+ }
3010
+ var githubActionsExtractor = {
3011
+ format: "github-actions",
3012
+ matches(rel2) {
3013
+ return isGitHubWorkflowPath(rel2);
3014
+ },
3015
+ extract(rel2, source) {
3016
+ const { docs, warnings } = parseYamlStream(rel2, source);
3017
+ const lines = new LineIndex(source);
3018
+ const nodes = [];
3019
+ const edges = [];
3020
+ const doc = docs[0];
3021
+ if (!doc) return { nodes, edges, warnings };
3022
+ const workflowName = getString(doc.value, ["name"]) ?? toPosix3(rel2).split("/").pop() ?? rel2;
3023
+ const workflowAddress = `workflow:${toPosix3(rel2)}`;
3024
+ nodes.push({
3025
+ kind: "job",
3026
+ name: workflowName,
3027
+ qualifiedName: workflowAddress,
3028
+ span: spanAt(doc, lines, []),
3029
+ signature: "gha.workflow",
3030
+ doc: safeDoc(`GitHub Actions workflow`),
3031
+ importance: 0.6
3032
+ });
3033
+ const onValue = getPath(doc.value, ["on"]) ?? getPath(doc.value, [true]);
3034
+ for (const event of triggerEvents(onValue)) {
3035
+ const eventAddress = `event:${event}`;
3036
+ nodes.push({
3037
+ kind: "property",
3038
+ name: event,
3039
+ qualifiedName: eventAddress,
3040
+ span: spanAt(doc, lines, ["on"]),
3041
+ signature: "gha.event",
3042
+ importance: 0.2
3043
+ });
3044
+ edges.push({ kind: "triggers", from: eventAddress, to: workflowAddress, confidence: 1 });
3045
+ }
3046
+ const jobs = getRecordEntries(doc.value, ["jobs"]);
3047
+ const jobAddresses = new Set(jobs.map(([id]) => `job:${id}`));
3048
+ for (const [jobId, job] of jobs) {
3049
+ if (nodes.length >= TOOLCHAIN_NODES_PER_FILE_MAX) {
3050
+ warnings.push(`${rel2}: stopped at ${TOOLCHAIN_NODES_PER_FILE_MAX} nodes`);
3051
+ break;
3052
+ }
3053
+ const jobAddress = `job:${jobId}`;
3054
+ const runsOn = getPath(job, ["runs-on"]);
3055
+ const steps = getArray(job, ["steps"]);
3056
+ nodes.push({
3057
+ kind: "job",
3058
+ name: getString(job, ["name"]) ?? jobId,
3059
+ qualifiedName: jobAddress,
3060
+ span: spanAt(doc, lines, ["jobs", jobId]),
3061
+ signature: "gha.job",
3062
+ doc: safeDoc(
3063
+ [
3064
+ typeof runsOn === "string" ? `runs-on ${runsOn}` : null,
3065
+ steps.length ? `${steps.length} step${steps.length === 1 ? "" : "s"}` : null
3066
+ ].filter(Boolean).join(", ")
3067
+ ),
3068
+ importance: 0.5
3069
+ });
3070
+ edges.push({ kind: "contains", from: workflowAddress, to: jobAddress, confidence: 1 });
3071
+ for (const need of asStringList(getPath(job, ["needs"]))) {
3072
+ const to = `job:${need}`;
3073
+ if (jobAddresses.has(to)) {
3074
+ edges.push({ kind: "depends_on", from: jobAddress, to, confidence: 1 });
3075
+ }
3076
+ }
3077
+ steps.slice(0, MAX_STEPS_PER_JOB).forEach((step, index) => {
3078
+ const uses = getString(step, ["uses"]);
3079
+ const run = getString(step, ["run"]);
3080
+ const stepName = getString(step, ["name"]) ?? uses ?? (run ? run.split("\n")[0] : `step ${index + 1}`);
3081
+ const stepAddress = `${jobAddress}#${index}`;
3082
+ nodes.push({
3083
+ kind: "step",
3084
+ name: stepName.slice(0, 80),
3085
+ qualifiedName: stepAddress,
3086
+ span: spanAt(doc, lines, ["jobs", jobId, "steps", index]),
3087
+ signature: uses ? "gha.step.uses" : "gha.step.run",
3088
+ // `run:` bodies are shell scripts and are exactly where a token gets
3089
+ // pasted — safeDoc redacts, and we keep only the first line.
3090
+ doc: safeDoc(uses ? `uses ${uses}` : run?.split("\n")[0]),
3091
+ importance: 0.2
3092
+ });
3093
+ edges.push({ kind: "contains", from: jobAddress, to: stepAddress, confidence: 1 });
3094
+ if (uses) {
3095
+ const actionAddress = `action:${uses}`;
3096
+ nodes.push({
3097
+ kind: "external",
3098
+ name: uses.split("@")[0],
3099
+ qualifiedName: actionAddress,
3100
+ span: spanAt(doc, lines, ["jobs", jobId, "steps", index, "uses"]),
3101
+ signature: "gha.action",
3102
+ importance: 0.2
3103
+ });
3104
+ edges.push({ kind: "depends_on", from: stepAddress, to: actionAddress, confidence: 1 });
3105
+ }
3106
+ if (run) {
3107
+ const stepSpan = spanAt(doc, lines, ["jobs", jobId, "steps", index]);
3108
+ const workingDirectory = getString(step, ["working-directory"]) ?? getString(job, ["defaults", "run", "working-directory"]);
3109
+ emitScriptEffects(rel2, run, workingDirectory, jobAddress, stepSpan, nodes, edges);
3110
+ }
3111
+ });
3112
+ if (steps.length > MAX_STEPS_PER_JOB) {
3113
+ warnings.push(`${rel2}: job ${jobId} has ${steps.length} steps \u2014 enumerated the first ${MAX_STEPS_PER_JOB}`);
3114
+ }
3115
+ const secrets = /* @__PURE__ */ new Set();
3116
+ collectSecretReferences(job, secrets);
3117
+ for (const secret of [...secrets].sort()) {
3118
+ nodes.push({
3119
+ kind: "property",
3120
+ name: secret.slice("secret:".length),
3121
+ qualifiedName: secret,
3122
+ span: spanAt(doc, lines, ["jobs", jobId]),
3123
+ signature: "gha.secret-ref",
3124
+ importance: 0.2
3125
+ });
3126
+ edges.push({ kind: "mounts", from: jobAddress, to: secret, confidence: 1 });
3127
+ }
3128
+ }
3129
+ return { nodes, edges, warnings };
3130
+ }
3131
+ };
3132
+ var gitlabCiExtractor = {
3133
+ format: "gitlab-ci",
3134
+ matches(rel2) {
3135
+ return isGitLabCiPath(rel2);
3136
+ },
3137
+ extract(rel2, source) {
3138
+ const { docs, warnings } = parseYamlStream(rel2, source);
3139
+ const lines = new LineIndex(source);
3140
+ const nodes = [];
3141
+ const edges = [];
3142
+ const doc = docs[0];
3143
+ if (!doc) return { nodes, edges, warnings };
3144
+ const pipelineAddress = `workflow:${toPosix3(rel2)}`;
3145
+ nodes.push({
3146
+ kind: "job",
3147
+ name: "GitLab CI",
3148
+ qualifiedName: pipelineAddress,
3149
+ span: spanAt(doc, lines, []),
3150
+ signature: "gitlab.pipeline",
3151
+ importance: 0.6
3152
+ });
3153
+ const jobs = getRecordEntries(doc.value, []).filter(
3154
+ ([key, value]) => !GITLAB_RESERVED.has(key) && !key.startsWith(".") && value !== null && typeof value === "object" && !Array.isArray(value)
3155
+ );
3156
+ const jobAddresses = new Set(jobs.map(([id]) => `job:${id}`));
3157
+ for (const [jobId, job] of jobs) {
3158
+ if (nodes.length >= TOOLCHAIN_NODES_PER_FILE_MAX) {
3159
+ warnings.push(`${rel2}: stopped at ${TOOLCHAIN_NODES_PER_FILE_MAX} nodes`);
3160
+ break;
3161
+ }
3162
+ const jobAddress = `job:${jobId}`;
3163
+ const stage = getString(job, ["stage"]);
3164
+ const image = getString(job, ["image"]) ?? getString(job, ["image", "name"]);
3165
+ nodes.push({
3166
+ kind: "job",
3167
+ name: jobId,
3168
+ qualifiedName: jobAddress,
3169
+ span: spanAt(doc, lines, [jobId]),
3170
+ signature: "gitlab.job",
3171
+ doc: safeDoc([stage && `stage ${stage}`, image && `image ${image}`].filter(Boolean).join(", ")),
3172
+ importance: 0.5
3173
+ });
3174
+ edges.push({ kind: "contains", from: pipelineAddress, to: jobAddress, confidence: 1 });
3175
+ for (const key of ["needs", "dependencies"]) {
3176
+ for (const need of asStringList(getPath(job, [key]))) {
3177
+ const to = `job:${need}`;
3178
+ if (jobAddresses.has(to)) {
3179
+ edges.push({ kind: "depends_on", from: jobAddress, to, confidence: 1 });
3180
+ }
3181
+ }
3182
+ }
3183
+ }
3184
+ return { nodes, edges, warnings };
3185
+ }
3186
+ };
3187
+ function emitScriptEffects(rel2, script, workingDirectory, jobAddress, span, nodes, edges) {
3188
+ for (const tag of imagesBuiltBy(script)) {
3189
+ const ref = parseImageRef(tag);
3190
+ if (!ref) continue;
3191
+ const address = `image:${ref.raw}`;
3192
+ nodes.push({
3193
+ kind: "image",
3194
+ name: ref.repository.split("/").pop() ?? ref.repository,
3195
+ qualifiedName: address,
3196
+ span,
3197
+ signature: "ci.builds-image",
3198
+ doc: safeDoc(ref.tag ? `${ref.repository} tag ${ref.tag}` : ref.repository),
3199
+ importance: 0.4
3200
+ });
3201
+ edges.push({ kind: "provisions", from: jobAddress, to: address, confidence: 1 });
3202
+ }
3203
+ for (const dir of terraformAppliedDirs(script, workingDirectory)) {
3204
+ const resolved = normaliseWorkspacePath(dir);
3205
+ const address = `tfdir:${resolved}`;
3206
+ nodes.push({
3207
+ kind: "resource",
3208
+ name: resolved,
3209
+ qualifiedName: address,
3210
+ span,
3211
+ signature: "ci.terraform-apply",
3212
+ doc: safeDoc(`terraform apply in ${resolved}`),
3213
+ importance: 0.4
3214
+ });
3215
+ edges.push({ kind: "deploys", from: jobAddress, to: address, confidence: 1 });
3216
+ }
3217
+ }
3218
+ function normaliseWorkspacePath(dir) {
3219
+ const out = [];
3220
+ for (const part of toPosix3(dir).split("/")) {
3221
+ if (!part || part === ".") continue;
3222
+ if (part === "..") {
3223
+ out.pop();
3224
+ continue;
3225
+ }
3226
+ out.push(part);
3227
+ }
3228
+ return out.join("/") || ".";
3229
+ }
3230
+ function triggerEvents(value) {
3231
+ if (typeof value === "string") return [value];
3232
+ if (Array.isArray(value)) return value.filter((v) => typeof v === "string").sort();
3233
+ if (value && typeof value === "object") return Object.keys(value).sort();
3234
+ return [];
3235
+ }
3236
+ function asStringList(value) {
3237
+ if (typeof value === "string") return [value];
3238
+ if (!Array.isArray(value)) return [];
3239
+ const out = [];
3240
+ for (const item of value) {
3241
+ if (typeof item === "string") out.push(item);
3242
+ else if (item && typeof item === "object") {
3243
+ const job = item.job;
3244
+ if (typeof job === "string") out.push(job);
3245
+ }
3246
+ }
3247
+ return out.sort();
3248
+ }
3249
+
3250
+ // src/engine/toolchain/link.ts
3251
+ var LINK_CONFIDENCE = {
3252
+ /** Compose names the Dockerfile path outright. */
3253
+ composeBuild: 0.95,
3254
+ /** A CI job builds tag X; a workload runs tag X. Same string, both sides. */
3255
+ imageIdentity: 0.8,
3256
+ /** A CI job applies Terraform in a directory that contains these resources. */
3257
+ terraformDir: 0.75,
3258
+ /** A Dockerfile copies a path that exists in the repository. */
3259
+ dockerfileCopy: 0.6
3260
+ };
3261
+ function linkToolchain(input) {
3262
+ const { nodes, edges, fileNodes, stageCopySources } = input;
3263
+ const out = /* @__PURE__ */ new Map();
3264
+ const counts = {};
3265
+ const add3 = (kind, src, dst, confidence, join11) => {
3266
+ if (src === dst) return;
3267
+ const id = edgeId(kind, src, dst);
3268
+ if (out.has(id)) return;
3269
+ out.set(id, {
3270
+ id,
3271
+ kind,
3272
+ src,
3273
+ dst,
3274
+ resolution: "heuristic",
3275
+ confidence,
3276
+ epistemic: "name-matched"
3277
+ });
3278
+ counts[join11] = (counts[join11] ?? 0) + 1;
3279
+ };
3280
+ const declared = new Set(edges.map((e) => `${e.kind} ${e.src} ${e.dst}`));
3281
+ const dockerfileByPath = /* @__PURE__ */ new Map();
3282
+ const dockerfileRefs = [];
3283
+ const imageSites = /* @__PURE__ */ new Map();
3284
+ const terraformDirMarkers = [];
3285
+ const terraformNodesByDir = /* @__PURE__ */ new Map();
3286
+ const stagesByDockerfile = /* @__PURE__ */ new Map();
3287
+ for (const node of nodes) {
3288
+ const qn = node.qualifiedName;
3289
+ if (qn.startsWith("dockerfile:")) {
3290
+ const target = qn.slice("dockerfile:".length);
3291
+ if (target.includes("#")) {
3292
+ push(stagesByDockerfile, target.split("#")[0], node);
3293
+ continue;
3294
+ }
3295
+ if (toPosix3(node.file) === target) dockerfileByPath.set(target, node);
3296
+ else dockerfileRefs.push(node);
3297
+ continue;
3298
+ }
3299
+ if (qn.startsWith("image:")) {
3300
+ const ref = parseImageRef(qn.slice("image:".length));
3301
+ if (ref) push(imageSites, imageIdentity(ref), node);
3302
+ continue;
3303
+ }
3304
+ if (qn.startsWith("tfdir:")) {
3305
+ terraformDirMarkers.push(node);
3306
+ continue;
3307
+ }
3308
+ if (node.lang === "terraform") push(terraformNodesByDir, dirOf(toPosix3(node.file)), node);
3309
+ }
3310
+ for (const ref of dockerfileRefs) {
3311
+ const target = dockerfileByPath.get(ref.qualifiedName.slice("dockerfile:".length));
3312
+ if (!target) continue;
3313
+ add3("builds_from", ref.id, target.id, LINK_CONFIDENCE.composeBuild, "compose\u2192dockerfile");
3314
+ }
3315
+ const producerOf = /* @__PURE__ */ new Map();
3316
+ const consumerOf = /* @__PURE__ */ new Map();
3317
+ for (const edge of edges) {
3318
+ if (edge.kind === "provisions") push(producerOf, edge.dst, edge.src);
3319
+ else if (edge.kind === "builds_from") push(consumerOf, edge.dst, edge.src);
3320
+ }
3321
+ for (const identity of [...imageSites.keys()].sort((a, b) => a.localeCompare(b, "en"))) {
3322
+ const sites = imageSites.get(identity) ?? [];
3323
+ if (sites.length < 2) continue;
3324
+ const producers = /* @__PURE__ */ new Set();
3325
+ const consumers = /* @__PURE__ */ new Set();
3326
+ for (const site of sites) {
3327
+ for (const id of producerOf.get(site.id) ?? []) producers.add(id);
3328
+ for (const id of consumerOf.get(site.id) ?? []) consumers.add(id);
3329
+ }
3330
+ for (const producer of [...producers].sort()) {
3331
+ for (const consumer of [...consumers].sort()) {
3332
+ if (declared.has(`deploys ${producer} ${consumer}`)) continue;
3333
+ add3("deploys", producer, consumer, LINK_CONFIDENCE.imageIdentity, "ci\u2192workload");
3334
+ }
3335
+ }
3336
+ const ordered = [...sites].sort((a, b) => a.id.localeCompare(b.id, "en"));
3337
+ for (let i = 1; i < ordered.length; i++) {
3338
+ add3("references", ordered[0].id, ordered[i].id, LINK_CONFIDENCE.imageIdentity, "image\u2194image");
3339
+ }
3340
+ }
3341
+ const terraformDirs = [...terraformNodesByDir.keys()].sort((a, b) => a.localeCompare(b, "en"));
3342
+ for (const marker of [...terraformDirMarkers].sort((a, b) => a.id.localeCompare(b.id, "en"))) {
3343
+ const dir = marker.qualifiedName.slice("tfdir:".length);
3344
+ for (const resourceDir of terraformDirs) {
3345
+ if (resourceDir !== dir && !resourceDir.startsWith(`${dir}/`)) continue;
3346
+ for (const resource of terraformNodesByDir.get(resourceDir) ?? []) {
3347
+ if (resource.signature !== "terraform.resource" && resource.signature !== "terraform.module") {
3348
+ continue;
3349
+ }
3350
+ add3("deploys", marker.id, resource.id, LINK_CONFIDENCE.terraformDir, "ci\u2192terraform");
3351
+ }
3352
+ }
3353
+ }
3354
+ for (const file of [...stagesByDockerfile.keys()].sort((a, b) => a.localeCompare(b, "en"))) {
3355
+ const stages = [...stagesByDockerfile.get(file) ?? []].sort(
3356
+ (a, b) => a.qualifiedName.localeCompare(b.qualifiedName, "en")
3357
+ );
3358
+ for (const stage of stages) {
3359
+ const copied = stageCopySources.get(stage.qualifiedName);
3360
+ if (!copied?.length) continue;
3361
+ for (const path13 of copied) {
3362
+ const fileNodeId = fileNodes.get(toPosix3(path13));
3363
+ if (!fileNodeId) continue;
3364
+ add3("depends_on", stage.id, fileNodeId, LINK_CONFIDENCE.dockerfileCopy, "dockerfile\u2192source");
3365
+ }
3366
+ }
3367
+ }
3368
+ return {
3369
+ edges: [...out.values()].sort(
3370
+ (a, b) => a.kind.localeCompare(b.kind, "en") || a.src.localeCompare(b.src, "en") || a.dst.localeCompare(b.dst, "en")
3371
+ ),
3372
+ counts
3373
+ };
3374
+ }
3375
+ function push(map, key, value) {
3376
+ const list = map.get(key);
3377
+ if (list) list.push(value);
3378
+ else map.set(key, [value]);
3379
+ }
3380
+ function dirOf(rel2) {
3381
+ const idx = rel2.lastIndexOf("/");
3382
+ return idx === -1 ? "." : rel2.slice(0, idx);
3383
+ }
3384
+
3385
+ // src/engine/toolchain/index.ts
3386
+ var EXTRACTORS = [
3387
+ terraformExtractor,
3388
+ dockerfileExtractor,
3389
+ githubActionsExtractor,
3390
+ gitlabCiExtractor,
3391
+ helmExtractor,
3392
+ composeExtractor,
3393
+ kubernetesExtractor
3394
+ ];
3395
+ var HEAD_BYTES = 4096;
3396
+ var EMPTY_RESULT = {
3397
+ nodes: [],
3398
+ edges: [],
3399
+ warnings: [],
3400
+ counts: {},
3401
+ linkCounts: {}
3402
+ };
3403
+ function extractorFor(doc, head) {
3404
+ for (const extractor of EXTRACTORS) {
3405
+ try {
3406
+ if (extractor.matches(toPosix3(doc.rel), doc.category ?? doc.kind, head)) return extractor;
3407
+ } catch {
3408
+ }
3409
+ }
3410
+ return null;
3411
+ }
3412
+ async function extractToolchain(docs, options = {}) {
3413
+ if (!docs.length) return EMPTY_RESULT;
3414
+ const nodes = /* @__PURE__ */ new Map();
3415
+ const edges = /* @__PURE__ */ new Map();
3416
+ const warnings = [];
3417
+ const counts = {};
3418
+ const stageCopySources = /* @__PURE__ */ new Map();
3419
+ const ordered = [...docs].sort((a, b) => toPosix3(a.rel).localeCompare(toPosix3(b.rel), "en"));
3420
+ for (const doc of ordered) {
3421
+ let source;
3422
+ let head;
3423
+ try {
3424
+ const stat = fs13.statSync(doc.abs);
3425
+ if (stat.size > TOOLCHAIN_FILE_MAX_BYTES) continue;
3426
+ source = fs13.readFileSync(doc.abs, "utf8");
3427
+ head = source.slice(0, HEAD_BYTES);
3428
+ } catch {
3429
+ continue;
3430
+ }
3431
+ const extractor = extractorFor(doc, head);
3432
+ if (!extractor) continue;
3433
+ const rel2 = toPosix3(doc.rel);
3434
+ let extraction;
3435
+ try {
3436
+ extraction = await extractor.extract(rel2, source);
3437
+ } catch (err) {
3438
+ warnings.push(`${rel2}: ${extractor.format} extraction failed (${err.message})`);
3439
+ continue;
3440
+ }
3441
+ if (extraction.warnings?.length) warnings.push(...extraction.warnings);
3442
+ for (const [stage, paths] of Object.entries(extraction.linkHints?.copySourcesByStage ?? {})) {
3443
+ if (paths.length) stageCopySources.set(stage, paths);
3444
+ }
3445
+ if (!extraction.nodes.length) continue;
3446
+ counts[extractor.format] = (counts[extractor.format] ?? 0) + 1;
3447
+ const idByQualifiedName = /* @__PURE__ */ new Map();
3448
+ for (const draft of extraction.nodes) {
3449
+ const id = nodeId({
3450
+ kind: draft.kind,
3451
+ qualifiedName: draft.qualifiedName,
3452
+ file: rel2,
3453
+ signature: draft.signature
3454
+ });
3455
+ if (!idByQualifiedName.has(draft.qualifiedName)) {
3456
+ idByQualifiedName.set(draft.qualifiedName, id);
3457
+ }
3458
+ if (nodes.has(id)) continue;
3459
+ nodes.set(id, toGraphNode(id, draft, rel2, extractor.format));
3460
+ }
3461
+ for (const draft of extraction.edges) {
3462
+ const src = idByQualifiedName.get(draft.from);
3463
+ const dst = idByQualifiedName.get(draft.to);
3464
+ if (!src || !dst || src === dst) continue;
3465
+ const id = edgeId(draft.kind, src, dst);
3466
+ if (edges.has(id)) continue;
3467
+ edges.set(id, {
3468
+ id,
3469
+ kind: draft.kind,
3470
+ src,
3471
+ dst,
3472
+ // Structural extraction from a declaration in the file itself.
3473
+ resolution: "heuristic",
3474
+ confidence: draft.confidence ?? 1,
3475
+ epistemic: "declared"
3476
+ });
3477
+ }
3478
+ }
3479
+ const sortedNodes = [...nodes.values()].sort((a, b) => a.id.localeCompare(b.id, "en"));
3480
+ const sortedEdges = [...edges.values()].sort(byEdgeOrder);
3481
+ const linked = linkToolchain({
3482
+ nodes: sortedNodes,
3483
+ edges: sortedEdges,
3484
+ fileNodes: options.fileNodes ?? /* @__PURE__ */ new Map(),
3485
+ stageCopySources
3486
+ });
3487
+ const declaredIds = new Set(sortedEdges.map((e) => e.id));
3488
+ const allEdges = [...sortedEdges, ...linked.edges.filter((e) => !declaredIds.has(e.id))].sort(
3489
+ byEdgeOrder
3490
+ );
3491
+ return {
3492
+ nodes: sortedNodes,
3493
+ edges: allEdges,
3494
+ warnings: [...new Set(warnings)].sort(),
3495
+ counts,
3496
+ linkCounts: linked.counts
3497
+ };
3498
+ }
3499
+ function byEdgeOrder(a, b) {
3500
+ return a.kind.localeCompare(b.kind, "en") || a.src.localeCompare(b.src, "en") || a.dst.localeCompare(b.dst, "en");
3501
+ }
3502
+ function toGraphNode(id, draft, rel2, format) {
3503
+ return {
3504
+ id,
3505
+ kind: draft.kind,
3506
+ name: draft.name,
3507
+ qualifiedName: draft.qualifiedName,
3508
+ file: rel2,
3509
+ span: draft.span,
3510
+ // `lang` is the format tag, so `--only` and the report can filter on it
3511
+ // without colliding with a source language id.
3512
+ lang: format,
3513
+ signature: draft.signature,
3514
+ doc: safeDoc(draft.doc),
3515
+ importance: draft.importance ?? 0.3,
3516
+ centrality: { degree: 0, pagerank: 0, betweenness: 0, eigenvector: 0 },
3517
+ area: -1,
3518
+ isHub: false,
3519
+ // Infrastructure has no notion of unit-test coverage; `null` is the
3520
+ // schema's "not analyzable", which is the honest answer.
3521
+ tested: null
3522
+ };
3523
+ }
1829
3524
  function shardTsProjects(root, files) {
1830
3525
  if (files.length === 0) return [];
1831
3526
  const cache = /* @__PURE__ */ new Map();
1832
3527
  const byKey = /* @__PURE__ */ new Map();
1833
3528
  for (const f of files) {
1834
- const dir = path11.dirname(f.abs);
3529
+ const dir = path12.dirname(f.abs);
1835
3530
  const key = nearestProjectKey(root, dir, cache);
1836
3531
  const list = byKey.get(key);
1837
3532
  if (list) list.push(f);
@@ -1846,22 +3541,22 @@ function nearestProjectKey(root, startDir, cache) {
1846
3541
  const hit = cache.get(startDir);
1847
3542
  if (hit !== void 0) return hit;
1848
3543
  let dir = startDir;
1849
- const rootAbs = path11.resolve(root);
3544
+ const rootAbs = path12.resolve(root);
1850
3545
  while (true) {
1851
- if (fs11.existsSync(path11.join(dir, "tsconfig.json")) || fs11.existsSync(path11.join(dir, "tsconfig.base.json")) || fs11.existsSync(path11.join(dir, "package.json"))) {
1852
- const key = toPosix3(path11.relative(rootAbs, dir)) || ".";
3546
+ if (fs13.existsSync(path12.join(dir, "tsconfig.json")) || fs13.existsSync(path12.join(dir, "tsconfig.base.json")) || fs13.existsSync(path12.join(dir, "package.json"))) {
3547
+ const key = toPosix4(path12.relative(rootAbs, dir)) || ".";
1853
3548
  cache.set(startDir, key);
1854
3549
  return key;
1855
3550
  }
1856
- if (dir === rootAbs || path11.dirname(dir) === dir) {
3551
+ if (dir === rootAbs || path12.dirname(dir) === dir) {
1857
3552
  cache.set(startDir, ".");
1858
3553
  return ".";
1859
3554
  }
1860
- dir = path11.dirname(dir);
3555
+ dir = path12.dirname(dir);
1861
3556
  }
1862
3557
  }
1863
- function toPosix3(p) {
1864
- return p.split(path11.sep).join("/");
3558
+ function toPosix4(p) {
3559
+ return p.split(path12.sep).join("/");
1865
3560
  }
1866
3561
 
1867
3562
  // src/engine/timing.ts
@@ -1953,9 +3648,9 @@ var emptyNode = (partial) => ({
1953
3648
  tested: null
1954
3649
  });
1955
3650
  function extractManifests(root, opts = {}) {
1956
- const absRoot = path11.resolve(root);
3651
+ const absRoot = path12.resolve(root);
1957
3652
  const ig = buildRootIgnore3(absRoot, opts.exclude ?? []);
1958
- const scopes = (opts.paths?.length ? opts.paths : ["."]).map((p) => path11.resolve(absRoot, p)).filter((p) => fs11.existsSync(p));
3653
+ const scopes = (opts.paths?.length ? opts.paths : ["."]).map((p) => path12.resolve(absRoot, p)).filter((p) => fs13.existsSync(p));
1959
3654
  const found = /* @__PURE__ */ new Map();
1960
3655
  for (const scope of scopes) {
1961
3656
  walkManifests(absRoot, scope, ig, found);
@@ -1965,7 +3660,7 @@ function extractManifests(root, opts = {}) {
1965
3660
  let deps = 0;
1966
3661
  for (const rel2 of [...found.keys()].sort()) {
1967
3662
  const abs = found.get(rel2);
1968
- const base = path11.posix.basename(rel2);
3663
+ const base = path12.posix.basename(rel2);
1969
3664
  try {
1970
3665
  if (base === "package.json") {
1971
3666
  deps += ingestPackageJson(rel2, abs, nodes, edges);
@@ -1985,8 +3680,8 @@ function extractManifests(root, opts = {}) {
1985
3680
  };
1986
3681
  }
1987
3682
  function ingestPackageJson(rel2, abs, nodes, edges) {
1988
- const raw = JSON.parse(fs11.readFileSync(abs, "utf8"));
1989
- const pkgName = raw.name && String(raw.name).trim() || path11.posix.dirname(rel2) || ".";
3683
+ const raw = JSON.parse(fs13.readFileSync(abs, "utf8"));
3684
+ const pkgName = raw.name && String(raw.name).trim() || path12.posix.dirname(rel2) || ".";
1990
3685
  const localId = nodeId({ kind: "package", qualifiedName: pkgName, file: rel2 });
1991
3686
  nodes.set(
1992
3687
  localId,
@@ -2005,7 +3700,7 @@ function ingestPackageJson(rel2, abs, nodes, edges) {
2005
3700
  emptyNode({
2006
3701
  id: fileId,
2007
3702
  kind: "file",
2008
- name: path11.posix.basename(rel2),
3703
+ name: path12.posix.basename(rel2),
2009
3704
  qualifiedName: rel2,
2010
3705
  file: rel2,
2011
3706
  lang: "json"
@@ -2029,8 +3724,8 @@ function ingestPackageJson(rel2, abs, nodes, edges) {
2029
3724
  return n;
2030
3725
  }
2031
3726
  function ingestGoMod(rel2, abs, nodes, edges) {
2032
- const text = fs11.readFileSync(abs, "utf8");
2033
- let moduleName = path11.posix.dirname(rel2) || ".";
3727
+ const text = fs13.readFileSync(abs, "utf8");
3728
+ let moduleName = path12.posix.dirname(rel2) || ".";
2034
3729
  const moduleMatch = /^\s*module\s+(\S+)/m.exec(text);
2035
3730
  if (moduleMatch) moduleName = moduleMatch[1];
2036
3731
  const localId = nodeId({ kind: "package", qualifiedName: moduleName, file: rel2 });
@@ -2051,7 +3746,7 @@ function ingestGoMod(rel2, abs, nodes, edges) {
2051
3746
  emptyNode({
2052
3747
  id: fileId,
2053
3748
  kind: "file",
2054
- name: path11.posix.basename(rel2),
3749
+ name: path12.posix.basename(rel2),
2055
3750
  qualifiedName: rel2,
2056
3751
  file: rel2,
2057
3752
  lang: "go"
@@ -2112,10 +3807,10 @@ function addEdge(edges, kind, src, dst, confidence) {
2112
3807
  }
2113
3808
  function buildRootIgnore3(root, exclude) {
2114
3809
  const ig = ignore();
2115
- const gitignorePath = path11.join(root, ".gitignore");
2116
- if (fs11.existsSync(gitignorePath)) {
3810
+ const gitignorePath = path12.join(root, ".gitignore");
3811
+ if (fs13.existsSync(gitignorePath)) {
2117
3812
  try {
2118
- ig.add(fs11.readFileSync(gitignorePath, "utf8"));
3813
+ ig.add(fs13.readFileSync(gitignorePath, "utf8"));
2119
3814
  } catch {
2120
3815
  }
2121
3816
  }
@@ -2125,13 +3820,13 @@ function buildRootIgnore3(root, exclude) {
2125
3820
  function walkManifests(root, dir, ig, found) {
2126
3821
  let entries;
2127
3822
  try {
2128
- entries = fs11.readdirSync(dir, { withFileTypes: true });
3823
+ entries = fs13.readdirSync(dir, { withFileTypes: true });
2129
3824
  } catch {
2130
3825
  return;
2131
3826
  }
2132
3827
  for (const entry of entries) {
2133
- const abs = path11.join(dir, entry.name);
2134
- const rel2 = path11.relative(root, abs).split(path11.sep).join("/");
3828
+ const abs = path12.join(dir, entry.name);
3829
+ const rel2 = path12.relative(root, abs).split(path12.sep).join("/");
2135
3830
  if (entry.isDirectory()) {
2136
3831
  if (isSkippedDirName(entry.name)) continue;
2137
3832
  if (rel2 && ig.ignores(`${rel2}/`)) continue;
@@ -2158,7 +3853,7 @@ async function hashFilesParallel(jobs, concurrency) {
2158
3853
  if (i >= jobs.length) return;
2159
3854
  const job = jobs[i];
2160
3855
  try {
2161
- const buf = await fs8.readFile(job.abs);
3856
+ const buf = await fs10.readFile(job.abs);
2162
3857
  out[i] = { rel: job.rel, hash: hashBytes(buf), ok: true };
2163
3858
  } catch {
2164
3859
  out[i] = { rel: job.rel, hash: "", ok: false };
@@ -2173,7 +3868,7 @@ async function hashFilesParallel(jobs, concurrency) {
2173
3868
  async function buildGraph(options) {
2174
3869
  const timer = new StageTimer();
2175
3870
  timer.start("total");
2176
- const root = path11.resolve(options.root);
3871
+ const root = path12.resolve(options.root);
2177
3872
  timer.start("discover");
2178
3873
  const files = discover({
2179
3874
  root,
@@ -2189,7 +3884,7 @@ async function buildGraph(options) {
2189
3884
  );
2190
3885
  }
2191
3886
  checkMemoryBudget("discovery", limits.memoryBudgetMb);
2192
- const grammars = grammarSetVersion();
3887
+ const grammars = `${grammarSetVersion()}+${toolchainGrammarSetVersion()}`;
2193
3888
  const cache = loadCache(root, {
2194
3889
  toolVersion: VERSION,
2195
3890
  grammars,
@@ -2206,7 +3901,7 @@ async function buildGraph(options) {
2206
3901
  for (const file of files) {
2207
3902
  let stat;
2208
3903
  try {
2209
- stat = fs11.statSync(file.abs);
3904
+ stat = fs13.statSync(file.abs);
2210
3905
  } catch {
2211
3906
  continue;
2212
3907
  }
@@ -2412,8 +4107,8 @@ async function buildGraph(options) {
2412
4107
  });
2413
4108
  for (const d of docs) {
2414
4109
  try {
2415
- const st = fs11.statSync(d.abs);
2416
- const hash = hashBytes(fs11.readFileSync(d.abs));
4110
+ const st = fs13.statSync(d.abs);
4111
+ const hash = hashBytes(fs13.readFileSync(d.abs));
2417
4112
  hashes.set(d.rel, hash);
2418
4113
  fileStats.push({ rel: d.rel, size: st.size, mtimeMs: st.mtimeMs, hash });
2419
4114
  } catch {
@@ -2421,14 +4116,29 @@ async function buildGraph(options) {
2421
4116
  }
2422
4117
  const docNodes = documentNodesFromDocs(docs);
2423
4118
  if (docNodes.length) nodes = [...nodes, ...docNodes];
4119
+ timer.start("toolchain");
4120
+ const fileNodesByPath = /* @__PURE__ */ new Map();
4121
+ for (const node of nodes) {
4122
+ if (node.kind === "file") fileNodesByPath.set(node.file.replace(/\\/g, "/"), node.id);
4123
+ }
4124
+ const toolchainResult = await extractToolchain(docs, { fileNodes: fileNodesByPath });
4125
+ if (toolchainResult.nodes.length) nodes = [...nodes, ...toolchainResult.nodes];
4126
+ timer.end("toolchain");
2424
4127
  timer.start("analyze");
2425
- const analysis = analyze(nodes, linked.edges, {
4128
+ const graphEdges = toolchainResult.edges.length ? [...linked.edges, ...toolchainResult.edges] : linked.edges;
4129
+ const analysis = analyze(nodes, graphEdges, {
2426
4130
  cluster: options.cluster,
2427
4131
  tier: options.analysisTier
2428
4132
  });
2429
4133
  timer.end("analyze");
2430
4134
  checkMemoryBudget("analysis", limits.memoryBudgetMb);
2431
- const languages = [.../* @__PURE__ */ new Set([...parses.map((p) => p.lang), ...docNodes.map((n) => n.lang)])].sort();
4135
+ const languages = [
4136
+ .../* @__PURE__ */ new Set([
4137
+ ...parses.map((p) => p.lang),
4138
+ ...docNodes.map((n) => n.lang),
4139
+ ...toolchainResult.nodes.map((n) => n.lang)
4140
+ ])
4141
+ ].sort();
2432
4142
  const edgeKinds = [...new Set(analysis.edges.map((e) => e.kind))].sort();
2433
4143
  const corpusHash = computeCorpusHash(parses, hashes);
2434
4144
  const nodeKindById = new Map(analysis.nodes.map((n) => [n.id, n.kind]));
@@ -2454,7 +4164,7 @@ async function buildGraph(options) {
2454
4164
  toolchain
2455
4165
  },
2456
4166
  meta: {
2457
- root: path11.basename(root) === "" ? "." : ".",
4167
+ root: path12.basename(root) === "" ? "." : ".",
2458
4168
  languages,
2459
4169
  analysisTier: analysis.tier,
2460
4170
  counts: {
@@ -2513,14 +4223,14 @@ function toRepoRel(p) {
2513
4223
  function loadScipIndex(root, explicit) {
2514
4224
  const candidates = [
2515
4225
  explicit,
2516
- path11.join(root, "index.scip"),
2517
- path11.join(root, ".vibgrate", "index.scip")
4226
+ path12.join(root, "index.scip"),
4227
+ path12.join(root, ".vibgrate", "index.scip")
2518
4228
  ].filter((p) => Boolean(p));
2519
4229
  for (const file of candidates) {
2520
- const abs = path11.isAbsolute(file) ? file : path11.resolve(root, file);
2521
- if (!fs11.existsSync(abs)) continue;
4230
+ const abs = path12.isAbsolute(file) ? file : path12.resolve(root, file);
4231
+ if (!fs13.existsSync(abs)) continue;
2522
4232
  try {
2523
- const index = decodeScipIndex(new Uint8Array(fs11.readFileSync(abs)));
4233
+ const index = decodeScipIndex(new Uint8Array(fs13.readFileSync(abs)));
2524
4234
  if (index.documents.length) {
2525
4235
  return { index, tool: index.toolVersion ? `${index.toolName} ${index.toolVersion}` : index.toolName };
2526
4236
  }
@@ -2612,53 +4322,23 @@ function diffHint(x, y) {
2612
4322
  }
2613
4323
  return "outputs differ in length";
2614
4324
  }
2615
-
2616
- // src/util/exit.ts
2617
- var ExitCode = {
2618
- OK: 0,
2619
- ERROR: 1,
2620
- GATE_FAILED: 2,
2621
- NOT_FOUND: 3,
2622
- NON_DETERMINISTIC: 4,
2623
- USAGE_ERROR: 5,
2624
- /**
2625
- * A required optional module (engine) is not installed and could not be
2626
- * fetched. Deliberately distinct from GATE_FAILED so a CI gate can never
2627
- * read "engine missing" as "gate passed" or "gate failed".
2628
- */
2629
- ENGINE_UNAVAILABLE: 6
2630
- };
2631
- var CliError = class extends Error {
2632
- code;
2633
- constructor(message, code = ExitCode.ERROR) {
2634
- super(message);
2635
- this.name = "CliError";
2636
- this.code = code;
2637
- }
2638
- };
2639
- function usageError(message) {
2640
- return new CliError(message, ExitCode.USAGE_ERROR);
2641
- }
2642
- function engineUnavailable(message) {
2643
- return new CliError(message, ExitCode.ENGINE_UNAVAILABLE);
2644
- }
2645
4325
  var SNAPSHOT_VERSION = "vg-freshness/1";
2646
4326
  function snapshotPath(root) {
2647
- return path11.join(cacheDir(root), "freshness.json");
4327
+ return path12.join(cacheDir(root), "freshness.json");
2648
4328
  }
2649
4329
  function writeSnapshot(root, corpusHash, fileStats, scope = {}) {
2650
4330
  const files = {};
2651
4331
  for (const f of fileStats) files[f.rel] = { size: f.size, mtimeMs: f.mtimeMs, hash: f.hash };
2652
4332
  const snapshot = { version: SNAPSHOT_VERSION, corpusHash, scope: pruneScope(scope), files };
2653
4333
  try {
2654
- fs11.mkdirSync(cacheDir(root), { recursive: true });
2655
- fs11.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
4334
+ fs13.mkdirSync(cacheDir(root), { recursive: true });
4335
+ fs13.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
2656
4336
  } catch {
2657
4337
  }
2658
4338
  }
2659
4339
  function loadSnapshot(root) {
2660
4340
  try {
2661
- const loaded = JSON.parse(fs11.readFileSync(snapshotPath(root), "utf8"));
4341
+ const loaded = JSON.parse(fs13.readFileSync(snapshotPath(root), "utf8"));
2662
4342
  if (loaded.version === SNAPSHOT_VERSION && loaded.files && typeof loaded.corpusHash === "string") {
2663
4343
  return { ...loaded, scope: loaded.scope ?? {} };
2664
4344
  }
@@ -2701,7 +4381,7 @@ function probeFreshness(root) {
2701
4381
  }
2702
4382
  let stat;
2703
4383
  try {
2704
- stat = fs11.statSync(file.abs);
4384
+ stat = fs13.statSync(file.abs);
2705
4385
  } catch {
2706
4386
  drift.removed.push(file.rel);
2707
4387
  continue;
@@ -2719,7 +4399,7 @@ function probeFreshness(root) {
2719
4399
  }
2720
4400
  let hash = "";
2721
4401
  try {
2722
- hash = hashBytes(fs11.readFileSync(file.abs));
4402
+ hash = hashBytes(fs13.readFileSync(file.abs));
2723
4403
  } catch {
2724
4404
  drift.removed.push(file.rel);
2725
4405
  continue;
@@ -2736,7 +4416,7 @@ function probeFreshness(root) {
2736
4416
  }
2737
4417
  if (absorbed && !hasDrift(drift)) {
2738
4418
  try {
2739
- fs11.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
4419
+ fs13.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
2740
4420
  } catch {
2741
4421
  }
2742
4422
  }
@@ -2981,12 +4661,12 @@ function detectAssistants(root, opts = {}) {
2981
4661
  const which = opts.which ?? whichOnPath;
2982
4662
  const found = [];
2983
4663
  for (const assistant of ASSISTANTS) {
2984
- const repoHit = (assistant.markers ?? []).find((m) => fs11.existsSync(path11.join(root, m)));
4664
+ const repoHit = (assistant.markers ?? []).find((m) => fs13.existsSync(path12.join(root, m)));
2985
4665
  if (repoHit) {
2986
4666
  found.push({ assistant, via: "repo", marker: repoHit });
2987
4667
  continue;
2988
4668
  }
2989
- const homeHit = (assistant.homeMarkers ?? []).find((m) => fs11.existsSync(path11.join(home, m)));
4669
+ const homeHit = (assistant.homeMarkers ?? []).find((m) => fs13.existsSync(path12.join(home, m)));
2990
4670
  if (homeHit) {
2991
4671
  found.push({ assistant, via: "home", marker: homeHit });
2992
4672
  continue;
@@ -3018,19 +4698,19 @@ function installAssistant(a, opts) {
3018
4698
  const skipped = [];
3019
4699
  let note;
3020
4700
  if (a.skill) {
3021
- writeFileEnsured(path11.join(opts.root, a.skill), skillMarkdown(a.id));
4701
+ writeFileEnsured(path12.join(opts.root, a.skill), skillMarkdown(a.id));
3022
4702
  wrote.push(a.skill);
3023
4703
  }
3024
4704
  if (a.mcp) {
3025
4705
  const launch = opts.launch ?? detectServeLaunch();
3026
- upsertMcp(path11.join(opts.root, a.mcp.file), a.mcp, launch);
4706
+ upsertMcp(path12.join(opts.root, a.mcp.file), a.mcp, launch);
3027
4707
  wrote.push(a.mcp.file);
3028
4708
  note = launch.note;
3029
4709
  } else {
3030
4710
  skipped.push("mcp (host-specific setup)");
3031
4711
  }
3032
4712
  if (a.nudge && opts.hook !== false) {
3033
- writeNudge(path11.join(opts.root, a.nudge.file), a.nudge, opts.smallRepo, a.id);
4713
+ writeNudge(path12.join(opts.root, a.nudge.file), a.nudge, opts.smallRepo, a.id);
3034
4714
  wrote.push(a.nudge.file);
3035
4715
  } else if (a.nudge) {
3036
4716
  skipped.push("nudge (--no-hook)");
@@ -3040,34 +4720,34 @@ function installAssistant(a, opts) {
3040
4720
  function uninstallAssistant(a, root, purge) {
3041
4721
  const removed = [];
3042
4722
  if (a.mcp) {
3043
- const file = path11.join(root, a.mcp.file);
4723
+ const file = path12.join(root, a.mcp.file);
3044
4724
  if (removeMcp(file, a.mcp)) removed.push(a.mcp.file);
3045
4725
  }
3046
4726
  if (a.nudge) {
3047
- const file = path11.join(root, a.nudge.file);
4727
+ const file = path12.join(root, a.nudge.file);
3048
4728
  if (a.nudge.kind === "block") {
3049
4729
  if (removeBlock(file)) removed.push(a.nudge.file);
3050
- } else if (fs11.existsSync(file)) {
3051
- fs11.rmSync(file);
4730
+ } else if (fs13.existsSync(file)) {
4731
+ fs13.rmSync(file);
3052
4732
  removed.push(a.nudge.file);
3053
4733
  }
3054
4734
  }
3055
4735
  if (purge && a.skill) {
3056
- const file = path11.join(root, a.skill);
3057
- if (fs11.existsSync(file)) {
3058
- fs11.rmSync(file);
4736
+ const file = path12.join(root, a.skill);
4737
+ if (fs13.existsSync(file)) {
4738
+ fs13.rmSync(file);
3059
4739
  removed.push(a.skill);
3060
4740
  }
3061
4741
  }
3062
4742
  return removed;
3063
4743
  }
3064
4744
  function isAssistantInstalled(a, root) {
3065
- if (a.skill && fs11.existsSync(path11.join(root, a.skill))) return true;
4745
+ if (a.skill && fs13.existsSync(path12.join(root, a.skill))) return true;
3066
4746
  if (a.mcp) {
3067
- const file = path11.join(root, a.mcp.file);
4747
+ const file = path12.join(root, a.mcp.file);
3068
4748
  if (a.mcp.format === "toml") {
3069
4749
  if (hasTomlMcp(file, a.mcp)) return true;
3070
- } else if (fs11.existsSync(file)) {
4750
+ } else if (fs13.existsSync(file)) {
3071
4751
  try {
3072
4752
  const config = readJson(file);
3073
4753
  const bag = config[a.mcp.key];
@@ -3077,8 +4757,8 @@ function isAssistantInstalled(a, root) {
3077
4757
  }
3078
4758
  }
3079
4759
  if (a.nudge) {
3080
- const file = path11.join(root, a.nudge.file);
3081
- if (!fs11.existsSync(file)) return false;
4760
+ const file = path12.join(root, a.nudge.file);
4761
+ if (!fs13.existsSync(file)) return false;
3082
4762
  const text = readTextSafe(file);
3083
4763
  if (!text) return false;
3084
4764
  if (a.nudge.kind === "block") return text.includes(NUDGE_BEGIN);
@@ -3087,19 +4767,19 @@ function isAssistantInstalled(a, root) {
3087
4767
  return false;
3088
4768
  }
3089
4769
  function writeFileEnsured(file, content) {
3090
- fs11.mkdirSync(path11.dirname(file), { recursive: true });
3091
- fs11.writeFileSync(file, content);
4770
+ fs13.mkdirSync(path12.dirname(file), { recursive: true });
4771
+ fs13.writeFileSync(file, content);
3092
4772
  }
3093
4773
  function writeNavigationConfig(root) {
3094
4774
  ensureVibgrateGitignore(root);
3095
- const rel2 = path11.join(".vibgrate", "mcp-navigation.json");
4775
+ const rel2 = path12.join(".vibgrate", "mcp-navigation.json");
3096
4776
  const doc = {
3097
4777
  _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.",
3098
4778
  hot_core: [...HOT_TOOLS],
3099
4779
  deferred: deferredToolNames(),
3100
4780
  toolset: navigationToolsetConfig()
3101
4781
  };
3102
- writeFileEnsured(path11.join(root, rel2), `${JSON.stringify(doc, null, 2)}
4782
+ writeFileEnsured(path12.join(root, rel2), `${JSON.stringify(doc, null, 2)}
3103
4783
  `);
3104
4784
  return rel2;
3105
4785
  }
@@ -3129,7 +4809,7 @@ function upsertTomlMcp(file, target, launch) {
3129
4809
  command = ${tomlString(entry.command)}
3130
4810
  args = [${entry.args.map(tomlString).join(", ")}]
3131
4811
  `;
3132
- const existing = fs11.existsSync(file) ? fs11.readFileSync(file, "utf8") : "";
4812
+ const existing = fs13.existsSync(file) ? fs13.readFileSync(file, "utf8") : "";
3133
4813
  const re = tomlTableRe(target.key);
3134
4814
  const next = re.test(existing) ? existing.replace(re, (m) => m.startsWith("\n") ? `
3135
4815
  ${table}` : table) : existing.length ? `${existing.replace(/\s*$/, "")}
@@ -3138,7 +4818,7 @@ ${table}` : table;
3138
4818
  writeFileEnsured(file, next);
3139
4819
  }
3140
4820
  function removeTomlMcp(file, target) {
3141
- if (!fs11.existsSync(file)) return false;
4821
+ if (!fs13.existsSync(file)) return false;
3142
4822
  const existing = readTextSafe(file);
3143
4823
  if (existing === null) return false;
3144
4824
  const re = tomlTableRe(target.key);
@@ -3148,13 +4828,13 @@ function removeTomlMcp(file, target) {
3148
4828
  return true;
3149
4829
  }
3150
4830
  function hasTomlMcp(file, target) {
3151
- if (!fs11.existsSync(file)) return false;
4831
+ if (!fs13.existsSync(file)) return false;
3152
4832
  const text = readTextSafe(file);
3153
4833
  return text !== null && tomlTableRe(target.key).test(text);
3154
4834
  }
3155
4835
  function removeMcp(file, target) {
3156
4836
  if (target.format === "toml") return removeTomlMcp(file, target);
3157
- if (!fs11.existsSync(file)) return false;
4837
+ if (!fs13.existsSync(file)) return false;
3158
4838
  const config = readJson(file);
3159
4839
  const bag = config[target.key];
3160
4840
  if (!bag || !(bag.vg !== void 0)) return false;
@@ -3173,8 +4853,8 @@ function refreshInstalledInstructions(root, smallRepo) {
3173
4853
  };
3174
4854
  for (const a of ASSISTANTS) {
3175
4855
  if (a.skill) {
3176
- const file = path11.join(root, a.skill);
3177
- if (fs11.existsSync(file)) {
4856
+ const file = path12.join(root, a.skill);
4857
+ if (fs13.existsSync(file)) {
3178
4858
  const text = readTextSafe(file);
3179
4859
  const from = text === null ? null : staleVersion(text, "# vg \u2014 the code map");
3180
4860
  if (from !== null) {
@@ -3184,8 +4864,8 @@ function refreshInstalledInstructions(root, smallRepo) {
3184
4864
  }
3185
4865
  }
3186
4866
  if (a.nudge) {
3187
- const file = path11.join(root, a.nudge.file);
3188
- if (!fs11.existsSync(file)) continue;
4867
+ const file = path12.join(root, a.nudge.file);
4868
+ if (!fs13.existsSync(file)) continue;
3189
4869
  const text = readTextSafe(file);
3190
4870
  if (text === null) continue;
3191
4871
  if (a.nudge.kind === "block") {
@@ -3209,7 +4889,7 @@ function refreshInstalledInstructions(root, smallRepo) {
3209
4889
  }
3210
4890
  function readTextSafe(file) {
3211
4891
  try {
3212
- return fs11.readFileSync(file, "utf8");
4892
+ return fs13.readFileSync(file, "utf8");
3213
4893
  } catch {
3214
4894
  return null;
3215
4895
  }
@@ -3228,8 +4908,8 @@ ${stripMarkers(nudgeMarkdown(smallRepo, client))}` : stripMarkers(nudgeMarkdown(
3228
4908
  upsertBlock(file, nudgeMarkdown(smallRepo, client));
3229
4909
  }
3230
4910
  function upsertBlock(file, block) {
3231
- fs11.mkdirSync(path11.dirname(file), { recursive: true });
3232
- let existing = fs11.existsSync(file) ? fs11.readFileSync(file, "utf8") : "";
4911
+ fs13.mkdirSync(path12.dirname(file), { recursive: true });
4912
+ let existing = fs13.existsSync(file) ? fs13.readFileSync(file, "utf8") : "";
3233
4913
  const re = new RegExp(`${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}`);
3234
4914
  if (re.test(existing)) existing = existing.replace(re, block);
3235
4915
  else existing = existing.length ? `${existing.replace(/\s*$/, "")}
@@ -3237,20 +4917,20 @@ function upsertBlock(file, block) {
3237
4917
  ${block}
3238
4918
  ` : `${block}
3239
4919
  `;
3240
- fs11.writeFileSync(file, existing);
4920
+ fs13.writeFileSync(file, existing);
3241
4921
  }
3242
4922
  function removeBlock(file) {
3243
- if (!fs11.existsSync(file)) return false;
3244
- const existing = fs11.readFileSync(file, "utf8");
4923
+ if (!fs13.existsSync(file)) return false;
4924
+ const existing = fs13.readFileSync(file, "utf8");
3245
4925
  const re = new RegExp(`\\n*${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}\\n*`);
3246
4926
  if (!re.test(existing)) return false;
3247
- fs11.writeFileSync(file, existing.replace(re, "\n"));
4927
+ fs13.writeFileSync(file, existing.replace(re, "\n"));
3248
4928
  return true;
3249
4929
  }
3250
4930
  function readJson(file) {
3251
- if (!fs11.existsSync(file)) return {};
4931
+ if (!fs13.existsSync(file)) return {};
3252
4932
  try {
3253
- const parsed = JSON.parse(fs11.readFileSync(file, "utf8"));
4933
+ const parsed = JSON.parse(fs13.readFileSync(file, "utf8"));
3254
4934
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
3255
4935
  return parsed;
3256
4936
  }
@@ -3269,6 +4949,6 @@ function escapeRe(s) {
3269
4949
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3270
4950
  }
3271
4951
 
3272
- export { ASSISTANTS, CliError, ExitCode, ResourceLimitError, SKIP_DIRS, SKIP_FILES, SMALL_REPO_FILES, UsageError, analyze, applyCoverage, assistantById, buildFacts, buildGraph, decodeScipIndex, detectAssistants, detectServeLaunch, discover, driftCount, engineUnavailable, epistemicBreakdown, groundGraph, hasDrift, installAssistant, isAssistantInstalled, isSkippedDirName, loadCoverage, loadSnapshot, probeFreshness, refreshInstalledInstructions, resolveLimits, scipEdges, uninstallAssistant, usageError, verifyDeterminism, versionMarker, writeNavigationConfig, writeSnapshot };
3273
- //# sourceMappingURL=chunk-TNROG5CO.js.map
3274
- //# sourceMappingURL=chunk-TNROG5CO.js.map
4952
+ export { ASSISTANTS, ResourceLimitError, SKIP_DIRS, SKIP_FILES, SMALL_REPO_FILES, TOOLCHAIN_GRAMMARS, UsageError, analyze, applyCoverage, assistantById, buildFacts, buildGraph, decodeScipIndex, detectAssistants, detectServeLaunch, discover, driftCount, epistemicBreakdown, groundGraph, hasDrift, installAssistant, isAssistantInstalled, isSkippedDirName, loadCoverage, loadSnapshot, probeFreshness, refreshInstalledInstructions, resolveLimits, resolveToolchainGrammar, scipEdges, uninstallAssistant, verifyDeterminism, versionMarker, writeNavigationConfig, writeSnapshot };
4953
+ //# sourceMappingURL=chunk-RJCRWQNS.js.map
4954
+ //# sourceMappingURL=chunk-RJCRWQNS.js.map