@vibgrate/cli 2026.814.2 → 2026.817.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/DOCS.md +61 -0
  2. package/README.md +1 -0
  3. package/dist/agent-UQC7QN77.js +27 -0
  4. package/dist/{agent-5ANHRYEI.js.map → agent-UQC7QN77.js.map} +1 -1
  5. package/dist/{approvals-T2KXPJLV.js → approvals-ASPJQUZK.js} +6 -5
  6. package/dist/{approvals-T2KXPJLV.js.map → approvals-ASPJQUZK.js.map} +1 -1
  7. package/dist/baseline-FIV6PR2M.js +9 -0
  8. package/dist/{baseline-WVD6P5IO.js.map → baseline-FIV6PR2M.js.map} +1 -1
  9. package/dist/{chunk-FOW5IF73.js → chunk-2FEB5KJS.js} +11 -34
  10. package/dist/chunk-2FEB5KJS.js.map +1 -0
  11. package/dist/{chunk-WQLZSVNF.js → chunk-3FMRFECC.js} +7 -6
  12. package/dist/chunk-3FMRFECC.js.map +1 -0
  13. package/dist/{chunk-7QDHQLUS.js → chunk-3H3SVJUY.js} +13 -11
  14. package/dist/chunk-3H3SVJUY.js.map +1 -0
  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-YQNTIBKS.js → chunk-3NJUOIZQ.js} +6 -6
  18. package/dist/{chunk-YQNTIBKS.js.map → chunk-3NJUOIZQ.js.map} +1 -1
  19. package/dist/{chunk-BH54SLGZ.js → chunk-5OL2TSHK.js} +4 -4
  20. package/dist/{chunk-BH54SLGZ.js.map → chunk-5OL2TSHK.js.map} +1 -1
  21. package/dist/{chunk-TFAXW7VM.js → chunk-6K3XK7C5.js} +69 -976
  22. package/dist/chunk-6K3XK7C5.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-X76VPOYH.js → chunk-AXZZ5CCG.js} +4 -4
  26. package/dist/{chunk-X76VPOYH.js.map → chunk-AXZZ5CCG.js.map} +1 -1
  27. package/dist/{chunk-5GLQNMTI.js → chunk-C3LMWZKF.js} +6 -6
  28. package/dist/{chunk-5GLQNMTI.js.map → chunk-C3LMWZKF.js.map} +1 -1
  29. package/dist/{chunk-WWGLVF7D.js → chunk-DVW3SVCW.js} +9 -8
  30. package/dist/chunk-DVW3SVCW.js.map +1 -0
  31. package/dist/{chunk-TNROG5CO.js → chunk-E2FAQREZ.js} +1739 -116
  32. package/dist/chunk-E2FAQREZ.js.map +1 -0
  33. package/dist/{chunk-7NMXE6KG.js → chunk-EXV4QSAD.js} +4 -4
  34. package/dist/{chunk-7NMXE6KG.js.map → chunk-EXV4QSAD.js.map} +1 -1
  35. package/dist/{chunk-3LSG7Z4I.js → chunk-EZ7UC5Y5.js} +25 -4
  36. package/dist/chunk-EZ7UC5Y5.js.map +1 -0
  37. package/dist/{chunk-OWSFZQOP.js → chunk-FJG5AXKZ.js} +3 -3
  38. package/dist/{chunk-OWSFZQOP.js.map → chunk-FJG5AXKZ.js.map} +1 -1
  39. package/dist/{chunk-NKFXXP6W.js → chunk-GOGMS3ST.js} +8 -6
  40. package/dist/chunk-GOGMS3ST.js.map +1 -0
  41. package/dist/{chunk-WHJMS7XC.js → chunk-I5LP55B6.js} +3 -3
  42. package/dist/{chunk-WHJMS7XC.js.map → chunk-I5LP55B6.js.map} +1 -1
  43. package/dist/{chunk-IHXNOY3R.js → chunk-K6DXETMC.js} +12 -12
  44. package/dist/{chunk-IHXNOY3R.js.map → chunk-K6DXETMC.js.map} +1 -1
  45. package/dist/{chunk-DSUGC7F5.js → chunk-KJT2R6WW.js} +4 -4
  46. package/dist/{chunk-DSUGC7F5.js.map → chunk-KJT2R6WW.js.map} +1 -1
  47. package/dist/chunk-KNGAZDGF.js +467 -0
  48. package/dist/chunk-KNGAZDGF.js.map +1 -0
  49. package/dist/{chunk-JCN6MSXQ.js → chunk-LHKQGVRF.js} +17 -29
  50. package/dist/chunk-LHKQGVRF.js.map +1 -0
  51. package/dist/{chunk-YNV5M3JW.js → chunk-M7IO3DXH.js} +9 -8
  52. package/dist/chunk-M7IO3DXH.js.map +1 -0
  53. package/dist/chunk-MS6V5XKB.js +33 -0
  54. package/dist/chunk-MS6V5XKB.js.map +1 -0
  55. package/dist/{chunk-2U37PFB2.js → chunk-MVPRPSXU.js} +7 -4
  56. package/dist/chunk-MVPRPSXU.js.map +1 -0
  57. package/dist/{chunk-DT4KIVRI.js → chunk-NG4KIJW6.js} +34 -2
  58. package/dist/chunk-NG4KIJW6.js.map +1 -0
  59. package/dist/{chunk-IHLLWBZU.js → chunk-O2DL2Q55.js} +6 -6
  60. package/dist/{chunk-IHLLWBZU.js.map → chunk-O2DL2Q55.js.map} +1 -1
  61. package/dist/chunk-O3LABX6I.js +43 -0
  62. package/dist/chunk-O3LABX6I.js.map +1 -0
  63. package/dist/chunk-OZ3IFXRD.js +22 -0
  64. package/dist/chunk-OZ3IFXRD.js.map +1 -0
  65. package/dist/chunk-PRG2ULPK.js +6 -0
  66. package/dist/{chunk-2H2U3DC7.js.map → chunk-PRG2ULPK.js.map} +1 -1
  67. package/dist/{chunk-TVDYEHIM.js → chunk-Q3M6K3HY.js} +3 -3
  68. package/dist/{chunk-TVDYEHIM.js.map → chunk-Q3M6K3HY.js.map} +1 -1
  69. package/dist/{chunk-KGPX4EUN.js → chunk-Q4G2BWE3.js} +4 -4
  70. package/dist/{chunk-KGPX4EUN.js.map → chunk-Q4G2BWE3.js.map} +1 -1
  71. package/dist/chunk-T7RSQ5WD.js +227 -0
  72. package/dist/chunk-T7RSQ5WD.js.map +1 -0
  73. package/dist/{chunk-54RGT2T5.js → chunk-TVDMNIUT.js} +3 -3
  74. package/dist/{chunk-54RGT2T5.js.map → chunk-TVDMNIUT.js.map} +1 -1
  75. package/dist/{chunk-N6ZMZ5AI.js → chunk-WAKP7SK7.js} +4922 -1011
  76. package/dist/chunk-WAKP7SK7.js.map +1 -0
  77. package/dist/{chunk-75LJM5XD.js → chunk-WVMOGIPC.js} +9 -43
  78. package/dist/chunk-WVMOGIPC.js.map +1 -0
  79. package/dist/{chunk-UTNNGKKX.js → chunk-XLXJC7H3.js} +142 -14
  80. package/dist/chunk-XLXJC7H3.js.map +1 -0
  81. package/dist/cli.js +1727 -1127
  82. package/dist/cli.js.map +1 -1
  83. package/dist/daemon-FPTRHO5N.js +26 -0
  84. package/dist/daemon-FPTRHO5N.js.map +1 -0
  85. package/dist/embed-worker-main.js +5 -4
  86. package/dist/embed-worker-main.js.map +1 -1
  87. package/dist/ensure-map-342G2IV3.js +33 -0
  88. package/dist/{ensure-map-HQ7A5NDL.js.map → ensure-map-342G2IV3.js.map} +1 -1
  89. package/dist/{fs-NTQUFCXV.js → fs-TLPOWC2D.js} +3 -3
  90. package/dist/fs-TLPOWC2D.js.map +1 -0
  91. package/dist/graph-backend-RNWQBT3P.js +15 -0
  92. package/dist/{graph-backend-W2TNTMY7.js.map → graph-backend-RNWQBT3P.js.map} +1 -1
  93. package/dist/index.d.ts +28 -6
  94. package/dist/index.js +21 -18
  95. package/dist/index.js.map +1 -1
  96. package/dist/{interactive-EW2VJ3BZ.js → interactive-SR7TMF6M.js} +36 -33
  97. package/dist/interactive-SR7TMF6M.js.map +1 -0
  98. package/dist/load-AYFECIQB.js +11 -0
  99. package/dist/{load-64RK5O6C.js.map → load-AYFECIQB.js.map} +1 -1
  100. package/dist/local-runtime-6SNWKSA7.js +6 -0
  101. package/dist/{local-runtime-2ED33KK6.js.map → local-runtime-6SNWKSA7.js.map} +1 -1
  102. package/dist/model-manager-SXHZ5VD4.js +10 -0
  103. package/dist/{model-manager-GJ2YSTSK.js.map → model-manager-SXHZ5VD4.js.map} +1 -1
  104. package/dist/parse-worker.d.ts +1 -1
  105. package/dist/parse-worker.js +5 -2
  106. package/dist/parse-worker.js.map +1 -1
  107. package/dist/{run-outcome-6QNQIHPV.js → run-outcome-YQQJG3LW.js} +7 -6
  108. package/dist/run-outcome-YQQJG3LW.js.map +1 -0
  109. package/dist/runtime-session-CNJ4DAV5.js +22 -0
  110. package/dist/{runtime-session-RJHH3KMF.js.map → runtime-session-CNJ4DAV5.js.map} +1 -1
  111. package/dist/session-H34HUV2Y.js +17 -0
  112. package/dist/{session-4BGYASOH.js.map → session-H34HUV2Y.js.map} +1 -1
  113. package/dist/{stream-json-GY5N7WZT.js → stream-json-55YDNAFX.js} +18 -17
  114. package/dist/stream-json-55YDNAFX.js.map +1 -0
  115. package/dist/{types-DLH-Ritr.d.ts → types-DHqH0XIE.d.ts} +68 -6
  116. package/dist/version-WSE3QOPK.js +4 -0
  117. package/dist/{version-DPAMQW6M.js.map → version-WSE3QOPK.js.map} +1 -1
  118. package/dist/{vg-mcp-bridge-IGHJKKSL.js → vg-mcp-bridge-2CNLHNEL.js} +13 -12
  119. package/dist/vg-mcp-bridge-2CNLHNEL.js.map +1 -0
  120. package/dist/{vgd-ORPA5C2K.js → vgd-URUZQORN.js} +10 -9
  121. package/dist/vgd-URUZQORN.js.map +1 -0
  122. package/grammars/tree-sitter-hcl.wasm +0 -0
  123. package/package.json +9 -8
  124. package/dist/agent-5ANHRYEI.js +0 -26
  125. package/dist/baseline-WVD6P5IO.js +0 -9
  126. package/dist/chunk-2H2U3DC7.js +0 -6
  127. package/dist/chunk-2U37PFB2.js.map +0 -1
  128. package/dist/chunk-3LSG7Z4I.js.map +0 -1
  129. package/dist/chunk-75LJM5XD.js.map +0 -1
  130. package/dist/chunk-7QDHQLUS.js.map +0 -1
  131. package/dist/chunk-DT4KIVRI.js.map +0 -1
  132. package/dist/chunk-FOW5IF73.js.map +0 -1
  133. package/dist/chunk-JCN6MSXQ.js.map +0 -1
  134. package/dist/chunk-N6ZMZ5AI.js.map +0 -1
  135. package/dist/chunk-NKFXXP6W.js.map +0 -1
  136. package/dist/chunk-RXP66R2E.js +0 -39
  137. package/dist/chunk-RXP66R2E.js.map +0 -1
  138. package/dist/chunk-TFAXW7VM.js.map +0 -1
  139. package/dist/chunk-TNROG5CO.js.map +0 -1
  140. package/dist/chunk-UTNNGKKX.js.map +0 -1
  141. package/dist/chunk-WQLZSVNF.js.map +0 -1
  142. package/dist/chunk-WWGLVF7D.js.map +0 -1
  143. package/dist/chunk-YNV5M3JW.js.map +0 -1
  144. package/dist/ensure-map-HQ7A5NDL.js +0 -30
  145. package/dist/fs-NTQUFCXV.js.map +0 -1
  146. package/dist/graph-backend-W2TNTMY7.js +0 -14
  147. package/dist/interactive-EW2VJ3BZ.js.map +0 -1
  148. package/dist/load-64RK5O6C.js +0 -10
  149. package/dist/local-runtime-2ED33KK6.js +0 -6
  150. package/dist/model-manager-GJ2YSTSK.js +0 -10
  151. package/dist/run-outcome-6QNQIHPV.js.map +0 -1
  152. package/dist/runtime-session-RJHH3KMF.js +0 -21
  153. package/dist/session-4BGYASOH.js +0 -16
  154. package/dist/stream-json-GY5N7WZT.js.map +0 -1
  155. package/dist/version-DPAMQW6M.js +0 -4
  156. package/dist/vg-mcp-bridge-IGHJKKSL.js.map +0 -1
  157. package/dist/vgd-ORPA5C2K.js.map +0 -1
@@ -1,15 +1,17 @@
1
- import { FREE_PACK, navigationToolsetConfig, deferredToolNames, HOT_TOOLS } from './chunk-WWGLVF7D.js';
1
+ import { FREE_PACK, navigationToolsetConfig, deferredToolNames, HOT_TOOLS } from './chunk-DVW3SVCW.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';
7
- 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';
4
+ import { indexFor } from './chunk-Q4G2BWE3.js';
5
+ import { writeGraphIndex, SCHEMA_VERSION, loadGraph } from './chunk-3FMRFECC.js';
6
+ import { init_cli_invocation, whichOnPath, isInstalledOwnBinary } from './chunk-EZ7UC5Y5.js';
7
+ import { langById, grammarSetVersion, toolchainGrammarSetVersion, fileRolesFromParses, langForExtension, setGrammarsOverride, parseSource, toolchainParser } from './chunk-XLXJC7H3.js';
8
+ import { redactSecrets } from './chunk-T7RSQ5WD.js';
9
+ import { ensureVibgrateGitignore } from './chunk-WVMOGIPC.js';
10
+ import { loadCache, cacheDir } from './chunk-MVPRPSXU.js';
11
+ import { serializeGraph, stableStringify } from './chunk-O3LABX6I.js';
12
+ import { VERSION } from './chunk-PRG2ULPK.js';
11
13
  import { shortId, canonicalize, hashString, hashBytes } from './chunk-JFGYT6BI.js';
12
- import * as fs11 from 'fs';
14
+ import * as fs12 from 'fs';
13
15
  import * as path11 from 'path';
14
16
  import ignore from 'ignore';
15
17
  import * as v8 from 'v8';
@@ -22,7 +24,8 @@ import * as os from 'os';
22
24
  import { fileURLToPath } from 'url';
23
25
  import ts from 'typescript';
24
26
  import * as crypto from 'crypto';
25
- import * as fs8 from 'fs/promises';
27
+ import YAML from 'yaml';
28
+ import * as fs9 from 'fs/promises';
26
29
 
27
30
  var SKIP_DIRS = /* @__PURE__ */ new Set([
28
31
  // Version control, IDE & tool metadata
@@ -192,8 +195,8 @@ function toPosix(p) {
192
195
  function buildRootIgnore(root, exclude) {
193
196
  const ig = ignore();
194
197
  const gitignorePath = path11.join(root, ".gitignore");
195
- if (fs11.existsSync(gitignorePath)) {
196
- ig.add(fs11.readFileSync(gitignorePath, "utf8"));
198
+ if (fs12.existsSync(gitignorePath)) {
199
+ ig.add(fs12.readFileSync(gitignorePath, "utf8"));
197
200
  }
198
201
  if (exclude.length) ig.add(exclude);
199
202
  return ig;
@@ -208,7 +211,7 @@ function discover(options) {
208
211
  }
209
212
  }
210
213
  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));
214
+ const scopeAbs = (options.paths && options.paths.length ? options.paths.map((p) => path11.resolve(root, p)) : [root]).filter((p) => fs12.existsSync(p));
212
215
  const found = /* @__PURE__ */ new Map();
213
216
  const considerFile = (abs) => {
214
217
  const rel2 = toPosix(path11.relative(root, abs));
@@ -222,7 +225,7 @@ function discover(options) {
222
225
  const walk = (dir) => {
223
226
  let entries;
224
227
  try {
225
- entries = fs11.readdirSync(dir, { withFileTypes: true });
228
+ entries = fs12.readdirSync(dir, { withFileTypes: true });
226
229
  } catch {
227
230
  return;
228
231
  }
@@ -239,7 +242,7 @@ function discover(options) {
239
242
  }
240
243
  };
241
244
  for (const scope of scopeAbs) {
242
- const stat = fs11.statSync(scope);
245
+ const stat = fs12.statSync(scope);
243
246
  if (stat.isDirectory()) walk(scope);
244
247
  else if (stat.isFile()) considerFile(scope);
245
248
  }
@@ -723,12 +726,12 @@ function loadCoverage(root, explicit) {
723
726
  const candidates = (explicit && explicit.length ? explicit : DEFAULT_PATHS).map(
724
727
  (p) => path11.resolve(root, p)
725
728
  );
726
- const found = candidates.filter((p) => fs11.existsSync(p));
729
+ const found = candidates.filter((p) => fs12.existsSync(p));
727
730
  if (found.length === 0) return null;
728
731
  const map = /* @__PURE__ */ new Map();
729
732
  for (const file of found) {
730
733
  try {
731
- const text = fs11.readFileSync(file, "utf8");
734
+ const text = fs12.readFileSync(file, "utf8");
732
735
  if (file.endsWith(".json")) mergeIstanbul(map, text, root);
733
736
  else mergeLcov(map, text, root);
734
737
  } catch {
@@ -949,7 +952,7 @@ async function parseInline(files, options) {
949
952
  onProgress?.(0, files.length);
950
953
  for (const file of files) {
951
954
  try {
952
- const source = fs11.readFileSync(file.abs, "utf8");
955
+ const source = fs12.readFileSync(file.abs, "utf8");
953
956
  out.push(await parseSource(file.rel, file.lang.id, source));
954
957
  } catch (err) {
955
958
  out.push(emptyParse(file, `parse failed: ${err.message}`));
@@ -1006,7 +1009,7 @@ function isWorkerOom(err) {
1006
1009
  function resolveWorkerFile() {
1007
1010
  const here = path11.dirname(fileURLToPath(import.meta.url));
1008
1011
  const candidate = path11.join(here, "parse-worker.js");
1009
- return fs11.existsSync(candidate) ? candidate : null;
1012
+ return fs12.existsSync(candidate) ? candidate : null;
1010
1013
  }
1011
1014
  function chunk(items, buckets) {
1012
1015
  const out = Array.from({ length: Math.min(buckets, items.length || 1) }, () => []);
@@ -1300,9 +1303,9 @@ function loadTscCache(root, opts) {
1300
1303
  ts: ts.version,
1301
1304
  entries: {}
1302
1305
  };
1303
- if (!opts.disabled && fs11.existsSync(file)) {
1306
+ if (!opts.disabled && fs12.existsSync(file)) {
1304
1307
  try {
1305
- const loaded = JSON.parse(fs11.readFileSync(file, "utf8"));
1308
+ const loaded = JSON.parse(fs12.readFileSync(file, "utf8"));
1306
1309
  if (loaded.version === TSC_CACHE_VERSION && loaded.toolVersion === opts.toolVersion && loaded.ts === ts.version && loaded.entries) {
1307
1310
  data = loaded;
1308
1311
  }
@@ -1333,8 +1336,8 @@ function loadTscCache(root, opts) {
1333
1336
  },
1334
1337
  save() {
1335
1338
  if (opts.disabled) return;
1336
- fs11.mkdirSync(cacheDir(root), { recursive: true });
1337
- fs11.writeFileSync(file, stableStringify(data, 0));
1339
+ fs12.mkdirSync(cacheDir(root), { recursive: true });
1340
+ fs12.writeFileSync(file, stableStringify(data, 0));
1338
1341
  }
1339
1342
  };
1340
1343
  }
@@ -1344,7 +1347,7 @@ function abs$(root, rel2) {
1344
1347
  function detectAmbient(rel2, abs) {
1345
1348
  if (rel2.endsWith(".d.ts")) return true;
1346
1349
  try {
1347
- return AMBIENT_RE.test(fs11.readFileSync(abs, "utf8"));
1350
+ return AMBIENT_RE.test(fs12.readFileSync(abs, "utf8"));
1348
1351
  } catch {
1349
1352
  return true;
1350
1353
  }
@@ -1643,9 +1646,9 @@ function toPosix2(p) {
1643
1646
  function buildRootIgnore2(root, exclude) {
1644
1647
  const ig = ignore();
1645
1648
  const gitignorePath = path11.join(root, ".gitignore");
1646
- if (fs11.existsSync(gitignorePath)) {
1649
+ if (fs12.existsSync(gitignorePath)) {
1647
1650
  try {
1648
- ig.add(fs11.readFileSync(gitignorePath, "utf8"));
1651
+ ig.add(fs12.readFileSync(gitignorePath, "utf8"));
1649
1652
  } catch {
1650
1653
  }
1651
1654
  }
@@ -1689,7 +1692,7 @@ function discoverDocs(options) {
1689
1692
  const maxFiles = options.maxFiles ?? DOC_MAX_FILES;
1690
1693
  const rootIg = buildRootIgnore2(root, options.exclude ?? []);
1691
1694
  const scopeAbs = (options.paths?.length ? options.paths.map((p) => path11.resolve(root, p)) : [root]).filter(
1692
- (p) => fs11.existsSync(p)
1695
+ (p) => fs12.existsSync(p)
1693
1696
  );
1694
1697
  const found = /* @__PURE__ */ new Map();
1695
1698
  const consider = (abs) => {
@@ -1702,7 +1705,7 @@ function discoverDocs(options) {
1702
1705
  const category = classifyProjectContext(rel2);
1703
1706
  if (!category) return;
1704
1707
  try {
1705
- const st = fs11.statSync(abs);
1708
+ const st = fs12.statSync(abs);
1706
1709
  if (!st.isFile() || st.size > DOC_FILE_MAX_BYTES) return;
1707
1710
  if (st.size === 0) return;
1708
1711
  } catch {
@@ -1714,7 +1717,7 @@ function discoverDocs(options) {
1714
1717
  if (found.size >= maxFiles || depth > 12) return;
1715
1718
  let entries;
1716
1719
  try {
1717
- entries = fs11.readdirSync(dir, { withFileTypes: true });
1720
+ entries = fs12.readdirSync(dir, { withFileTypes: true });
1718
1721
  } catch {
1719
1722
  return;
1720
1723
  }
@@ -1733,7 +1736,7 @@ function discoverDocs(options) {
1733
1736
  };
1734
1737
  for (const scope of scopeAbs) {
1735
1738
  try {
1736
- const st = fs11.statSync(scope);
1739
+ const st = fs12.statSync(scope);
1737
1740
  if (st.isDirectory()) walk(scope, 0);
1738
1741
  else if (st.isFile()) consider(scope);
1739
1742
  } catch {
@@ -1790,7 +1793,7 @@ function documentNodesFromDocs(docs) {
1790
1793
  for (const d of docs) {
1791
1794
  let raw = "";
1792
1795
  try {
1793
- raw = fs11.readFileSync(d.abs, "utf8");
1796
+ raw = fs12.readFileSync(d.abs, "utf8");
1794
1797
  } catch {
1795
1798
  continue;
1796
1799
  }
@@ -1826,6 +1829,1640 @@ function documentNodesFromDocs(docs) {
1826
1829
  }
1827
1830
  return nodes.sort((a, b) => a.file.localeCompare(b.file) || a.id.localeCompare(b.id));
1828
1831
  }
1832
+
1833
+ // src/engine/toolchain/util.ts
1834
+ var TOOLCHAIN_FILE_MAX_BYTES = 2 * 1024 * 1024;
1835
+ var TOOLCHAIN_NODES_PER_FILE_MAX = 2e3;
1836
+ var DOC_MAX_CHARS = 240;
1837
+ var LineIndex = class {
1838
+ /** Byte offset at which each line starts. */
1839
+ starts;
1840
+ constructor(source) {
1841
+ const starts = [0];
1842
+ for (let i = 0; i < source.length; i++) {
1843
+ if (source.charCodeAt(i) === 10) starts.push(i + 1);
1844
+ }
1845
+ this.starts = starts;
1846
+ }
1847
+ /** 1-based line number containing `offset`. */
1848
+ lineAt(offset) {
1849
+ if (offset <= 0) return 1;
1850
+ let lo = 0;
1851
+ let hi = this.starts.length - 1;
1852
+ while (lo < hi) {
1853
+ const mid = lo + hi + 1 >> 1;
1854
+ if (this.starts[mid] <= offset) lo = mid;
1855
+ else hi = mid - 1;
1856
+ }
1857
+ return lo + 1;
1858
+ }
1859
+ /** Span covering the byte range `[start, end)`. */
1860
+ span(start, end) {
1861
+ const s = this.lineAt(start);
1862
+ const e = this.lineAt(Math.max(start, end - 1));
1863
+ return { start: s, end: Math.max(s, e) };
1864
+ }
1865
+ get lineCount() {
1866
+ return this.starts.length;
1867
+ }
1868
+ };
1869
+ function safeDoc(text) {
1870
+ if (!text) return void 0;
1871
+ const collapsed = text.replace(/\s+/g, " ").trim();
1872
+ if (!collapsed) return void 0;
1873
+ const redacted = redactSecrets(collapsed);
1874
+ return redacted.length > DOC_MAX_CHARS ? `${redacted.slice(0, DOC_MAX_CHARS - 1)}\u2026` : redacted;
1875
+ }
1876
+ function parseImageRef(raw) {
1877
+ const value = raw.trim();
1878
+ if (!value || /\s/.test(value)) return null;
1879
+ if (value.includes("$") || value.includes("{{")) return null;
1880
+ const atIdx = value.lastIndexOf("@");
1881
+ let digest;
1882
+ let rest = value;
1883
+ if (atIdx > 0) {
1884
+ digest = value.slice(atIdx + 1);
1885
+ rest = value.slice(0, atIdx);
1886
+ }
1887
+ let tag;
1888
+ const colonIdx = rest.lastIndexOf(":");
1889
+ if (colonIdx > 0 && colonIdx > rest.lastIndexOf("/")) {
1890
+ tag = rest.slice(colonIdx + 1);
1891
+ rest = rest.slice(0, colonIdx);
1892
+ }
1893
+ if (!rest) return null;
1894
+ return { raw: value, repository: rest, tag, digest };
1895
+ }
1896
+ function imageIdentity(ref) {
1897
+ if (ref.digest) return `${ref.repository}@${ref.digest}`;
1898
+ return ref.tag ? `${ref.repository}:${ref.tag}` : ref.repository;
1899
+ }
1900
+ function toPosix3(rel2) {
1901
+ return rel2.replace(/\\/g, "/");
1902
+ }
1903
+ function resolveRelative(fromFileRel, target) {
1904
+ const base = toPosix3(fromFileRel).split("/").slice(0, -1);
1905
+ const parts = toPosix3(target).split("/");
1906
+ const out = [...base];
1907
+ for (const part of parts) {
1908
+ if (!part || part === ".") continue;
1909
+ if (part === "..") {
1910
+ if (!out.length) return null;
1911
+ out.pop();
1912
+ continue;
1913
+ }
1914
+ out.push(part);
1915
+ }
1916
+ const joined = out.join("/");
1917
+ return joined || null;
1918
+ }
1919
+ function parseYamlStream(rel2, source) {
1920
+ const warnings = [];
1921
+ let parsed;
1922
+ try {
1923
+ parsed = YAML.parseAllDocuments(source, {
1924
+ // Parsing itself does not expand aliases — the bomb only detonates on
1925
+ // materialisation, so the alias budget is applied at `toJS` below.
1926
+ logLevel: "silent",
1927
+ uniqueKeys: false
1928
+ });
1929
+ } catch (err) {
1930
+ warnings.push(`${rel2}: YAML parse failed (${err.message})`);
1931
+ return { docs: [], warnings };
1932
+ }
1933
+ const docs = [];
1934
+ parsed.forEach((ast, index) => {
1935
+ if (ast.errors.length) {
1936
+ warnings.push(`${rel2}: YAML document ${index + 1} has errors \u2014 skipped`);
1937
+ return;
1938
+ }
1939
+ let value;
1940
+ try {
1941
+ value = ast.toJS({ maxAliasCount: 100 });
1942
+ } catch (err) {
1943
+ warnings.push(`${rel2}: YAML document ${index + 1} could not be materialised (${err.message})`);
1944
+ return;
1945
+ }
1946
+ if (value === null || value === void 0) return;
1947
+ docs.push({ value, ast, index });
1948
+ });
1949
+ return { docs, warnings };
1950
+ }
1951
+ function spanAt(doc, lines, path12) {
1952
+ const node = path12.length ? safeGetIn(doc.ast, path12) : doc.ast.contents;
1953
+ const range = nodeRange(node) ?? nodeRange(doc.ast.contents);
1954
+ if (!range) return { start: 1, end: 1 };
1955
+ return lines.span(range[0], range[2] ?? range[1]);
1956
+ }
1957
+ function safeGetIn(ast, path12) {
1958
+ try {
1959
+ return ast.getIn(path12, true);
1960
+ } catch {
1961
+ return void 0;
1962
+ }
1963
+ }
1964
+ function nodeRange(node) {
1965
+ const range = node?.range;
1966
+ return Array.isArray(range) ? range : null;
1967
+ }
1968
+ function getPath(value, path12) {
1969
+ let current = value;
1970
+ for (const key of path12) {
1971
+ if (current === null || typeof current !== "object") return void 0;
1972
+ current = current[key];
1973
+ }
1974
+ return current;
1975
+ }
1976
+ function getString(value, path12) {
1977
+ const found = getPath(value, path12);
1978
+ if (typeof found === "string") return found;
1979
+ if (typeof found === "number" || typeof found === "boolean") return String(found);
1980
+ return void 0;
1981
+ }
1982
+ function getArray(value, path12) {
1983
+ const found = getPath(value, path12);
1984
+ return Array.isArray(found) ? found : [];
1985
+ }
1986
+ function getRecordEntries(value, path12) {
1987
+ const found = getPath(value, path12);
1988
+ if (!found || typeof found !== "object" || Array.isArray(found)) return [];
1989
+ return Object.entries(found).sort(([a], [b]) => a.localeCompare(b, "en"));
1990
+ }
1991
+ function looksTemplated(source) {
1992
+ return /\{\{-?\s*(if|range|with|end|define|template|include|toYaml|\.Values|\.Release|\.Chart)/.test(
1993
+ source
1994
+ );
1995
+ }
1996
+
1997
+ // src/engine/toolchain/compose.ts
1998
+ var DEFAULT_DOCKERFILE = "Dockerfile";
1999
+ var composeExtractor = {
2000
+ format: "compose",
2001
+ matches(rel2, category) {
2002
+ if (category === "compose") return true;
2003
+ return /(^|\/)(docker-)?compose([.-][\w.-]+)?\.ya?ml$/i.test(rel2);
2004
+ },
2005
+ extract(rel2, source) {
2006
+ const { docs, warnings } = parseYamlStream(rel2, source);
2007
+ const lines = new LineIndex(source);
2008
+ const nodes = [];
2009
+ const edges = [];
2010
+ const doc = docs[0];
2011
+ if (!doc) return { nodes, edges, warnings };
2012
+ const services = getRecordEntries(doc.value, ["services"]);
2013
+ const declared = new Set(services.map(([name]) => `service:${name}`));
2014
+ for (const [name, service] of services) {
2015
+ if (nodes.length >= TOOLCHAIN_NODES_PER_FILE_MAX) {
2016
+ warnings.push(`${rel2}: stopped at ${TOOLCHAIN_NODES_PER_FILE_MAX} nodes`);
2017
+ break;
2018
+ }
2019
+ const address = `service:${name}`;
2020
+ const image = getString(service, ["image"]);
2021
+ const ports = getArray(service, ["ports"]).map(String);
2022
+ nodes.push({
2023
+ kind: "resource",
2024
+ name,
2025
+ qualifiedName: address,
2026
+ span: spanAt(doc, lines, ["services", name]),
2027
+ signature: "compose.service",
2028
+ doc: safeDoc(
2029
+ [image && `image ${image}`, ports.length && `ports ${ports.join(", ")}`].filter(Boolean).join(", ")
2030
+ ),
2031
+ importance: 0.5
2032
+ });
2033
+ if (image) {
2034
+ const ref = parseImageRef(image);
2035
+ if (ref) {
2036
+ nodes.push({
2037
+ kind: "image",
2038
+ name: ref.repository.split("/").pop() ?? ref.repository,
2039
+ qualifiedName: `image:${ref.raw}`,
2040
+ span: spanAt(doc, lines, ["services", name, "image"]),
2041
+ signature: "compose.image",
2042
+ doc: safeDoc(ref.tag ? `${ref.repository} tag ${ref.tag}` : ref.repository),
2043
+ importance: 0.3
2044
+ });
2045
+ edges.push({ kind: "builds_from", from: address, to: `image:${ref.raw}`, confidence: 1 });
2046
+ }
2047
+ }
2048
+ const dockerfileRel = composeDockerfilePath(rel2, service);
2049
+ if (dockerfileRel) {
2050
+ nodes.push({
2051
+ kind: "image",
2052
+ name: dockerfileRel.split("/").pop() ?? dockerfileRel,
2053
+ qualifiedName: `dockerfile:${dockerfileRel}`,
2054
+ span: spanAt(doc, lines, ["services", name, "build"]),
2055
+ signature: "compose.build",
2056
+ doc: safeDoc(`builds from ${dockerfileRel}`),
2057
+ importance: 0.4
2058
+ });
2059
+ edges.push({
2060
+ kind: "builds_from",
2061
+ from: address,
2062
+ to: `dockerfile:${dockerfileRel}`,
2063
+ confidence: 1
2064
+ });
2065
+ }
2066
+ for (const target of composeDependsOn(service)) {
2067
+ const to = `service:${target}`;
2068
+ if (declared.has(to)) edges.push({ kind: "depends_on", from: address, to, confidence: 1 });
2069
+ }
2070
+ for (const port of ports) {
2071
+ edges.push({ kind: "exposes", from: address, to: `port:${port}`, confidence: 1 });
2072
+ }
2073
+ }
2074
+ const portTargets = new Set(
2075
+ edges.filter((e) => e.kind === "exposes").map((e) => e.to)
2076
+ );
2077
+ for (const target of [...portTargets].sort()) {
2078
+ nodes.push({
2079
+ kind: "property",
2080
+ name: target.slice("port:".length),
2081
+ qualifiedName: target,
2082
+ span: { start: 1, end: 1 },
2083
+ signature: "compose.port",
2084
+ importance: 0.1
2085
+ });
2086
+ }
2087
+ return { nodes, edges, warnings };
2088
+ }
2089
+ };
2090
+ function composeDockerfilePath(rel2, service) {
2091
+ const build = getPath(service, ["build"]);
2092
+ if (build === void 0 || build === null) return null;
2093
+ if (typeof build === "string") {
2094
+ return resolveRelative(rel2, `${build}/${DEFAULT_DOCKERFILE}`);
2095
+ }
2096
+ if (typeof build !== "object") return null;
2097
+ const context = getString(build, ["context"]) ?? ".";
2098
+ const dockerfile = getString(build, ["dockerfile"]) ?? DEFAULT_DOCKERFILE;
2099
+ return resolveRelative(rel2, `${context}/${dockerfile}`);
2100
+ }
2101
+ function composeDependsOn(service) {
2102
+ const raw = getPath(service, ["depends_on"]);
2103
+ if (Array.isArray(raw)) {
2104
+ return raw.filter((v) => typeof v === "string").sort();
2105
+ }
2106
+ if (raw && typeof raw === "object") {
2107
+ return Object.keys(raw).sort();
2108
+ }
2109
+ return [];
2110
+ }
2111
+
2112
+ // src/engine/toolchain/dockerfile.ts
2113
+ var INSTRUCTION = /^([A-Za-z][A-Za-z0-9_]*)\s+([\s\S]*)$/;
2114
+ function logicalLines(source) {
2115
+ const rawLines = source.split(/\r?\n/);
2116
+ let escapeChar = "\\";
2117
+ for (const line of rawLines) {
2118
+ const trimmed = line.trim();
2119
+ if (!trimmed) continue;
2120
+ if (!trimmed.startsWith("#")) break;
2121
+ const directive = /^#\s*escape\s*=\s*(\S)/i.exec(trimmed);
2122
+ if (directive) escapeChar = directive[1];
2123
+ }
2124
+ const out = [];
2125
+ let buffer = null;
2126
+ let startLine = 0;
2127
+ for (let i = 0; i < rawLines.length; i++) {
2128
+ const raw = rawLines[i];
2129
+ const trimmed = raw.trim();
2130
+ if (!trimmed || trimmed.startsWith("#")) continue;
2131
+ const continues = trimmed.endsWith(escapeChar);
2132
+ const fragment = continues ? trimmed.slice(0, -1).trimEnd() : trimmed;
2133
+ if (buffer === null) {
2134
+ buffer = fragment;
2135
+ startLine = i + 1;
2136
+ } else {
2137
+ buffer += ` ${fragment}`;
2138
+ }
2139
+ if (!continues) {
2140
+ const match = INSTRUCTION.exec(buffer);
2141
+ if (match) {
2142
+ out.push({
2143
+ instruction: match[1].toUpperCase(),
2144
+ args: match[2].trim(),
2145
+ startLine,
2146
+ endLine: i + 1
2147
+ });
2148
+ }
2149
+ buffer = null;
2150
+ }
2151
+ }
2152
+ if (buffer !== null) {
2153
+ const match = INSTRUCTION.exec(buffer);
2154
+ if (match) {
2155
+ out.push({
2156
+ instruction: match[1].toUpperCase(),
2157
+ args: match[2].trim(),
2158
+ startLine,
2159
+ endLine: rawLines.length
2160
+ });
2161
+ }
2162
+ }
2163
+ return out;
2164
+ }
2165
+ function parseFrom(args) {
2166
+ const tokens = args.split(/\s+/).filter(Boolean);
2167
+ const positional = [];
2168
+ for (const token of tokens) {
2169
+ if (token.startsWith("--")) continue;
2170
+ positional.push(token);
2171
+ }
2172
+ if (!positional.length) return null;
2173
+ const image = positional[0];
2174
+ const asIdx = positional.findIndex((t) => t.toUpperCase() === "AS");
2175
+ const stage = asIdx > 0 && positional[asIdx + 1] ? positional[asIdx + 1] : void 0;
2176
+ return { image, stage };
2177
+ }
2178
+ function parseCopyFrom(args) {
2179
+ const match = /--from=(\S+)/i.exec(args);
2180
+ return match ? match[1] : null;
2181
+ }
2182
+ function parseExpose(args) {
2183
+ return args.split(/\s+/).filter(Boolean).filter((token) => !token.startsWith("--"));
2184
+ }
2185
+ function copySourcePaths(args, hasFromFlag) {
2186
+ const tokens = args.split(/\s+/).filter(Boolean).filter((t) => !t.startsWith("--"));
2187
+ if (tokens.length < 2) return [];
2188
+ const sources = tokens.slice(0, -1);
2189
+ return sources.filter(
2190
+ (source) => source !== "." && !source.includes("*") && !source.includes("?") && !source.includes("[") && !source.includes("$") && !/^https?:\/\//i.test(source)
2191
+ ).map((source) => source.replace(/^\.\//, "")).sort();
2192
+ }
2193
+ var dockerfileExtractor = {
2194
+ format: "dockerfile",
2195
+ matches(rel2, category) {
2196
+ if (category === "docker") return true;
2197
+ const base = toPosix3(rel2).split("/").pop()?.toLowerCase() ?? "";
2198
+ return base === "dockerfile" || base === "containerfile" || base.startsWith("dockerfile.");
2199
+ },
2200
+ extract(rel2, source) {
2201
+ const lines = new LineIndex(source);
2202
+ const nodes = [];
2203
+ const edges = [];
2204
+ const warnings = [];
2205
+ const posixRel = toPosix3(rel2);
2206
+ const fileAddress = `dockerfile:${posixRel}`;
2207
+ nodes.push({
2208
+ kind: "image",
2209
+ name: posixRel.split("/").pop() ?? posixRel,
2210
+ qualifiedName: fileAddress,
2211
+ span: { start: 1, end: Math.max(1, lines.lineCount) },
2212
+ signature: "dockerfile",
2213
+ importance: 0.5
2214
+ });
2215
+ const stages = /* @__PURE__ */ new Map();
2216
+ const copySources = /* @__PURE__ */ new Map();
2217
+ let stageIndex = 0;
2218
+ let currentStage = null;
2219
+ for (const line of logicalLines(source)) {
2220
+ if (nodes.length >= TOOLCHAIN_NODES_PER_FILE_MAX) {
2221
+ warnings.push(`${rel2}: stopped at ${TOOLCHAIN_NODES_PER_FILE_MAX} nodes`);
2222
+ break;
2223
+ }
2224
+ switch (line.instruction) {
2225
+ case "FROM": {
2226
+ const from = parseFrom(line.args);
2227
+ if (!from) break;
2228
+ const stageName = from.stage ?? String(stageIndex);
2229
+ const stageAddress = `${fileAddress}#${stageName}`;
2230
+ stages.set(stageName.toLowerCase(), stageAddress);
2231
+ currentStage = stageAddress;
2232
+ stageIndex++;
2233
+ nodes.push({
2234
+ kind: "image",
2235
+ name: from.stage ?? `stage ${stageIndex - 1}`,
2236
+ qualifiedName: stageAddress,
2237
+ span: { start: line.startLine, end: line.endLine },
2238
+ signature: "dockerfile.stage",
2239
+ doc: safeDoc(`from ${from.image}`),
2240
+ importance: 0.4
2241
+ });
2242
+ edges.push({ kind: "contains", from: fileAddress, to: stageAddress, confidence: 1 });
2243
+ const baseStage = stages.get(from.image.toLowerCase());
2244
+ if (baseStage && baseStage !== stageAddress) {
2245
+ edges.push({
2246
+ kind: "builds_from",
2247
+ from: stageAddress,
2248
+ to: baseStage,
2249
+ confidence: 1
2250
+ });
2251
+ } else {
2252
+ const ref = parseImageRef(from.image);
2253
+ if (ref) {
2254
+ const imageAddress = `image:${ref.raw}`;
2255
+ nodes.push({
2256
+ kind: "image",
2257
+ name: ref.repository.split("/").pop() ?? ref.repository,
2258
+ qualifiedName: imageAddress,
2259
+ span: { start: line.startLine, end: line.endLine },
2260
+ signature: "dockerfile.base",
2261
+ doc: safeDoc(ref.tag ? `${ref.repository} tag ${ref.tag}` : ref.repository),
2262
+ importance: 0.3
2263
+ });
2264
+ edges.push({
2265
+ kind: "builds_from",
2266
+ from: stageAddress,
2267
+ to: imageAddress,
2268
+ confidence: 1
2269
+ });
2270
+ }
2271
+ }
2272
+ break;
2273
+ }
2274
+ case "COPY":
2275
+ case "ADD": {
2276
+ const fromRef = parseCopyFrom(line.args);
2277
+ if (fromRef) {
2278
+ if (!currentStage) break;
2279
+ const target = stages.get(fromRef.toLowerCase());
2280
+ if (target && target !== currentStage) {
2281
+ edges.push({ kind: "depends_on", from: currentStage, to: target, confidence: 1 });
2282
+ }
2283
+ break;
2284
+ }
2285
+ if (!currentStage) break;
2286
+ const forStage = copySources.get(currentStage) ?? /* @__PURE__ */ new Set();
2287
+ for (const source2 of copySourcePaths(line.args)) {
2288
+ const relativeToDockerfile = resolveRelative(posixRel, source2);
2289
+ if (relativeToDockerfile) forStage.add(relativeToDockerfile);
2290
+ forStage.add(toPosix3(source2).replace(/^\.\//, ""));
2291
+ }
2292
+ if (forStage.size) copySources.set(currentStage, forStage);
2293
+ break;
2294
+ }
2295
+ case "EXPOSE": {
2296
+ if (!currentStage) break;
2297
+ for (const port of parseExpose(line.args)) {
2298
+ const portAddress = `port:${port}`;
2299
+ nodes.push({
2300
+ kind: "property",
2301
+ name: port,
2302
+ qualifiedName: portAddress,
2303
+ span: { start: line.startLine, end: line.endLine },
2304
+ signature: "dockerfile.expose",
2305
+ importance: 0.1
2306
+ });
2307
+ edges.push({ kind: "exposes", from: currentStage, to: portAddress, confidence: 1 });
2308
+ }
2309
+ break;
2310
+ }
2311
+ }
2312
+ }
2313
+ const copySourcesByStage = {};
2314
+ for (const stage of [...copySources.keys()].sort()) {
2315
+ copySourcesByStage[stage] = [...copySources.get(stage) ?? []].sort();
2316
+ }
2317
+ return { nodes, edges, warnings, linkHints: { copySourcesByStage } };
2318
+ }
2319
+ };
2320
+
2321
+ // src/engine/toolchain/helm.ts
2322
+ function collectValuesImages(value, prefix, out) {
2323
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
2324
+ const repository = getString(value, ["repository"]);
2325
+ const tag = getString(value, ["tag"]);
2326
+ const registry = getString(value, ["registry"]);
2327
+ if (repository) {
2328
+ const full = [registry, repository].filter(Boolean).join("/");
2329
+ const raw = tag ? `${full}:${tag}` : full;
2330
+ if (!out.has(raw)) out.set(raw, prefix);
2331
+ }
2332
+ const direct = getString(value, ["image"]);
2333
+ if (direct && parseImageRef(direct)) {
2334
+ if (!out.has(direct)) out.set(direct, [...prefix, "image"]);
2335
+ }
2336
+ for (const [key, child] of getRecordEntries(value, [])) {
2337
+ if (key === "repository" || key === "tag" || key === "registry") continue;
2338
+ collectValuesImages(child, [...prefix, key], out);
2339
+ }
2340
+ }
2341
+ var helmExtractor = {
2342
+ format: "helm",
2343
+ matches(rel2) {
2344
+ const base = toPosix3(rel2).split("/").pop()?.toLowerCase() ?? "";
2345
+ if (base === "chart.yaml" || base === "chart.yml") return true;
2346
+ return /(^|\/)values([.-][\w.-]+)?\.ya?ml$/i.test(rel2) && /(^|\/)charts?\//i.test(rel2);
2347
+ },
2348
+ extract(rel2, source) {
2349
+ const { docs, warnings } = parseYamlStream(rel2, source);
2350
+ const lines = new LineIndex(source);
2351
+ const nodes = [];
2352
+ const edges = [];
2353
+ const doc = docs[0];
2354
+ if (!doc) return { nodes, edges, warnings };
2355
+ const base = toPosix3(rel2).split("/").pop()?.toLowerCase() ?? "";
2356
+ const isChartFile = base === "chart.yaml" || base === "chart.yml";
2357
+ if (isChartFile) {
2358
+ const name = getString(doc.value, ["name"]);
2359
+ if (!name) return { nodes, edges, warnings };
2360
+ const version = getString(doc.value, ["version"]);
2361
+ const appVersion = getString(doc.value, ["appVersion"]);
2362
+ const address = `chart:${name}`;
2363
+ nodes.push({
2364
+ kind: "chart",
2365
+ name,
2366
+ qualifiedName: address,
2367
+ span: spanAt(doc, lines, []),
2368
+ signature: "helm.chart",
2369
+ doc: safeDoc(
2370
+ [version && `version ${version}`, appVersion && `app ${appVersion}`].filter(Boolean).join(", ")
2371
+ ),
2372
+ importance: 0.7
2373
+ });
2374
+ for (const dependency of getArray(doc.value, ["dependencies"])) {
2375
+ const depName = getString(dependency, ["name"]);
2376
+ if (!depName) continue;
2377
+ const depVersion = getString(dependency, ["version"]);
2378
+ const depAddress = `chart:${depName}`;
2379
+ nodes.push({
2380
+ kind: "chart",
2381
+ name: depName,
2382
+ qualifiedName: depAddress,
2383
+ span: spanAt(doc, lines, ["dependencies"]),
2384
+ signature: "helm.dependency",
2385
+ doc: safeDoc(depVersion ? `version ${depVersion}` : void 0),
2386
+ importance: 0.4
2387
+ });
2388
+ edges.push({ kind: "depends_on", from: address, to: depAddress, confidence: 1 });
2389
+ }
2390
+ return { nodes, edges, warnings };
2391
+ }
2392
+ const images = /* @__PURE__ */ new Map();
2393
+ collectValuesImages(doc.value, [], images);
2394
+ for (const raw of [...images.keys()].sort()) {
2395
+ const ref = parseImageRef(raw);
2396
+ if (!ref) continue;
2397
+ nodes.push({
2398
+ kind: "image",
2399
+ name: ref.repository.split("/").pop() ?? ref.repository,
2400
+ qualifiedName: `image:${ref.raw}`,
2401
+ span: spanAt(doc, lines, images.get(raw) ?? []),
2402
+ signature: "helm.values.image",
2403
+ doc: safeDoc(ref.tag ? `${ref.repository} tag ${ref.tag}` : ref.repository),
2404
+ importance: 0.3
2405
+ });
2406
+ }
2407
+ return { nodes, edges, warnings };
2408
+ }
2409
+ };
2410
+
2411
+ // src/engine/toolchain/kubernetes.ts
2412
+ var WORKLOAD_TEMPLATE_PATHS = {
2413
+ Deployment: ["spec", "template"],
2414
+ StatefulSet: ["spec", "template"],
2415
+ DaemonSet: ["spec", "template"],
2416
+ ReplicaSet: ["spec", "template"],
2417
+ ReplicationController: ["spec", "template"],
2418
+ Job: ["spec", "template"],
2419
+ CronJob: ["spec", "jobTemplate", "spec", "template"],
2420
+ Pod: []
2421
+ };
2422
+ var CONTAINER_FIELDS = ["initContainers", "containers", "ephemeralContainers"];
2423
+ function isKubernetesDoc(value) {
2424
+ return typeof getString(value, ["apiVersion"]) === "string" && typeof getString(value, ["kind"]) === "string";
2425
+ }
2426
+ function objectAddress(value) {
2427
+ const kind = getString(value, ["kind"]);
2428
+ const name = getString(value, ["metadata", "name"]);
2429
+ if (!kind || !name) return null;
2430
+ const namespace = getString(value, ["metadata", "namespace"]) ?? "default";
2431
+ return `${namespace}/${kind}/${name}`;
2432
+ }
2433
+ function podSpecPath(kind) {
2434
+ const templatePath = WORKLOAD_TEMPLATE_PATHS[kind];
2435
+ if (!templatePath) return null;
2436
+ return [...templatePath, "spec"];
2437
+ }
2438
+ var kubernetesExtractor = {
2439
+ format: "kubernetes",
2440
+ matches(rel2, category, head) {
2441
+ if (!/\.ya?ml$/i.test(rel2)) return false;
2442
+ if (looksTemplated(head)) return false;
2443
+ if (/(^|\/)templates\//i.test(rel2)) return false;
2444
+ return /(^|\n)apiVersion:\s*\S/.test(head) && /(^|\n)kind:\s*\S/.test(head);
2445
+ },
2446
+ extract(rel2, source) {
2447
+ const { docs, warnings } = parseYamlStream(rel2, source);
2448
+ const lines = new LineIndex(source);
2449
+ const nodes = [];
2450
+ const edges = [];
2451
+ const workloadLabels = /* @__PURE__ */ new Map();
2452
+ for (const doc of docs) {
2453
+ if (nodes.length >= TOOLCHAIN_NODES_PER_FILE_MAX) {
2454
+ warnings.push(`${rel2}: stopped at ${TOOLCHAIN_NODES_PER_FILE_MAX} nodes`);
2455
+ break;
2456
+ }
2457
+ if (!isKubernetesDoc(doc.value)) continue;
2458
+ const address = objectAddress(doc.value);
2459
+ if (!address) continue;
2460
+ const kind = getString(doc.value, ["kind"]);
2461
+ const name = getString(doc.value, ["metadata", "name"]);
2462
+ const podPath = podSpecPath(kind);
2463
+ const isWorkload = podPath !== null;
2464
+ nodes.push({
2465
+ kind: isWorkload ? "workload" : "resource",
2466
+ name,
2467
+ qualifiedName: address,
2468
+ span: spanAt(doc, lines, []),
2469
+ signature: `k8s.${kind}`,
2470
+ doc: safeDoc(describeObject(doc.value, kind)),
2471
+ importance: isWorkload ? 0.6 : 0.4
2472
+ });
2473
+ if (isWorkload) {
2474
+ const labels = readLabels(doc.value, [...WORKLOAD_TEMPLATE_PATHS[kind] ?? [], "metadata", "labels"]);
2475
+ if (Object.keys(labels).length) workloadLabels.set(address, labels);
2476
+ extractContainers(doc, lines, address, podPath, nodes, edges);
2477
+ }
2478
+ for (const reference of referencedConfig(doc.value, podPath)) {
2479
+ edges.push({ kind: "mounts", from: address, to: reference, confidence: 1 });
2480
+ }
2481
+ }
2482
+ for (const doc of docs) {
2483
+ if (getString(doc.value, ["kind"]) !== "Service") continue;
2484
+ const from = objectAddress(doc.value);
2485
+ if (!from) continue;
2486
+ const selector = readLabels(doc.value, ["spec", "selector"]);
2487
+ if (!Object.keys(selector).length) continue;
2488
+ for (const [address, labels] of [...workloadLabels].sort(([a], [b]) => a.localeCompare(b, "en"))) {
2489
+ if (isSubset(selector, labels)) {
2490
+ edges.push({ kind: "exposes", from, to: address, confidence: 1 });
2491
+ }
2492
+ }
2493
+ }
2494
+ return { nodes, edges, warnings };
2495
+ }
2496
+ };
2497
+ function extractContainers(doc, lines, workloadAddress, podPath, nodes, edges) {
2498
+ const seenImages = /* @__PURE__ */ new Set();
2499
+ for (const field of CONTAINER_FIELDS) {
2500
+ const containers = getArray(doc.value, [...podPath, field]);
2501
+ containers.forEach((container, index) => {
2502
+ const image = getString(container, ["image"]);
2503
+ if (!image) return;
2504
+ const ref = parseImageRef(image);
2505
+ if (!ref) return;
2506
+ if (seenImages.has(ref.raw)) return;
2507
+ seenImages.add(ref.raw);
2508
+ nodes.push({
2509
+ kind: "image",
2510
+ name: ref.repository.split("/").pop() ?? ref.repository,
2511
+ qualifiedName: `image:${ref.raw}`,
2512
+ span: spanAt(doc, lines, [...podPath, field, index, "image"]),
2513
+ signature: "k8s.image",
2514
+ doc: safeDoc(ref.tag ? `${ref.repository} tag ${ref.tag}` : ref.repository),
2515
+ importance: 0.3
2516
+ });
2517
+ edges.push({
2518
+ kind: "builds_from",
2519
+ from: workloadAddress,
2520
+ to: `image:${ref.raw}`,
2521
+ confidence: 1
2522
+ });
2523
+ });
2524
+ }
2525
+ }
2526
+ function referencedConfig(value, podPath) {
2527
+ if (!podPath) return [];
2528
+ const out = /* @__PURE__ */ new Set();
2529
+ for (const volume of getArray(value, [...podPath, "volumes"])) {
2530
+ const configMap = getString(volume, ["configMap", "name"]);
2531
+ if (configMap) out.add(`ConfigMap/${configMap}`);
2532
+ const secret = getString(volume, ["secret", "secretName"]);
2533
+ if (secret) out.add(`Secret/${secret}`);
2534
+ const claim = getString(volume, ["persistentVolumeClaim", "claimName"]);
2535
+ if (claim) out.add(`PersistentVolumeClaim/${claim}`);
2536
+ }
2537
+ for (const field of CONTAINER_FIELDS) {
2538
+ for (const container of getArray(value, [...podPath, field])) {
2539
+ for (const envFrom of getArray(container, ["envFrom"])) {
2540
+ const configMap = getString(envFrom, ["configMapRef", "name"]);
2541
+ if (configMap) out.add(`ConfigMap/${configMap}`);
2542
+ const secret = getString(envFrom, ["secretRef", "name"]);
2543
+ if (secret) out.add(`Secret/${secret}`);
2544
+ }
2545
+ for (const env of getArray(container, ["env"])) {
2546
+ const configMap = getString(env, ["valueFrom", "configMapKeyRef", "name"]);
2547
+ if (configMap) out.add(`ConfigMap/${configMap}`);
2548
+ const secret = getString(env, ["valueFrom", "secretKeyRef", "name"]);
2549
+ if (secret) out.add(`Secret/${secret}`);
2550
+ }
2551
+ }
2552
+ }
2553
+ return [...out].sort();
2554
+ }
2555
+ function readLabels(value, path12) {
2556
+ const out = {};
2557
+ for (const [key, raw] of getRecordEntries(value, path12)) {
2558
+ if (typeof raw === "string") out[key] = raw;
2559
+ else if (typeof raw === "number" || typeof raw === "boolean") out[key] = String(raw);
2560
+ }
2561
+ return out;
2562
+ }
2563
+ function isSubset(selector, labels) {
2564
+ const keys = Object.keys(selector);
2565
+ if (!keys.length) return false;
2566
+ return keys.every((key) => labels[key] === selector[key]);
2567
+ }
2568
+ function describeObject(value, kind) {
2569
+ const parts = [];
2570
+ const replicas = getPath(value, ["spec", "replicas"]);
2571
+ if (typeof replicas === "number") parts.push(`${replicas} replica${replicas === 1 ? "" : "s"}`);
2572
+ const podPath = podSpecPath(kind);
2573
+ if (podPath) {
2574
+ const count = CONTAINER_FIELDS.reduce(
2575
+ (sum, field) => sum + getArray(value, [...podPath, field]).length,
2576
+ 0
2577
+ );
2578
+ if (count) parts.push(`${count} container${count === 1 ? "" : "s"}`);
2579
+ }
2580
+ if (kind === "Service") {
2581
+ const type = getString(value, ["spec", "type"]);
2582
+ if (type) parts.push(type);
2583
+ const ports = getArray(value, ["spec", "ports"]).map((p) => getString(p, ["port"])).filter(Boolean);
2584
+ if (ports.length) parts.push(`port ${ports.join(", ")}`);
2585
+ }
2586
+ return parts.join(", ");
2587
+ }
2588
+
2589
+ // src/engine/toolchain/terraform.ts
2590
+ var BUILTIN_REF_PREFIXES = /* @__PURE__ */ new Set([
2591
+ "count",
2592
+ "each",
2593
+ "self",
2594
+ "path",
2595
+ "terraform"
2596
+ ]);
2597
+ var BLOCK_SPECS = {
2598
+ resource: {
2599
+ kind: "resource",
2600
+ labels: 2,
2601
+ address: ([type, name]) => `${type}.${name}`,
2602
+ signature: "terraform.resource",
2603
+ importance: 0.5
2604
+ },
2605
+ data: {
2606
+ kind: "resource",
2607
+ labels: 2,
2608
+ address: ([type, name]) => `data.${type}.${name}`,
2609
+ signature: "terraform.data",
2610
+ importance: 0.3
2611
+ },
2612
+ module: {
2613
+ kind: "module",
2614
+ labels: 1,
2615
+ address: ([name]) => `module.${name}`,
2616
+ signature: "terraform.module",
2617
+ importance: 0.6
2618
+ },
2619
+ variable: {
2620
+ kind: "property",
2621
+ labels: 1,
2622
+ address: ([name]) => `var.${name}`,
2623
+ signature: "terraform.variable",
2624
+ importance: 0.2
2625
+ },
2626
+ output: {
2627
+ kind: "property",
2628
+ labels: 1,
2629
+ address: ([name]) => `output.${name}`,
2630
+ signature: "terraform.output",
2631
+ importance: 0.3
2632
+ },
2633
+ provider: {
2634
+ kind: "package",
2635
+ labels: 1,
2636
+ address: ([name]) => `provider.${name}`,
2637
+ signature: "terraform.provider",
2638
+ importance: 0.4
2639
+ }
2640
+ };
2641
+ function stringLitValue(node) {
2642
+ for (const child of node.namedChildren) {
2643
+ if (child?.type === "template_literal") return child.text;
2644
+ }
2645
+ return "";
2646
+ }
2647
+ function topLevelBlocks(root) {
2648
+ const out = [];
2649
+ for (const bodyCandidate of root.namedChildren) {
2650
+ if (!bodyCandidate) continue;
2651
+ const body = bodyCandidate.type === "body" ? bodyCandidate : null;
2652
+ if (!body) continue;
2653
+ for (const child of body.namedChildren) {
2654
+ if (child?.type === "block") out.push(child);
2655
+ }
2656
+ }
2657
+ return out;
2658
+ }
2659
+ function blockBody(block) {
2660
+ for (const child of block.namedChildren) {
2661
+ if (child?.type === "body") return child;
2662
+ }
2663
+ return null;
2664
+ }
2665
+ function attributes(body) {
2666
+ const out = [];
2667
+ for (const child of body.namedChildren) {
2668
+ if (child?.type !== "attribute") continue;
2669
+ let name = "";
2670
+ let expr = null;
2671
+ for (const part of child.namedChildren) {
2672
+ if (!part) continue;
2673
+ if (!name && part.type === "identifier") name = part.text;
2674
+ else if (part.type === "expression") expr = part;
2675
+ }
2676
+ if (name) out.push({ name, expr, node: child });
2677
+ }
2678
+ return out;
2679
+ }
2680
+ function nestedBlocks(body) {
2681
+ const out = [];
2682
+ for (const child of body.namedChildren) {
2683
+ if (child?.type === "block") out.push(child);
2684
+ }
2685
+ return out;
2686
+ }
2687
+ function blockHead(block) {
2688
+ let type = "";
2689
+ const labels = [];
2690
+ for (const child of block.namedChildren) {
2691
+ if (!child) continue;
2692
+ if (!type && child.type === "identifier") type = child.text;
2693
+ else if (child.type === "string_lit") labels.push(stringLitValue(child));
2694
+ else if (child.type === "body") break;
2695
+ }
2696
+ return { type, labels };
2697
+ }
2698
+ function referencedAddresses(expr, out) {
2699
+ if (!expr) return;
2700
+ const stack = [expr];
2701
+ while (stack.length) {
2702
+ const node = stack.pop();
2703
+ if (!node) continue;
2704
+ if (node.type === "variable_expr") {
2705
+ const root = node.text;
2706
+ const parts = [root];
2707
+ let sibling = node.nextNamedSibling;
2708
+ while (sibling && sibling.type === "get_attr") {
2709
+ const id = sibling.namedChildren.find((c) => c?.type === "identifier");
2710
+ if (!id) break;
2711
+ parts.push(id.text);
2712
+ sibling = sibling.nextNamedSibling;
2713
+ }
2714
+ const address = terraformAddress(parts);
2715
+ if (address) out.add(address);
2716
+ continue;
2717
+ }
2718
+ for (const child of node.namedChildren) {
2719
+ if (child) stack.push(child);
2720
+ }
2721
+ }
2722
+ }
2723
+ function terraformAddress(parts) {
2724
+ const [head, ...rest] = parts;
2725
+ if (!head || BUILTIN_REF_PREFIXES.has(head)) return null;
2726
+ if (head === "var" || head === "local") return rest.length ? `${head}.${rest[0]}` : null;
2727
+ if (head === "module") return rest.length ? `module.${rest[0]}` : null;
2728
+ if (head === "data") return rest.length >= 2 ? `data.${rest[0]}.${rest[1]}` : null;
2729
+ return rest.length ? `${head}.${rest[0]}` : null;
2730
+ }
2731
+ function extractRequiredProviders(terraformBlock, lines, nodes, seen) {
2732
+ const body = blockBody(terraformBlock);
2733
+ if (!body) return;
2734
+ for (const nested of nestedBlocks(body)) {
2735
+ const { type } = blockHead(nested);
2736
+ if (type !== "required_providers") continue;
2737
+ const inner = blockBody(nested);
2738
+ if (!inner) continue;
2739
+ for (const attr of attributes(inner)) {
2740
+ let source;
2741
+ let version;
2742
+ const objectAttrs = attr.expr ? collectObjectAttributes(attr.expr) : [];
2743
+ if (objectAttrs.length) {
2744
+ for (const { key, value } of objectAttrs) {
2745
+ if (key === "source") source = value;
2746
+ else if (key === "version") version = value;
2747
+ }
2748
+ } else if (attr.expr) {
2749
+ const literal = findFirst(attr.expr, "string_lit");
2750
+ if (literal) {
2751
+ version = stringLitValue(literal);
2752
+ source = `hashicorp/${attr.name}`;
2753
+ }
2754
+ }
2755
+ const address = `provider.${attr.name}`;
2756
+ if (seen.has(address)) continue;
2757
+ seen.add(address);
2758
+ nodes.push({
2759
+ kind: "package",
2760
+ name: attr.name,
2761
+ qualifiedName: address,
2762
+ span: lines.span(attr.node.startIndex, attr.node.endIndex),
2763
+ signature: "terraform.required_provider",
2764
+ doc: safeDoc(
2765
+ [source && `source ${source}`, version && `version ${version}`].filter(Boolean).join(", ")
2766
+ ),
2767
+ importance: 0.4
2768
+ });
2769
+ }
2770
+ }
2771
+ }
2772
+ function collectObjectAttributes(expr) {
2773
+ const out = [];
2774
+ const stack = [expr];
2775
+ while (stack.length) {
2776
+ const node = stack.pop();
2777
+ if (!node) continue;
2778
+ if (node.type === "object_elem") {
2779
+ const key = node.namedChildren.find((c) => c?.type === "expression");
2780
+ const rest = node.namedChildren.filter((c) => c?.type === "expression");
2781
+ const valueNode = rest.length > 1 ? rest[1] : null;
2782
+ const keyText = key ? plainText(key) : "";
2783
+ const valueText = valueNode ? plainText(valueNode) : "";
2784
+ if (keyText) out.push({ key: keyText, value: valueText });
2785
+ continue;
2786
+ }
2787
+ for (const child of node.namedChildren) {
2788
+ if (child) stack.push(child);
2789
+ }
2790
+ }
2791
+ return out;
2792
+ }
2793
+ function plainText(expr) {
2794
+ const lit = findFirst(expr, "string_lit");
2795
+ if (lit) return stringLitValue(lit);
2796
+ return expr.text.trim();
2797
+ }
2798
+ function findFirst(node, type) {
2799
+ const stack = [node];
2800
+ while (stack.length) {
2801
+ const current = stack.pop();
2802
+ if (!current) continue;
2803
+ if (current.type === type) return current;
2804
+ for (const child of current.namedChildren) {
2805
+ if (child) stack.push(child);
2806
+ }
2807
+ }
2808
+ return null;
2809
+ }
2810
+ var terraformExtractor = {
2811
+ format: "terraform",
2812
+ matches(rel2) {
2813
+ const lower = rel2.toLowerCase();
2814
+ return lower.endsWith(".tf") || lower.endsWith(".tofu");
2815
+ },
2816
+ async extract(rel2, source) {
2817
+ const parser = await toolchainParser("hcl");
2818
+ if (!parser) {
2819
+ return {
2820
+ nodes: [],
2821
+ edges: [],
2822
+ warnings: [`${rel2}: HCL grammar unavailable \u2014 no Terraform structure extracted`]
2823
+ };
2824
+ }
2825
+ const tree = parser.parse(source);
2826
+ if (!tree) return { nodes: [], edges: [], warnings: [`${rel2}: HCL parse produced no tree`] };
2827
+ const lines = new LineIndex(source);
2828
+ const nodes = [];
2829
+ const edges = [];
2830
+ const warnings = [];
2831
+ const declared = /* @__PURE__ */ new Set();
2832
+ const pendingEdges = [];
2833
+ if (tree.rootNode.hasError) {
2834
+ warnings.push(`${rel2}: HCL parse recovered from a syntax error \u2014 extraction may be partial`);
2835
+ }
2836
+ for (const block of topLevelBlocks(tree.rootNode)) {
2837
+ if (nodes.length >= TOOLCHAIN_NODES_PER_FILE_MAX) {
2838
+ warnings.push(`${rel2}: stopped at ${TOOLCHAIN_NODES_PER_FILE_MAX} nodes`);
2839
+ break;
2840
+ }
2841
+ const { type, labels } = blockHead(block);
2842
+ if (type === "terraform") {
2843
+ extractRequiredProviders(block, lines, nodes, declared);
2844
+ continue;
2845
+ }
2846
+ const spec = BLOCK_SPECS[type];
2847
+ if (!spec || labels.length < spec.labels) continue;
2848
+ const address = spec.address(labels);
2849
+ if (declared.has(address)) continue;
2850
+ declared.add(address);
2851
+ const body = blockBody(block);
2852
+ const attrs = body ? attributes(body) : [];
2853
+ const summaryParts = [];
2854
+ for (const attr of attrs) {
2855
+ if (attr.name === "source" || attr.name === "version" || attr.name === "description") {
2856
+ const value = attr.expr ? plainText(attr.expr) : "";
2857
+ if (value) summaryParts.push(`${attr.name} ${value}`);
2858
+ }
2859
+ }
2860
+ nodes.push({
2861
+ kind: spec.kind,
2862
+ name: labels[labels.length - 1] ?? type,
2863
+ qualifiedName: address,
2864
+ span: lines.span(block.startIndex, block.endIndex),
2865
+ signature: spec.signature,
2866
+ doc: safeDoc(summaryParts.join(", ")),
2867
+ importance: spec.importance
2868
+ });
2869
+ if (!body) continue;
2870
+ const references = /* @__PURE__ */ new Set();
2871
+ for (const attr of attrs) {
2872
+ referencedAddresses(attr.expr, references);
2873
+ }
2874
+ const nestedStack = nestedBlocks(body);
2875
+ while (nestedStack.length) {
2876
+ const nested = nestedStack.pop();
2877
+ if (!nested) continue;
2878
+ const innerBody = blockBody(nested);
2879
+ if (!innerBody) continue;
2880
+ for (const attr of attributes(innerBody)) referencedAddresses(attr.expr, references);
2881
+ nestedStack.push(...nestedBlocks(innerBody));
2882
+ }
2883
+ references.delete(address);
2884
+ for (const target of [...references].sort()) {
2885
+ pendingEdges.push({ kind: "depends_on", from: address, to: target, confidence: 1 });
2886
+ }
2887
+ }
2888
+ for (const edge of pendingEdges) {
2889
+ if (declared.has(edge.to)) edges.push(edge);
2890
+ }
2891
+ return { nodes, edges, warnings };
2892
+ }
2893
+ };
2894
+
2895
+ // src/engine/toolchain/workflows.ts
2896
+ var MAX_STEPS_PER_JOB = 100;
2897
+ var GITLAB_RESERVED = /* @__PURE__ */ new Set([
2898
+ "stages",
2899
+ "variables",
2900
+ "default",
2901
+ "include",
2902
+ "workflow",
2903
+ "image",
2904
+ "services",
2905
+ "before_script",
2906
+ "after_script",
2907
+ "cache",
2908
+ "pages"
2909
+ ]);
2910
+ function isGitHubWorkflowPath(rel2) {
2911
+ return /(^|\/)\.github\/workflows\/[^/]+\.ya?ml$/i.test(toPosix3(rel2));
2912
+ }
2913
+ function isGitLabCiPath(rel2) {
2914
+ const base = toPosix3(rel2).split("/").pop()?.toLowerCase() ?? "";
2915
+ return base === ".gitlab-ci.yml" || base === ".gitlab-ci.yaml";
2916
+ }
2917
+ function imagesBuiltBy(script) {
2918
+ const out = /* @__PURE__ */ new Set();
2919
+ const buildTag = /\b(?:docker|podman)\s+(?:buildx\s+)?build\b[^\n]*?(?:-t|--tag)[=\s]+(\S+)/g;
2920
+ for (const match of script.matchAll(buildTag)) out.add(match[1]);
2921
+ const push2 = /\b(?:docker|podman)\s+push\s+(\S+)/g;
2922
+ for (const match of script.matchAll(push2)) out.add(match[1]);
2923
+ return [...out].filter((tag) => !tag.includes("${") && !tag.includes("{{")).sort();
2924
+ }
2925
+ function terraformAppliedDirs(script, workingDirectory) {
2926
+ const out = /* @__PURE__ */ new Set();
2927
+ const apply = /\b(?:terraform|tofu)\s+(?:-chdir=(\S+)\s+)?(?:apply|destroy)\b/g;
2928
+ for (const match of script.matchAll(apply)) {
2929
+ out.add(match[1] ?? workingDirectory ?? ".");
2930
+ }
2931
+ return [...out].sort();
2932
+ }
2933
+ function collectSecretReferences(value, out) {
2934
+ const stack = [value];
2935
+ while (stack.length) {
2936
+ const current = stack.pop();
2937
+ if (typeof current === "string") {
2938
+ for (const match of current.matchAll(/secrets\.([A-Za-z_][A-Za-z0-9_]*)/g)) {
2939
+ out.add(`secret:${match[1]}`);
2940
+ }
2941
+ continue;
2942
+ }
2943
+ if (Array.isArray(current)) {
2944
+ stack.push(...current);
2945
+ continue;
2946
+ }
2947
+ if (current && typeof current === "object") {
2948
+ stack.push(...Object.values(current));
2949
+ }
2950
+ }
2951
+ }
2952
+ var githubActionsExtractor = {
2953
+ format: "github-actions",
2954
+ matches(rel2) {
2955
+ return isGitHubWorkflowPath(rel2);
2956
+ },
2957
+ extract(rel2, source) {
2958
+ const { docs, warnings } = parseYamlStream(rel2, source);
2959
+ const lines = new LineIndex(source);
2960
+ const nodes = [];
2961
+ const edges = [];
2962
+ const doc = docs[0];
2963
+ if (!doc) return { nodes, edges, warnings };
2964
+ const workflowName = getString(doc.value, ["name"]) ?? toPosix3(rel2).split("/").pop() ?? rel2;
2965
+ const workflowAddress = `workflow:${toPosix3(rel2)}`;
2966
+ nodes.push({
2967
+ kind: "job",
2968
+ name: workflowName,
2969
+ qualifiedName: workflowAddress,
2970
+ span: spanAt(doc, lines, []),
2971
+ signature: "gha.workflow",
2972
+ doc: safeDoc(`GitHub Actions workflow`),
2973
+ importance: 0.6
2974
+ });
2975
+ const onValue = getPath(doc.value, ["on"]) ?? getPath(doc.value, [true]);
2976
+ for (const event of triggerEvents(onValue)) {
2977
+ const eventAddress = `event:${event}`;
2978
+ nodes.push({
2979
+ kind: "property",
2980
+ name: event,
2981
+ qualifiedName: eventAddress,
2982
+ span: spanAt(doc, lines, ["on"]),
2983
+ signature: "gha.event",
2984
+ importance: 0.2
2985
+ });
2986
+ edges.push({ kind: "triggers", from: eventAddress, to: workflowAddress, confidence: 1 });
2987
+ }
2988
+ const jobs = getRecordEntries(doc.value, ["jobs"]);
2989
+ const jobAddresses = new Set(jobs.map(([id]) => `job:${id}`));
2990
+ for (const [jobId, job] of jobs) {
2991
+ if (nodes.length >= TOOLCHAIN_NODES_PER_FILE_MAX) {
2992
+ warnings.push(`${rel2}: stopped at ${TOOLCHAIN_NODES_PER_FILE_MAX} nodes`);
2993
+ break;
2994
+ }
2995
+ const jobAddress = `job:${jobId}`;
2996
+ const runsOn = getPath(job, ["runs-on"]);
2997
+ const steps = getArray(job, ["steps"]);
2998
+ nodes.push({
2999
+ kind: "job",
3000
+ name: getString(job, ["name"]) ?? jobId,
3001
+ qualifiedName: jobAddress,
3002
+ span: spanAt(doc, lines, ["jobs", jobId]),
3003
+ signature: "gha.job",
3004
+ doc: safeDoc(
3005
+ [
3006
+ typeof runsOn === "string" ? `runs-on ${runsOn}` : null,
3007
+ steps.length ? `${steps.length} step${steps.length === 1 ? "" : "s"}` : null
3008
+ ].filter(Boolean).join(", ")
3009
+ ),
3010
+ importance: 0.5
3011
+ });
3012
+ edges.push({ kind: "contains", from: workflowAddress, to: jobAddress, confidence: 1 });
3013
+ for (const need of asStringList(getPath(job, ["needs"]))) {
3014
+ const to = `job:${need}`;
3015
+ if (jobAddresses.has(to)) {
3016
+ edges.push({ kind: "depends_on", from: jobAddress, to, confidence: 1 });
3017
+ }
3018
+ }
3019
+ steps.slice(0, MAX_STEPS_PER_JOB).forEach((step, index) => {
3020
+ const uses = getString(step, ["uses"]);
3021
+ const run = getString(step, ["run"]);
3022
+ const stepName = getString(step, ["name"]) ?? uses ?? (run ? run.split("\n")[0] : `step ${index + 1}`);
3023
+ const stepAddress = `${jobAddress}#${index}`;
3024
+ nodes.push({
3025
+ kind: "step",
3026
+ name: stepName.slice(0, 80),
3027
+ qualifiedName: stepAddress,
3028
+ span: spanAt(doc, lines, ["jobs", jobId, "steps", index]),
3029
+ signature: uses ? "gha.step.uses" : "gha.step.run",
3030
+ // `run:` bodies are shell scripts and are exactly where a token gets
3031
+ // pasted — safeDoc redacts, and we keep only the first line.
3032
+ doc: safeDoc(uses ? `uses ${uses}` : run?.split("\n")[0]),
3033
+ importance: 0.2
3034
+ });
3035
+ edges.push({ kind: "contains", from: jobAddress, to: stepAddress, confidence: 1 });
3036
+ if (uses) {
3037
+ const actionAddress = `action:${uses}`;
3038
+ nodes.push({
3039
+ kind: "external",
3040
+ name: uses.split("@")[0],
3041
+ qualifiedName: actionAddress,
3042
+ span: spanAt(doc, lines, ["jobs", jobId, "steps", index, "uses"]),
3043
+ signature: "gha.action",
3044
+ importance: 0.2
3045
+ });
3046
+ edges.push({ kind: "depends_on", from: stepAddress, to: actionAddress, confidence: 1 });
3047
+ }
3048
+ if (run) {
3049
+ const stepSpan = spanAt(doc, lines, ["jobs", jobId, "steps", index]);
3050
+ const workingDirectory = getString(step, ["working-directory"]) ?? getString(job, ["defaults", "run", "working-directory"]);
3051
+ emitScriptEffects(rel2, run, workingDirectory, jobAddress, stepSpan, nodes, edges);
3052
+ }
3053
+ });
3054
+ if (steps.length > MAX_STEPS_PER_JOB) {
3055
+ warnings.push(`${rel2}: job ${jobId} has ${steps.length} steps \u2014 enumerated the first ${MAX_STEPS_PER_JOB}`);
3056
+ }
3057
+ const secrets = /* @__PURE__ */ new Set();
3058
+ collectSecretReferences(job, secrets);
3059
+ for (const secret of [...secrets].sort()) {
3060
+ nodes.push({
3061
+ kind: "property",
3062
+ name: secret.slice("secret:".length),
3063
+ qualifiedName: secret,
3064
+ span: spanAt(doc, lines, ["jobs", jobId]),
3065
+ signature: "gha.secret-ref",
3066
+ importance: 0.2
3067
+ });
3068
+ edges.push({ kind: "mounts", from: jobAddress, to: secret, confidence: 1 });
3069
+ }
3070
+ }
3071
+ return { nodes, edges, warnings };
3072
+ }
3073
+ };
3074
+ var gitlabCiExtractor = {
3075
+ format: "gitlab-ci",
3076
+ matches(rel2) {
3077
+ return isGitLabCiPath(rel2);
3078
+ },
3079
+ extract(rel2, source) {
3080
+ const { docs, warnings } = parseYamlStream(rel2, source);
3081
+ const lines = new LineIndex(source);
3082
+ const nodes = [];
3083
+ const edges = [];
3084
+ const doc = docs[0];
3085
+ if (!doc) return { nodes, edges, warnings };
3086
+ const pipelineAddress = `workflow:${toPosix3(rel2)}`;
3087
+ nodes.push({
3088
+ kind: "job",
3089
+ name: "GitLab CI",
3090
+ qualifiedName: pipelineAddress,
3091
+ span: spanAt(doc, lines, []),
3092
+ signature: "gitlab.pipeline",
3093
+ importance: 0.6
3094
+ });
3095
+ const jobs = getRecordEntries(doc.value, []).filter(
3096
+ ([key, value]) => !GITLAB_RESERVED.has(key) && !key.startsWith(".") && value !== null && typeof value === "object" && !Array.isArray(value)
3097
+ );
3098
+ const jobAddresses = new Set(jobs.map(([id]) => `job:${id}`));
3099
+ for (const [jobId, job] of jobs) {
3100
+ if (nodes.length >= TOOLCHAIN_NODES_PER_FILE_MAX) {
3101
+ warnings.push(`${rel2}: stopped at ${TOOLCHAIN_NODES_PER_FILE_MAX} nodes`);
3102
+ break;
3103
+ }
3104
+ const jobAddress = `job:${jobId}`;
3105
+ const stage = getString(job, ["stage"]);
3106
+ const image = getString(job, ["image"]) ?? getString(job, ["image", "name"]);
3107
+ nodes.push({
3108
+ kind: "job",
3109
+ name: jobId,
3110
+ qualifiedName: jobAddress,
3111
+ span: spanAt(doc, lines, [jobId]),
3112
+ signature: "gitlab.job",
3113
+ doc: safeDoc([stage && `stage ${stage}`, image && `image ${image}`].filter(Boolean).join(", ")),
3114
+ importance: 0.5
3115
+ });
3116
+ edges.push({ kind: "contains", from: pipelineAddress, to: jobAddress, confidence: 1 });
3117
+ for (const key of ["needs", "dependencies"]) {
3118
+ for (const need of asStringList(getPath(job, [key]))) {
3119
+ const to = `job:${need}`;
3120
+ if (jobAddresses.has(to)) {
3121
+ edges.push({ kind: "depends_on", from: jobAddress, to, confidence: 1 });
3122
+ }
3123
+ }
3124
+ }
3125
+ }
3126
+ return { nodes, edges, warnings };
3127
+ }
3128
+ };
3129
+ function emitScriptEffects(rel2, script, workingDirectory, jobAddress, span, nodes, edges) {
3130
+ for (const tag of imagesBuiltBy(script)) {
3131
+ const ref = parseImageRef(tag);
3132
+ if (!ref) continue;
3133
+ const address = `image:${ref.raw}`;
3134
+ nodes.push({
3135
+ kind: "image",
3136
+ name: ref.repository.split("/").pop() ?? ref.repository,
3137
+ qualifiedName: address,
3138
+ span,
3139
+ signature: "ci.builds-image",
3140
+ doc: safeDoc(ref.tag ? `${ref.repository} tag ${ref.tag}` : ref.repository),
3141
+ importance: 0.4
3142
+ });
3143
+ edges.push({ kind: "provisions", from: jobAddress, to: address, confidence: 1 });
3144
+ }
3145
+ for (const dir of terraformAppliedDirs(script, workingDirectory)) {
3146
+ const resolved = normaliseWorkspacePath(dir);
3147
+ const address = `tfdir:${resolved}`;
3148
+ nodes.push({
3149
+ kind: "resource",
3150
+ name: resolved,
3151
+ qualifiedName: address,
3152
+ span,
3153
+ signature: "ci.terraform-apply",
3154
+ doc: safeDoc(`terraform apply in ${resolved}`),
3155
+ importance: 0.4
3156
+ });
3157
+ edges.push({ kind: "deploys", from: jobAddress, to: address, confidence: 1 });
3158
+ }
3159
+ }
3160
+ function normaliseWorkspacePath(dir) {
3161
+ const out = [];
3162
+ for (const part of toPosix3(dir).split("/")) {
3163
+ if (!part || part === ".") continue;
3164
+ if (part === "..") {
3165
+ out.pop();
3166
+ continue;
3167
+ }
3168
+ out.push(part);
3169
+ }
3170
+ return out.join("/") || ".";
3171
+ }
3172
+ function triggerEvents(value) {
3173
+ if (typeof value === "string") return [value];
3174
+ if (Array.isArray(value)) return value.filter((v) => typeof v === "string").sort();
3175
+ if (value && typeof value === "object") return Object.keys(value).sort();
3176
+ return [];
3177
+ }
3178
+ function asStringList(value) {
3179
+ if (typeof value === "string") return [value];
3180
+ if (!Array.isArray(value)) return [];
3181
+ const out = [];
3182
+ for (const item of value) {
3183
+ if (typeof item === "string") out.push(item);
3184
+ else if (item && typeof item === "object") {
3185
+ const job = item.job;
3186
+ if (typeof job === "string") out.push(job);
3187
+ }
3188
+ }
3189
+ return out.sort();
3190
+ }
3191
+
3192
+ // src/engine/toolchain/link.ts
3193
+ var LINK_CONFIDENCE = {
3194
+ /** Compose names the Dockerfile path outright. */
3195
+ composeBuild: 0.95,
3196
+ /** A CI job builds tag X; a workload runs tag X. Same string, both sides. */
3197
+ imageIdentity: 0.8,
3198
+ /** A CI job applies Terraform in a directory that contains these resources. */
3199
+ terraformDir: 0.75,
3200
+ /** A Dockerfile copies a path that exists in the repository. */
3201
+ dockerfileCopy: 0.6
3202
+ };
3203
+ function linkToolchain(input) {
3204
+ const { nodes, edges, fileNodes, stageCopySources } = input;
3205
+ const out = /* @__PURE__ */ new Map();
3206
+ const counts = {};
3207
+ const add3 = (kind, src, dst, confidence, join10) => {
3208
+ if (src === dst) return;
3209
+ const id = edgeId(kind, src, dst);
3210
+ if (out.has(id)) return;
3211
+ out.set(id, {
3212
+ id,
3213
+ kind,
3214
+ src,
3215
+ dst,
3216
+ resolution: "heuristic",
3217
+ confidence,
3218
+ epistemic: "name-matched"
3219
+ });
3220
+ counts[join10] = (counts[join10] ?? 0) + 1;
3221
+ };
3222
+ const declared = new Set(edges.map((e) => `${e.kind} ${e.src} ${e.dst}`));
3223
+ const dockerfileByPath = /* @__PURE__ */ new Map();
3224
+ const dockerfileRefs = [];
3225
+ const imageSites = /* @__PURE__ */ new Map();
3226
+ const terraformDirMarkers = [];
3227
+ const terraformNodesByDir = /* @__PURE__ */ new Map();
3228
+ const stagesByDockerfile = /* @__PURE__ */ new Map();
3229
+ for (const node of nodes) {
3230
+ const qn = node.qualifiedName;
3231
+ if (qn.startsWith("dockerfile:")) {
3232
+ const target = qn.slice("dockerfile:".length);
3233
+ if (target.includes("#")) {
3234
+ push(stagesByDockerfile, target.split("#")[0], node);
3235
+ continue;
3236
+ }
3237
+ if (toPosix3(node.file) === target) dockerfileByPath.set(target, node);
3238
+ else dockerfileRefs.push(node);
3239
+ continue;
3240
+ }
3241
+ if (qn.startsWith("image:")) {
3242
+ const ref = parseImageRef(qn.slice("image:".length));
3243
+ if (ref) push(imageSites, imageIdentity(ref), node);
3244
+ continue;
3245
+ }
3246
+ if (qn.startsWith("tfdir:")) {
3247
+ terraformDirMarkers.push(node);
3248
+ continue;
3249
+ }
3250
+ if (node.lang === "terraform") push(terraformNodesByDir, dirOf(toPosix3(node.file)), node);
3251
+ }
3252
+ for (const ref of dockerfileRefs) {
3253
+ const target = dockerfileByPath.get(ref.qualifiedName.slice("dockerfile:".length));
3254
+ if (!target) continue;
3255
+ add3("builds_from", ref.id, target.id, LINK_CONFIDENCE.composeBuild, "compose\u2192dockerfile");
3256
+ }
3257
+ const producerOf = /* @__PURE__ */ new Map();
3258
+ const consumerOf = /* @__PURE__ */ new Map();
3259
+ for (const edge of edges) {
3260
+ if (edge.kind === "provisions") push(producerOf, edge.dst, edge.src);
3261
+ else if (edge.kind === "builds_from") push(consumerOf, edge.dst, edge.src);
3262
+ }
3263
+ for (const identity of [...imageSites.keys()].sort((a, b) => a.localeCompare(b, "en"))) {
3264
+ const sites = imageSites.get(identity) ?? [];
3265
+ if (sites.length < 2) continue;
3266
+ const producers = /* @__PURE__ */ new Set();
3267
+ const consumers = /* @__PURE__ */ new Set();
3268
+ for (const site of sites) {
3269
+ for (const id of producerOf.get(site.id) ?? []) producers.add(id);
3270
+ for (const id of consumerOf.get(site.id) ?? []) consumers.add(id);
3271
+ }
3272
+ for (const producer of [...producers].sort()) {
3273
+ for (const consumer of [...consumers].sort()) {
3274
+ if (declared.has(`deploys ${producer} ${consumer}`)) continue;
3275
+ add3("deploys", producer, consumer, LINK_CONFIDENCE.imageIdentity, "ci\u2192workload");
3276
+ }
3277
+ }
3278
+ const ordered = [...sites].sort((a, b) => a.id.localeCompare(b.id, "en"));
3279
+ for (let i = 1; i < ordered.length; i++) {
3280
+ add3("references", ordered[0].id, ordered[i].id, LINK_CONFIDENCE.imageIdentity, "image\u2194image");
3281
+ }
3282
+ }
3283
+ const terraformDirs = [...terraformNodesByDir.keys()].sort((a, b) => a.localeCompare(b, "en"));
3284
+ for (const marker of [...terraformDirMarkers].sort((a, b) => a.id.localeCompare(b.id, "en"))) {
3285
+ const dir = marker.qualifiedName.slice("tfdir:".length);
3286
+ for (const resourceDir of terraformDirs) {
3287
+ if (resourceDir !== dir && !resourceDir.startsWith(`${dir}/`)) continue;
3288
+ for (const resource of terraformNodesByDir.get(resourceDir) ?? []) {
3289
+ if (resource.signature !== "terraform.resource" && resource.signature !== "terraform.module") {
3290
+ continue;
3291
+ }
3292
+ add3("deploys", marker.id, resource.id, LINK_CONFIDENCE.terraformDir, "ci\u2192terraform");
3293
+ }
3294
+ }
3295
+ }
3296
+ for (const file of [...stagesByDockerfile.keys()].sort((a, b) => a.localeCompare(b, "en"))) {
3297
+ const stages = [...stagesByDockerfile.get(file) ?? []].sort(
3298
+ (a, b) => a.qualifiedName.localeCompare(b.qualifiedName, "en")
3299
+ );
3300
+ for (const stage of stages) {
3301
+ const copied = stageCopySources.get(stage.qualifiedName);
3302
+ if (!copied?.length) continue;
3303
+ for (const path12 of copied) {
3304
+ const fileNodeId = fileNodes.get(toPosix3(path12));
3305
+ if (!fileNodeId) continue;
3306
+ add3("depends_on", stage.id, fileNodeId, LINK_CONFIDENCE.dockerfileCopy, "dockerfile\u2192source");
3307
+ }
3308
+ }
3309
+ }
3310
+ return {
3311
+ edges: [...out.values()].sort(
3312
+ (a, b) => a.kind.localeCompare(b.kind, "en") || a.src.localeCompare(b.src, "en") || a.dst.localeCompare(b.dst, "en")
3313
+ ),
3314
+ counts
3315
+ };
3316
+ }
3317
+ function push(map, key, value) {
3318
+ const list = map.get(key);
3319
+ if (list) list.push(value);
3320
+ else map.set(key, [value]);
3321
+ }
3322
+ function dirOf(rel2) {
3323
+ const idx = rel2.lastIndexOf("/");
3324
+ return idx === -1 ? "." : rel2.slice(0, idx);
3325
+ }
3326
+
3327
+ // src/engine/toolchain/index.ts
3328
+ var EXTRACTORS = [
3329
+ terraformExtractor,
3330
+ dockerfileExtractor,
3331
+ githubActionsExtractor,
3332
+ gitlabCiExtractor,
3333
+ helmExtractor,
3334
+ composeExtractor,
3335
+ kubernetesExtractor
3336
+ ];
3337
+ var HEAD_BYTES = 4096;
3338
+ var EMPTY_RESULT = {
3339
+ nodes: [],
3340
+ edges: [],
3341
+ warnings: [],
3342
+ counts: {},
3343
+ linkCounts: {}
3344
+ };
3345
+ function extractorFor(doc, head) {
3346
+ for (const extractor of EXTRACTORS) {
3347
+ try {
3348
+ if (extractor.matches(toPosix3(doc.rel), doc.category ?? doc.kind, head)) return extractor;
3349
+ } catch {
3350
+ }
3351
+ }
3352
+ return null;
3353
+ }
3354
+ async function extractToolchain(docs, options = {}) {
3355
+ if (!docs.length) return EMPTY_RESULT;
3356
+ const nodes = /* @__PURE__ */ new Map();
3357
+ const edges = /* @__PURE__ */ new Map();
3358
+ const warnings = [];
3359
+ const counts = {};
3360
+ const stageCopySources = /* @__PURE__ */ new Map();
3361
+ const ordered = [...docs].sort((a, b) => toPosix3(a.rel).localeCompare(toPosix3(b.rel), "en"));
3362
+ for (const doc of ordered) {
3363
+ let source;
3364
+ let head;
3365
+ try {
3366
+ const stat = fs12.statSync(doc.abs);
3367
+ if (stat.size > TOOLCHAIN_FILE_MAX_BYTES) continue;
3368
+ source = fs12.readFileSync(doc.abs, "utf8");
3369
+ head = source.slice(0, HEAD_BYTES);
3370
+ } catch {
3371
+ continue;
3372
+ }
3373
+ const extractor = extractorFor(doc, head);
3374
+ if (!extractor) continue;
3375
+ const rel2 = toPosix3(doc.rel);
3376
+ let extraction;
3377
+ try {
3378
+ extraction = await extractor.extract(rel2, source);
3379
+ } catch (err) {
3380
+ warnings.push(`${rel2}: ${extractor.format} extraction failed (${err.message})`);
3381
+ continue;
3382
+ }
3383
+ if (extraction.warnings?.length) warnings.push(...extraction.warnings);
3384
+ for (const [stage, paths] of Object.entries(extraction.linkHints?.copySourcesByStage ?? {})) {
3385
+ if (paths.length) stageCopySources.set(stage, paths);
3386
+ }
3387
+ if (!extraction.nodes.length) continue;
3388
+ counts[extractor.format] = (counts[extractor.format] ?? 0) + 1;
3389
+ const idByQualifiedName = /* @__PURE__ */ new Map();
3390
+ for (const draft of extraction.nodes) {
3391
+ const id = nodeId({
3392
+ kind: draft.kind,
3393
+ qualifiedName: draft.qualifiedName,
3394
+ file: rel2,
3395
+ signature: draft.signature
3396
+ });
3397
+ if (!idByQualifiedName.has(draft.qualifiedName)) {
3398
+ idByQualifiedName.set(draft.qualifiedName, id);
3399
+ }
3400
+ if (nodes.has(id)) continue;
3401
+ nodes.set(id, toGraphNode(id, draft, rel2, extractor.format));
3402
+ }
3403
+ for (const draft of extraction.edges) {
3404
+ const src = idByQualifiedName.get(draft.from);
3405
+ const dst = idByQualifiedName.get(draft.to);
3406
+ if (!src || !dst || src === dst) continue;
3407
+ const id = edgeId(draft.kind, src, dst);
3408
+ if (edges.has(id)) continue;
3409
+ edges.set(id, {
3410
+ id,
3411
+ kind: draft.kind,
3412
+ src,
3413
+ dst,
3414
+ // Structural extraction from a declaration in the file itself.
3415
+ resolution: "heuristic",
3416
+ confidence: draft.confidence ?? 1,
3417
+ epistemic: "declared"
3418
+ });
3419
+ }
3420
+ }
3421
+ const sortedNodes = [...nodes.values()].sort((a, b) => a.id.localeCompare(b.id, "en"));
3422
+ const sortedEdges = [...edges.values()].sort(byEdgeOrder);
3423
+ const linked = linkToolchain({
3424
+ nodes: sortedNodes,
3425
+ edges: sortedEdges,
3426
+ fileNodes: options.fileNodes ?? /* @__PURE__ */ new Map(),
3427
+ stageCopySources
3428
+ });
3429
+ const declaredIds = new Set(sortedEdges.map((e) => e.id));
3430
+ const allEdges = [...sortedEdges, ...linked.edges.filter((e) => !declaredIds.has(e.id))].sort(
3431
+ byEdgeOrder
3432
+ );
3433
+ return {
3434
+ nodes: sortedNodes,
3435
+ edges: allEdges,
3436
+ warnings: [...new Set(warnings)].sort(),
3437
+ counts,
3438
+ linkCounts: linked.counts
3439
+ };
3440
+ }
3441
+ function byEdgeOrder(a, b) {
3442
+ return a.kind.localeCompare(b.kind, "en") || a.src.localeCompare(b.src, "en") || a.dst.localeCompare(b.dst, "en");
3443
+ }
3444
+ function toGraphNode(id, draft, rel2, format) {
3445
+ return {
3446
+ id,
3447
+ kind: draft.kind,
3448
+ name: draft.name,
3449
+ qualifiedName: draft.qualifiedName,
3450
+ file: rel2,
3451
+ span: draft.span,
3452
+ // `lang` is the format tag, so `--only` and the report can filter on it
3453
+ // without colliding with a source language id.
3454
+ lang: format,
3455
+ signature: draft.signature,
3456
+ doc: safeDoc(draft.doc),
3457
+ importance: draft.importance ?? 0.3,
3458
+ centrality: { degree: 0, pagerank: 0, betweenness: 0, eigenvector: 0 },
3459
+ area: -1,
3460
+ isHub: false,
3461
+ // Infrastructure has no notion of unit-test coverage; `null` is the
3462
+ // schema's "not analyzable", which is the honest answer.
3463
+ tested: null
3464
+ };
3465
+ }
1829
3466
  function shardTsProjects(root, files) {
1830
3467
  if (files.length === 0) return [];
1831
3468
  const cache = /* @__PURE__ */ new Map();
@@ -1848,8 +3485,8 @@ function nearestProjectKey(root, startDir, cache) {
1848
3485
  let dir = startDir;
1849
3486
  const rootAbs = path11.resolve(root);
1850
3487
  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)) || ".";
3488
+ if (fs12.existsSync(path11.join(dir, "tsconfig.json")) || fs12.existsSync(path11.join(dir, "tsconfig.base.json")) || fs12.existsSync(path11.join(dir, "package.json"))) {
3489
+ const key = toPosix4(path11.relative(rootAbs, dir)) || ".";
1853
3490
  cache.set(startDir, key);
1854
3491
  return key;
1855
3492
  }
@@ -1860,7 +3497,7 @@ function nearestProjectKey(root, startDir, cache) {
1860
3497
  dir = path11.dirname(dir);
1861
3498
  }
1862
3499
  }
1863
- function toPosix3(p) {
3500
+ function toPosix4(p) {
1864
3501
  return p.split(path11.sep).join("/");
1865
3502
  }
1866
3503
 
@@ -1955,7 +3592,7 @@ var emptyNode = (partial) => ({
1955
3592
  function extractManifests(root, opts = {}) {
1956
3593
  const absRoot = path11.resolve(root);
1957
3594
  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));
3595
+ const scopes = (opts.paths?.length ? opts.paths : ["."]).map((p) => path11.resolve(absRoot, p)).filter((p) => fs12.existsSync(p));
1959
3596
  const found = /* @__PURE__ */ new Map();
1960
3597
  for (const scope of scopes) {
1961
3598
  walkManifests(absRoot, scope, ig, found);
@@ -1985,7 +3622,7 @@ function extractManifests(root, opts = {}) {
1985
3622
  };
1986
3623
  }
1987
3624
  function ingestPackageJson(rel2, abs, nodes, edges) {
1988
- const raw = JSON.parse(fs11.readFileSync(abs, "utf8"));
3625
+ const raw = JSON.parse(fs12.readFileSync(abs, "utf8"));
1989
3626
  const pkgName = raw.name && String(raw.name).trim() || path11.posix.dirname(rel2) || ".";
1990
3627
  const localId = nodeId({ kind: "package", qualifiedName: pkgName, file: rel2 });
1991
3628
  nodes.set(
@@ -2029,7 +3666,7 @@ function ingestPackageJson(rel2, abs, nodes, edges) {
2029
3666
  return n;
2030
3667
  }
2031
3668
  function ingestGoMod(rel2, abs, nodes, edges) {
2032
- const text = fs11.readFileSync(abs, "utf8");
3669
+ const text = fs12.readFileSync(abs, "utf8");
2033
3670
  let moduleName = path11.posix.dirname(rel2) || ".";
2034
3671
  const moduleMatch = /^\s*module\s+(\S+)/m.exec(text);
2035
3672
  if (moduleMatch) moduleName = moduleMatch[1];
@@ -2113,9 +3750,9 @@ function addEdge(edges, kind, src, dst, confidence) {
2113
3750
  function buildRootIgnore3(root, exclude) {
2114
3751
  const ig = ignore();
2115
3752
  const gitignorePath = path11.join(root, ".gitignore");
2116
- if (fs11.existsSync(gitignorePath)) {
3753
+ if (fs12.existsSync(gitignorePath)) {
2117
3754
  try {
2118
- ig.add(fs11.readFileSync(gitignorePath, "utf8"));
3755
+ ig.add(fs12.readFileSync(gitignorePath, "utf8"));
2119
3756
  } catch {
2120
3757
  }
2121
3758
  }
@@ -2125,7 +3762,7 @@ function buildRootIgnore3(root, exclude) {
2125
3762
  function walkManifests(root, dir, ig, found) {
2126
3763
  let entries;
2127
3764
  try {
2128
- entries = fs11.readdirSync(dir, { withFileTypes: true });
3765
+ entries = fs12.readdirSync(dir, { withFileTypes: true });
2129
3766
  } catch {
2130
3767
  return;
2131
3768
  }
@@ -2158,7 +3795,7 @@ async function hashFilesParallel(jobs, concurrency) {
2158
3795
  if (i >= jobs.length) return;
2159
3796
  const job = jobs[i];
2160
3797
  try {
2161
- const buf = await fs8.readFile(job.abs);
3798
+ const buf = await fs9.readFile(job.abs);
2162
3799
  out[i] = { rel: job.rel, hash: hashBytes(buf), ok: true };
2163
3800
  } catch {
2164
3801
  out[i] = { rel: job.rel, hash: "", ok: false };
@@ -2189,7 +3826,7 @@ async function buildGraph(options) {
2189
3826
  );
2190
3827
  }
2191
3828
  checkMemoryBudget("discovery", limits.memoryBudgetMb);
2192
- const grammars = grammarSetVersion();
3829
+ const grammars = `${grammarSetVersion()}+${toolchainGrammarSetVersion()}`;
2193
3830
  const cache = loadCache(root, {
2194
3831
  toolVersion: VERSION,
2195
3832
  grammars,
@@ -2206,7 +3843,7 @@ async function buildGraph(options) {
2206
3843
  for (const file of files) {
2207
3844
  let stat;
2208
3845
  try {
2209
- stat = fs11.statSync(file.abs);
3846
+ stat = fs12.statSync(file.abs);
2210
3847
  } catch {
2211
3848
  continue;
2212
3849
  }
@@ -2412,8 +4049,8 @@ async function buildGraph(options) {
2412
4049
  });
2413
4050
  for (const d of docs) {
2414
4051
  try {
2415
- const st = fs11.statSync(d.abs);
2416
- const hash = hashBytes(fs11.readFileSync(d.abs));
4052
+ const st = fs12.statSync(d.abs);
4053
+ const hash = hashBytes(fs12.readFileSync(d.abs));
2417
4054
  hashes.set(d.rel, hash);
2418
4055
  fileStats.push({ rel: d.rel, size: st.size, mtimeMs: st.mtimeMs, hash });
2419
4056
  } catch {
@@ -2421,14 +4058,29 @@ async function buildGraph(options) {
2421
4058
  }
2422
4059
  const docNodes = documentNodesFromDocs(docs);
2423
4060
  if (docNodes.length) nodes = [...nodes, ...docNodes];
4061
+ timer.start("toolchain");
4062
+ const fileNodesByPath = /* @__PURE__ */ new Map();
4063
+ for (const node of nodes) {
4064
+ if (node.kind === "file") fileNodesByPath.set(node.file.replace(/\\/g, "/"), node.id);
4065
+ }
4066
+ const toolchainResult = await extractToolchain(docs, { fileNodes: fileNodesByPath });
4067
+ if (toolchainResult.nodes.length) nodes = [...nodes, ...toolchainResult.nodes];
4068
+ timer.end("toolchain");
2424
4069
  timer.start("analyze");
2425
- const analysis = analyze(nodes, linked.edges, {
4070
+ const graphEdges = toolchainResult.edges.length ? [...linked.edges, ...toolchainResult.edges] : linked.edges;
4071
+ const analysis = analyze(nodes, graphEdges, {
2426
4072
  cluster: options.cluster,
2427
4073
  tier: options.analysisTier
2428
4074
  });
2429
4075
  timer.end("analyze");
2430
4076
  checkMemoryBudget("analysis", limits.memoryBudgetMb);
2431
- const languages = [.../* @__PURE__ */ new Set([...parses.map((p) => p.lang), ...docNodes.map((n) => n.lang)])].sort();
4077
+ const languages = [
4078
+ .../* @__PURE__ */ new Set([
4079
+ ...parses.map((p) => p.lang),
4080
+ ...docNodes.map((n) => n.lang),
4081
+ ...toolchainResult.nodes.map((n) => n.lang)
4082
+ ])
4083
+ ].sort();
2432
4084
  const edgeKinds = [...new Set(analysis.edges.map((e) => e.kind))].sort();
2433
4085
  const corpusHash = computeCorpusHash(parses, hashes);
2434
4086
  const nodeKindById = new Map(analysis.nodes.map((n) => [n.id, n.kind]));
@@ -2504,7 +4156,8 @@ async function buildGraph(options) {
2504
4156
  tsc: tscStats,
2505
4157
  scip: scipStats,
2506
4158
  index: indexResult,
2507
- warnings
4159
+ warnings,
4160
+ fileRoles: fileRolesFromParses(parses)
2508
4161
  };
2509
4162
  }
2510
4163
  function toRepoRel(p) {
@@ -2518,9 +4171,9 @@ function loadScipIndex(root, explicit) {
2518
4171
  ].filter((p) => Boolean(p));
2519
4172
  for (const file of candidates) {
2520
4173
  const abs = path11.isAbsolute(file) ? file : path11.resolve(root, file);
2521
- if (!fs11.existsSync(abs)) continue;
4174
+ if (!fs12.existsSync(abs)) continue;
2522
4175
  try {
2523
- const index = decodeScipIndex(new Uint8Array(fs11.readFileSync(abs)));
4176
+ const index = decodeScipIndex(new Uint8Array(fs12.readFileSync(abs)));
2524
4177
  if (index.documents.length) {
2525
4178
  return { index, tool: index.toolVersion ? `${index.toolName} ${index.toolVersion}` : index.toolName };
2526
4179
  }
@@ -2612,36 +4265,6 @@ function diffHint(x, y) {
2612
4265
  }
2613
4266
  return "outputs differ in length";
2614
4267
  }
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
4268
  var SNAPSHOT_VERSION = "vg-freshness/1";
2646
4269
  function snapshotPath(root) {
2647
4270
  return path11.join(cacheDir(root), "freshness.json");
@@ -2651,14 +4274,14 @@ function writeSnapshot(root, corpusHash, fileStats, scope = {}) {
2651
4274
  for (const f of fileStats) files[f.rel] = { size: f.size, mtimeMs: f.mtimeMs, hash: f.hash };
2652
4275
  const snapshot = { version: SNAPSHOT_VERSION, corpusHash, scope: pruneScope(scope), files };
2653
4276
  try {
2654
- fs11.mkdirSync(cacheDir(root), { recursive: true });
2655
- fs11.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
4277
+ fs12.mkdirSync(cacheDir(root), { recursive: true });
4278
+ fs12.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
2656
4279
  } catch {
2657
4280
  }
2658
4281
  }
2659
4282
  function loadSnapshot(root) {
2660
4283
  try {
2661
- const loaded = JSON.parse(fs11.readFileSync(snapshotPath(root), "utf8"));
4284
+ const loaded = JSON.parse(fs12.readFileSync(snapshotPath(root), "utf8"));
2662
4285
  if (loaded.version === SNAPSHOT_VERSION && loaded.files && typeof loaded.corpusHash === "string") {
2663
4286
  return { ...loaded, scope: loaded.scope ?? {} };
2664
4287
  }
@@ -2701,7 +4324,7 @@ function probeFreshness(root) {
2701
4324
  }
2702
4325
  let stat;
2703
4326
  try {
2704
- stat = fs11.statSync(file.abs);
4327
+ stat = fs12.statSync(file.abs);
2705
4328
  } catch {
2706
4329
  drift.removed.push(file.rel);
2707
4330
  continue;
@@ -2719,7 +4342,7 @@ function probeFreshness(root) {
2719
4342
  }
2720
4343
  let hash = "";
2721
4344
  try {
2722
- hash = hashBytes(fs11.readFileSync(file.abs));
4345
+ hash = hashBytes(fs12.readFileSync(file.abs));
2723
4346
  } catch {
2724
4347
  drift.removed.push(file.rel);
2725
4348
  continue;
@@ -2736,7 +4359,7 @@ function probeFreshness(root) {
2736
4359
  }
2737
4360
  if (absorbed && !hasDrift(drift)) {
2738
4361
  try {
2739
- fs11.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
4362
+ fs12.writeFileSync(snapshotPath(root), stableStringify(snapshot, 0));
2740
4363
  } catch {
2741
4364
  }
2742
4365
  }
@@ -2981,12 +4604,12 @@ function detectAssistants(root, opts = {}) {
2981
4604
  const which = opts.which ?? whichOnPath;
2982
4605
  const found = [];
2983
4606
  for (const assistant of ASSISTANTS) {
2984
- const repoHit = (assistant.markers ?? []).find((m) => fs11.existsSync(path11.join(root, m)));
4607
+ const repoHit = (assistant.markers ?? []).find((m) => fs12.existsSync(path11.join(root, m)));
2985
4608
  if (repoHit) {
2986
4609
  found.push({ assistant, via: "repo", marker: repoHit });
2987
4610
  continue;
2988
4611
  }
2989
- const homeHit = (assistant.homeMarkers ?? []).find((m) => fs11.existsSync(path11.join(home, m)));
4612
+ const homeHit = (assistant.homeMarkers ?? []).find((m) => fs12.existsSync(path11.join(home, m)));
2990
4613
  if (homeHit) {
2991
4614
  found.push({ assistant, via: "home", marker: homeHit });
2992
4615
  continue;
@@ -3047,27 +4670,27 @@ function uninstallAssistant(a, root, purge) {
3047
4670
  const file = path11.join(root, a.nudge.file);
3048
4671
  if (a.nudge.kind === "block") {
3049
4672
  if (removeBlock(file)) removed.push(a.nudge.file);
3050
- } else if (fs11.existsSync(file)) {
3051
- fs11.rmSync(file);
4673
+ } else if (fs12.existsSync(file)) {
4674
+ fs12.rmSync(file);
3052
4675
  removed.push(a.nudge.file);
3053
4676
  }
3054
4677
  }
3055
4678
  if (purge && a.skill) {
3056
4679
  const file = path11.join(root, a.skill);
3057
- if (fs11.existsSync(file)) {
3058
- fs11.rmSync(file);
4680
+ if (fs12.existsSync(file)) {
4681
+ fs12.rmSync(file);
3059
4682
  removed.push(a.skill);
3060
4683
  }
3061
4684
  }
3062
4685
  return removed;
3063
4686
  }
3064
4687
  function isAssistantInstalled(a, root) {
3065
- if (a.skill && fs11.existsSync(path11.join(root, a.skill))) return true;
4688
+ if (a.skill && fs12.existsSync(path11.join(root, a.skill))) return true;
3066
4689
  if (a.mcp) {
3067
4690
  const file = path11.join(root, a.mcp.file);
3068
4691
  if (a.mcp.format === "toml") {
3069
4692
  if (hasTomlMcp(file, a.mcp)) return true;
3070
- } else if (fs11.existsSync(file)) {
4693
+ } else if (fs12.existsSync(file)) {
3071
4694
  try {
3072
4695
  const config = readJson(file);
3073
4696
  const bag = config[a.mcp.key];
@@ -3078,7 +4701,7 @@ function isAssistantInstalled(a, root) {
3078
4701
  }
3079
4702
  if (a.nudge) {
3080
4703
  const file = path11.join(root, a.nudge.file);
3081
- if (!fs11.existsSync(file)) return false;
4704
+ if (!fs12.existsSync(file)) return false;
3082
4705
  const text = readTextSafe(file);
3083
4706
  if (!text) return false;
3084
4707
  if (a.nudge.kind === "block") return text.includes(NUDGE_BEGIN);
@@ -3087,8 +4710,8 @@ function isAssistantInstalled(a, root) {
3087
4710
  return false;
3088
4711
  }
3089
4712
  function writeFileEnsured(file, content) {
3090
- fs11.mkdirSync(path11.dirname(file), { recursive: true });
3091
- fs11.writeFileSync(file, content);
4713
+ fs12.mkdirSync(path11.dirname(file), { recursive: true });
4714
+ fs12.writeFileSync(file, content);
3092
4715
  }
3093
4716
  function writeNavigationConfig(root) {
3094
4717
  ensureVibgrateGitignore(root);
@@ -3129,7 +4752,7 @@ function upsertTomlMcp(file, target, launch) {
3129
4752
  command = ${tomlString(entry.command)}
3130
4753
  args = [${entry.args.map(tomlString).join(", ")}]
3131
4754
  `;
3132
- const existing = fs11.existsSync(file) ? fs11.readFileSync(file, "utf8") : "";
4755
+ const existing = fs12.existsSync(file) ? fs12.readFileSync(file, "utf8") : "";
3133
4756
  const re = tomlTableRe(target.key);
3134
4757
  const next = re.test(existing) ? existing.replace(re, (m) => m.startsWith("\n") ? `
3135
4758
  ${table}` : table) : existing.length ? `${existing.replace(/\s*$/, "")}
@@ -3138,7 +4761,7 @@ ${table}` : table;
3138
4761
  writeFileEnsured(file, next);
3139
4762
  }
3140
4763
  function removeTomlMcp(file, target) {
3141
- if (!fs11.existsSync(file)) return false;
4764
+ if (!fs12.existsSync(file)) return false;
3142
4765
  const existing = readTextSafe(file);
3143
4766
  if (existing === null) return false;
3144
4767
  const re = tomlTableRe(target.key);
@@ -3148,13 +4771,13 @@ function removeTomlMcp(file, target) {
3148
4771
  return true;
3149
4772
  }
3150
4773
  function hasTomlMcp(file, target) {
3151
- if (!fs11.existsSync(file)) return false;
4774
+ if (!fs12.existsSync(file)) return false;
3152
4775
  const text = readTextSafe(file);
3153
4776
  return text !== null && tomlTableRe(target.key).test(text);
3154
4777
  }
3155
4778
  function removeMcp(file, target) {
3156
4779
  if (target.format === "toml") return removeTomlMcp(file, target);
3157
- if (!fs11.existsSync(file)) return false;
4780
+ if (!fs12.existsSync(file)) return false;
3158
4781
  const config = readJson(file);
3159
4782
  const bag = config[target.key];
3160
4783
  if (!bag || !(bag.vg !== void 0)) return false;
@@ -3174,7 +4797,7 @@ function refreshInstalledInstructions(root, smallRepo) {
3174
4797
  for (const a of ASSISTANTS) {
3175
4798
  if (a.skill) {
3176
4799
  const file = path11.join(root, a.skill);
3177
- if (fs11.existsSync(file)) {
4800
+ if (fs12.existsSync(file)) {
3178
4801
  const text = readTextSafe(file);
3179
4802
  const from = text === null ? null : staleVersion(text, "# vg \u2014 the code map");
3180
4803
  if (from !== null) {
@@ -3185,7 +4808,7 @@ function refreshInstalledInstructions(root, smallRepo) {
3185
4808
  }
3186
4809
  if (a.nudge) {
3187
4810
  const file = path11.join(root, a.nudge.file);
3188
- if (!fs11.existsSync(file)) continue;
4811
+ if (!fs12.existsSync(file)) continue;
3189
4812
  const text = readTextSafe(file);
3190
4813
  if (text === null) continue;
3191
4814
  if (a.nudge.kind === "block") {
@@ -3209,7 +4832,7 @@ function refreshInstalledInstructions(root, smallRepo) {
3209
4832
  }
3210
4833
  function readTextSafe(file) {
3211
4834
  try {
3212
- return fs11.readFileSync(file, "utf8");
4835
+ return fs12.readFileSync(file, "utf8");
3213
4836
  } catch {
3214
4837
  return null;
3215
4838
  }
@@ -3228,8 +4851,8 @@ ${stripMarkers(nudgeMarkdown(smallRepo, client))}` : stripMarkers(nudgeMarkdown(
3228
4851
  upsertBlock(file, nudgeMarkdown(smallRepo, client));
3229
4852
  }
3230
4853
  function upsertBlock(file, block) {
3231
- fs11.mkdirSync(path11.dirname(file), { recursive: true });
3232
- let existing = fs11.existsSync(file) ? fs11.readFileSync(file, "utf8") : "";
4854
+ fs12.mkdirSync(path11.dirname(file), { recursive: true });
4855
+ let existing = fs12.existsSync(file) ? fs12.readFileSync(file, "utf8") : "";
3233
4856
  const re = new RegExp(`${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}`);
3234
4857
  if (re.test(existing)) existing = existing.replace(re, block);
3235
4858
  else existing = existing.length ? `${existing.replace(/\s*$/, "")}
@@ -3237,20 +4860,20 @@ function upsertBlock(file, block) {
3237
4860
  ${block}
3238
4861
  ` : `${block}
3239
4862
  `;
3240
- fs11.writeFileSync(file, existing);
4863
+ fs12.writeFileSync(file, existing);
3241
4864
  }
3242
4865
  function removeBlock(file) {
3243
- if (!fs11.existsSync(file)) return false;
3244
- const existing = fs11.readFileSync(file, "utf8");
4866
+ if (!fs12.existsSync(file)) return false;
4867
+ const existing = fs12.readFileSync(file, "utf8");
3245
4868
  const re = new RegExp(`\\n*${escapeRe(NUDGE_BEGIN)}[\\s\\S]*?${escapeRe(NUDGE_END)}\\n*`);
3246
4869
  if (!re.test(existing)) return false;
3247
- fs11.writeFileSync(file, existing.replace(re, "\n"));
4870
+ fs12.writeFileSync(file, existing.replace(re, "\n"));
3248
4871
  return true;
3249
4872
  }
3250
4873
  function readJson(file) {
3251
- if (!fs11.existsSync(file)) return {};
4874
+ if (!fs12.existsSync(file)) return {};
3252
4875
  try {
3253
- const parsed = JSON.parse(fs11.readFileSync(file, "utf8"));
4876
+ const parsed = JSON.parse(fs12.readFileSync(file, "utf8"));
3254
4877
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
3255
4878
  return parsed;
3256
4879
  }
@@ -3269,6 +4892,6 @@ function escapeRe(s) {
3269
4892
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3270
4893
  }
3271
4894
 
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
4895
+ export { ASSISTANTS, ResourceLimitError, SKIP_DIRS, SKIP_FILES, SMALL_REPO_FILES, UsageError, analyze, applyCoverage, assistantById, buildFacts, buildGraph, decodeScipIndex, detectAssistants, detectServeLaunch, discover, driftCount, epistemicBreakdown, groundGraph, hasDrift, installAssistant, isAssistantInstalled, isSkippedDirName, loadCoverage, loadSnapshot, probeFreshness, refreshInstalledInstructions, resolveLimits, scipEdges, uninstallAssistant, verifyDeterminism, versionMarker, writeNavigationConfig, writeSnapshot };
4896
+ //# sourceMappingURL=chunk-E2FAQREZ.js.map
4897
+ //# sourceMappingURL=chunk-E2FAQREZ.js.map