@neat.is/core 0.10.1 → 0.10.3-dev.20260930

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.
@@ -342,8 +342,8 @@ function deprecatedApis() {
342
342
  }
343
343
 
344
344
  // src/ingest.ts
345
- import { promises as fs7, existsSync, readFileSync } from "fs";
346
- import path8 from "path";
345
+ import { promises as fs8, existsSync, readFileSync } from "fs";
346
+ import path9 from "path";
347
347
  import * as sourceMapJs from "source-map-js";
348
348
 
349
349
  // src/policy.ts
@@ -942,7 +942,7 @@ import {
942
942
  } from "@neat.is/types";
943
943
 
944
944
  // src/extract/routes.ts
945
- import path7 from "path";
945
+ import path8 from "path";
946
946
  import Parser2 from "tree-sitter";
947
947
  import JavaScript2 from "tree-sitter-javascript";
948
948
  import Python2 from "tree-sitter-python";
@@ -974,6 +974,17 @@ var IGNORED_DIRS = /* @__PURE__ */ new Set([
974
974
  "dist",
975
975
  "build",
976
976
  ".next",
977
+ // `_next` is the *emitted* half of the pair above — `next build`'s output as
978
+ // it is served or exported. `.next` was listed and `_next` wasn't, so an
979
+ // exported bundle walked in as first-party source: minified chunks, one file
980
+ // per line, hundreds of KB each (#1254).
981
+ "_next",
982
+ // Agent scratch space. `.claude/worktrees/` holds a full checkout per agent
983
+ // session, so walking it re-extracts the whole repo once per worktree — and
984
+ // pulls in whatever build output those checkouts happen to carry. It is
985
+ // gitignored in practice, but the file walk doesn't read .gitignore, so the
986
+ // name has to be listed here.
987
+ ".claude",
977
988
  // Python virtualenv shapes (issue #344). Walking into a venv pulls in the
978
989
  // entire CPython stdlib + every installed package as if it were first-party
979
990
  // service code — 20k+ files, none of which the user wrote. The shape names
@@ -1212,6 +1223,20 @@ function noteExtractedDropped(edge) {
1212
1223
  function drainDroppedExtracted() {
1213
1224
  return droppedSink.splice(0, droppedSink.length);
1214
1225
  }
1226
+ var skippedSink = /* @__PURE__ */ new Map();
1227
+ function noteSkippedFile(file) {
1228
+ if (!skippedSink.has(file.path)) skippedSink.set(file.path, file);
1229
+ }
1230
+ function drainSkippedFiles() {
1231
+ const out = [...skippedSink.values()];
1232
+ skippedSink.clear();
1233
+ return out;
1234
+ }
1235
+ function formatSkippedBanner(count) {
1236
+ if (count <= 0) return null;
1237
+ if (count === 1) return `[neat] 1 minified file read but not parsed (FileNode kept, no symbols or calls)`;
1238
+ return `[neat] ${count} minified files read but not parsed (FileNodes kept, no symbols or calls)`;
1239
+ }
1215
1240
  function isRejectedLogEnabled() {
1216
1241
  const raw = process.env.NEAT_EXTRACTED_REJECTED_LOG;
1217
1242
  return raw === "1" || raw === "true";
@@ -1228,8 +1253,8 @@ function formatPrecisionFloorBanner(count) {
1228
1253
  }
1229
1254
 
1230
1255
  // src/extract/calls/shared.ts
1231
- import { promises as fs5 } from "fs";
1232
- import path5 from "path";
1256
+ import { promises as fs6 } from "fs";
1257
+ import path6 from "path";
1233
1258
  import {
1234
1259
  EdgeType as EdgeType2,
1235
1260
  NodeType as NodeType2,
@@ -1238,11 +1263,87 @@ import {
1238
1263
  extractedEdgeId as extractedEdgeId2,
1239
1264
  fileId
1240
1265
  } from "@neat.is/types";
1266
+
1267
+ // src/extract/gitignore.ts
1268
+ import { promises as fs5 } from "fs";
1269
+ import path5 from "path";
1270
+ import ignore from "ignore";
1271
+ var LAYER_CACHE = /* @__PURE__ */ new Map();
1272
+ async function layerFor(dir) {
1273
+ const cached = LAYER_CACHE.get(dir);
1274
+ if (cached !== void 0) return cached;
1275
+ let layer = null;
1276
+ try {
1277
+ const raw = await fs5.readFile(path5.join(dir, ".gitignore"), "utf8");
1278
+ layer = { dir, ig: ignore().add(raw) };
1279
+ } catch {
1280
+ layer = null;
1281
+ }
1282
+ LAYER_CACHE.set(dir, layer);
1283
+ return layer;
1284
+ }
1285
+ async function isRepoRoot(dir) {
1286
+ try {
1287
+ await fs5.stat(path5.join(dir, ".git"));
1288
+ return true;
1289
+ } catch {
1290
+ return false;
1291
+ }
1292
+ }
1293
+ async function loadIgnoreChain(startDir) {
1294
+ const dirs = [];
1295
+ let current = path5.resolve(startDir);
1296
+ for (; ; ) {
1297
+ dirs.push(current);
1298
+ if (await isRepoRoot(current)) break;
1299
+ const parent = path5.dirname(current);
1300
+ if (parent === current) break;
1301
+ current = parent;
1302
+ }
1303
+ dirs.reverse();
1304
+ const layers = [];
1305
+ for (const dir of dirs) {
1306
+ const layer = await layerFor(dir);
1307
+ if (layer) layers.push(layer);
1308
+ }
1309
+ return layers;
1310
+ }
1311
+ async function extendIgnoreChain(chain, dir) {
1312
+ const layer = await layerFor(path5.resolve(dir));
1313
+ return layer ? [...chain, layer] : chain;
1314
+ }
1315
+ function isIgnored(chain, fullPath, isDir) {
1316
+ return decide(chain, fullPath, isDir).ignored;
1317
+ }
1318
+ function decide(chain, fullPath, isDir) {
1319
+ if (chain.length === 0) return { ignored: false, subtree: chain };
1320
+ const abs = path5.resolve(fullPath);
1321
+ let ignored = false;
1322
+ let unignoredAt = -1;
1323
+ for (let i = 0; i < chain.length; i++) {
1324
+ const layer = chain[i];
1325
+ const rel = path5.relative(layer.dir, abs);
1326
+ if (!rel || rel.startsWith("..") || path5.isAbsolute(rel)) continue;
1327
+ const candidate = rel.split(path5.sep).join("/") + (isDir ? "/" : "");
1328
+ const result = layer.ig.test(candidate);
1329
+ if (result.ignored) ignored = true;
1330
+ else if (result.unignored) {
1331
+ ignored = false;
1332
+ unignoredAt = i;
1333
+ }
1334
+ }
1335
+ return {
1336
+ ignored,
1337
+ subtree: unignoredAt >= 0 ? chain.slice(unignoredAt) : chain
1338
+ };
1339
+ }
1340
+
1341
+ // src/extract/calls/shared.ts
1241
1342
  function buildServiceHostIndex(services) {
1242
1343
  const knownHosts = /* @__PURE__ */ new Set();
1243
1344
  const hostToNodeId = /* @__PURE__ */ new Map();
1244
1345
  for (const service of services) {
1245
- const base = path5.basename(service.dir);
1346
+ const base = path6.basename(service.dir);
1246
1347
  knownHosts.add(base);
1247
1348
  knownHosts.add(service.pkg.name);
1248
1349
  hostToNodeId.set(base, service.node.id);
@@ -1251,33 +1352,62 @@ function buildServiceHostIndex(services) {
1251
1352
  return { knownHosts, hostToNodeId };
1252
1353
  }
1253
1354
  async function walkSourceFiles(dir, excludeDirs = []) {
1254
- const excluded = new Set(excludeDirs.map((d) => path5.resolve(d)));
1355
+ const excluded = new Set(excludeDirs.map((d) => path6.resolve(d)));
1255
1356
  const out = [];
1256
- async function walk11(current) {
1257
- const entries = await fs5.readdir(current, { withFileTypes: true }).catch(() => []);
1357
+ const rootChain = await loadIgnoreChain(dir);
1358
+ async function walk11(current, inherited) {
1359
+ const entries = await fs6.readdir(current, { withFileTypes: true }).catch(() => []);
1360
+ const chain = entries.some((e) => e.isFile() && e.name === ".gitignore") ? await extendIgnoreChain(inherited, current) : inherited;
1258
1361
  for (const entry of entries) {
1259
- const full = path5.join(current, entry.name);
1362
+ const full = path6.join(current, entry.name);
1260
1363
  if (entry.isDirectory()) {
1261
1364
  if (IGNORED_DIRS.has(entry.name)) continue;
1262
- if (excluded.has(path5.resolve(full))) continue;
1365
+ if (excluded.has(path6.resolve(full))) continue;
1366
+ const verdict = decide(chain, full, true);
1367
+ if (verdict.ignored) continue;
1263
1368
  if (await isPythonVenvDir(full)) continue;
1264
- await walk11(full);
1265
- } else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(path5.extname(entry.name)) && // Skip NEAT's own generated `otel-init.*` bootstrap — extracting it
1369
+ await walk11(full, verdict.subtree);
1370
+ } else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(path6.extname(entry.name)) && // Skip NEAT's own generated `otel-init.*` bootstrap — extracting it
1266
1371
  // would attribute our instrumentation imports to the user's service.
1267
- !isNeatAuthoredSourceFile(entry.name)) {
1372
+ !isNeatAuthoredSourceFile(entry.name) && !isIgnored(chain, full, false)) {
1268
1373
  out.push(full);
1269
1374
  }
1270
1375
  }
1271
1376
  }
1272
- await walk11(dir);
1377
+ await walk11(dir, rootChain.filter((l) => path6.resolve(l.dir) !== path6.resolve(dir)));
1273
1378
  return out;
1274
1379
  }
1380
+ var MINIFIED_LINE_CHARS = 5e3;
1381
+ function hasMinifiedName(filePath) {
1382
+ return /\.min\.(js|mjs|cjs|jsx|ts|tsx)$/i.test(filePath);
1383
+ }
1384
+ function isMinifiedSource(filePath, content) {
1385
+ if (hasMinifiedName(filePath)) return { minified: true, detail: "named *.min.js" };
1386
+ let longest = 0;
1387
+ let start = 0;
1388
+ for (; ; ) {
1389
+ const nl = content.indexOf("\n", start);
1390
+ const len = (nl === -1 ? content.length : nl) - start;
1391
+ if (len > longest) longest = len;
1392
+ if (nl === -1) break;
1393
+ start = nl + 1;
1394
+ }
1395
+ if (longest > MINIFIED_LINE_CHARS) {
1396
+ return { minified: true, detail: `longest line ${longest} chars` };
1397
+ }
1398
+ return { minified: false, detail: "" };
1399
+ }
1275
1400
  async function loadSourceFiles(dir, excludeDirs = []) {
1276
1401
  const paths = await walkSourceFiles(dir, excludeDirs);
1277
1402
  const out = [];
1278
1403
  for (const p of paths) {
1279
1404
  try {
1280
- const content = await fs5.readFile(p, "utf8");
1405
+ const content = await fs6.readFile(p, "utf8");
1406
+ const verdict = isMinifiedSource(p, content);
1407
+ if (verdict.minified) {
1408
+ noteSkippedFile({ path: p, reason: "minified", detail: verdict.detail });
1409
+ continue;
1410
+ }
1281
1411
  out.push({ path: p, content });
1282
1412
  } catch {
1283
1413
  }
@@ -1289,15 +1419,18 @@ function lineOf(text, needle) {
1289
1419
  if (idx < 0) return 1;
1290
1420
  return text.slice(0, idx).split("\n").length;
1291
1421
  }
1422
+ var SNIPPET_MAX_CHARS = 120;
1292
1423
  function snippet(text, line) {
1293
1424
  const lines = text.split("\n");
1294
- return (lines[line - 1] ?? "").trim();
1425
+ const raw = (lines[line - 1] ?? "").trim();
1426
+ if (raw.length <= SNIPPET_MAX_CHARS) return raw;
1427
+ return `${raw.slice(0, SNIPPET_MAX_CHARS)}\u2026 (+${raw.length - SNIPPET_MAX_CHARS} chars)`;
1295
1428
  }
1296
1429
  function toPosix(p) {
1297
1430
  return p.split("\\").join("/");
1298
1431
  }
1299
1432
  function languageForPath(relPath) {
1300
- switch (path5.extname(relPath).toLowerCase()) {
1433
+ switch (path6.extname(relPath).toLowerCase()) {
1301
1434
  case ".py":
1302
1435
  return "python";
1303
1436
  case ".go":
@@ -1370,8 +1503,8 @@ function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
1370
1503
  }
1371
1504
 
1372
1505
  // src/extract/imports.ts
1373
- import path6 from "path";
1374
- import { promises as fs6 } from "fs";
1506
+ import path7 from "path";
1507
+ import { promises as fs7 } from "fs";
1375
1508
  import Parser from "tree-sitter";
1376
1509
  import JavaScript from "tree-sitter-javascript";
1377
1510
  import Python from "tree-sitter-python";
@@ -1522,15 +1655,15 @@ function collectPyImports(node, out) {
1522
1655
  }
1523
1656
  async function fileExists(p) {
1524
1657
  try {
1525
- await fs6.access(p);
1658
+ await fs7.access(p);
1526
1659
  return true;
1527
1660
  } catch {
1528
1661
  return false;
1529
1662
  }
1530
1663
  }
1531
1664
  function isWithinServiceDir(candidate, serviceDir) {
1532
- const rel = path6.relative(serviceDir, candidate);
1533
- return rel !== "" && !rel.startsWith("..") && !path6.isAbsolute(rel);
1665
+ const rel = path7.relative(serviceDir, candidate);
1666
+ return rel !== "" && !rel.startsWith("..") && !path7.isAbsolute(rel);
1534
1667
  }
1535
1668
  var JS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
1536
1669
  var JS_INDEX_FILES = JS_EXTENSIONS.map((ext) => `index${ext}`);
@@ -1538,22 +1671,22 @@ async function firstExistingCandidate(base, serviceDir) {
1538
1671
  for (const ext of JS_EXTENSIONS) {
1539
1672
  const candidate = base + ext;
1540
1673
  if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
1541
- return toPosix(path6.relative(serviceDir, candidate));
1674
+ return toPosix(path7.relative(serviceDir, candidate));
1542
1675
  }
1543
1676
  }
1544
1677
  for (const indexFile of JS_INDEX_FILES) {
1545
- const candidate = path6.join(base, indexFile);
1678
+ const candidate = path7.join(base, indexFile);
1546
1679
  if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
1547
- return toPosix(path6.relative(serviceDir, candidate));
1680
+ return toPosix(path7.relative(serviceDir, candidate));
1548
1681
  }
1549
1682
  }
1550
1683
  return null;
1551
1684
  }
1552
1685
  async function loadTsPathConfig(serviceDir) {
1553
- const tsconfigPath = path6.join(serviceDir, "tsconfig.json");
1686
+ const tsconfigPath = path7.join(serviceDir, "tsconfig.json");
1554
1687
  let raw;
1555
1688
  try {
1556
- raw = await fs6.readFile(tsconfigPath, "utf8");
1689
+ raw = await fs7.readFile(tsconfigPath, "utf8");
1557
1690
  } catch {
1558
1691
  return null;
1559
1692
  }
@@ -1562,7 +1695,7 @@ async function loadTsPathConfig(serviceDir) {
1562
1695
  const paths = parsed.compilerOptions?.paths;
1563
1696
  if (!paths || Object.keys(paths).length === 0) return null;
1564
1697
  const baseUrl = parsed.compilerOptions?.baseUrl;
1565
- return { paths, baseDir: baseUrl ? path6.resolve(serviceDir, baseUrl) : serviceDir };
1698
+ return { paths, baseDir: baseUrl ? path7.resolve(serviceDir, baseUrl) : serviceDir };
1566
1699
  } catch (err) {
1567
1700
  recordExtractionError("import alias resolution", tsconfigPath, err);
1568
1701
  return null;
@@ -1580,11 +1713,11 @@ async function resolveTsAlias(specifier, config, serviceDir) {
1580
1713
  if (suffix === null) continue;
1581
1714
  for (const target of targets) {
1582
1715
  const targetBase = target.endsWith("/*") ? target.slice(0, -2) : target.replace(/\*$/, "");
1583
- const resolvedBase = path6.resolve(config.baseDir, targetBase, suffix);
1716
+ const resolvedBase = path7.resolve(config.baseDir, targetBase, suffix);
1584
1717
  const hit = await firstExistingCandidate(resolvedBase, serviceDir);
1585
1718
  if (hit) return hit;
1586
1719
  if (isWithinServiceDir(resolvedBase, serviceDir) && await fileExists(resolvedBase)) {
1587
- return toPosix(path6.relative(serviceDir, resolvedBase));
1720
+ return toPosix(path7.relative(serviceDir, resolvedBase));
1588
1721
  }
1589
1722
  }
1590
1723
  }
@@ -1593,18 +1726,18 @@ async function resolveTsAlias(specifier, config, serviceDir) {
1593
1726
  async function resolveJsImport(specifier, importerDir, serviceDir, tsPaths) {
1594
1727
  if (!specifier) return null;
1595
1728
  if (specifier.startsWith("./") || specifier.startsWith("../")) {
1596
- const base = path6.resolve(importerDir, specifier);
1597
- const ext = path6.extname(specifier);
1729
+ const base = path7.resolve(importerDir, specifier);
1730
+ const ext = path7.extname(specifier);
1598
1731
  if (ext) {
1599
1732
  if (ext === ".js" || ext === ".jsx") {
1600
1733
  const tsExt = ext === ".jsx" ? ".tsx" : ".ts";
1601
1734
  const tsSibling = base.slice(0, -ext.length) + tsExt;
1602
1735
  if (isWithinServiceDir(tsSibling, serviceDir) && await fileExists(tsSibling)) {
1603
- return toPosix(path6.relative(serviceDir, tsSibling));
1736
+ return toPosix(path7.relative(serviceDir, tsSibling));
1604
1737
  }
1605
1738
  }
1606
1739
  if (isWithinServiceDir(base, serviceDir) && await fileExists(base)) {
1607
- return toPosix(path6.relative(serviceDir, base));
1740
+ return toPosix(path7.relative(serviceDir, base));
1608
1741
  }
1609
1742
  if (!JS_EXTENSIONS.includes(ext)) {
1610
1743
  return firstExistingCandidate(base, serviceDir);
@@ -1619,30 +1752,30 @@ async function resolveJsImport(specifier, importerDir, serviceDir, tsPaths) {
1619
1752
  async function resolvePyImport(imp, importerPath, serviceDir) {
1620
1753
  let baseDir;
1621
1754
  if (imp.level > 0) {
1622
- baseDir = path6.dirname(importerPath);
1623
- for (let i = 1; i < imp.level; i++) baseDir = path6.dirname(baseDir);
1755
+ baseDir = path7.dirname(importerPath);
1756
+ for (let i = 1; i < imp.level; i++) baseDir = path7.dirname(baseDir);
1624
1757
  } else {
1625
1758
  baseDir = serviceDir;
1626
1759
  }
1627
- const moduleBase = imp.modulePath ? path6.join(baseDir, imp.modulePath.split(".").join("/")) : baseDir;
1760
+ const moduleBase = imp.modulePath ? path7.join(baseDir, imp.modulePath.split(".").join("/")) : baseDir;
1628
1761
  const resolved = /* @__PURE__ */ new Set();
1629
1762
  let needModuleFile = imp.names.length === 0;
1630
1763
  for (const name of imp.names) {
1631
- const submoduleFile = path6.join(moduleBase, `${name}.py`);
1632
- const subpackageInit = path6.join(moduleBase, name, "__init__.py");
1764
+ const submoduleFile = path7.join(moduleBase, `${name}.py`);
1765
+ const subpackageInit = path7.join(moduleBase, name, "__init__.py");
1633
1766
  if (isWithinServiceDir(submoduleFile, serviceDir) && await fileExists(submoduleFile)) {
1634
- resolved.add(toPosix(path6.relative(serviceDir, submoduleFile)));
1767
+ resolved.add(toPosix(path7.relative(serviceDir, submoduleFile)));
1635
1768
  } else if (isWithinServiceDir(subpackageInit, serviceDir) && await fileExists(subpackageInit)) {
1636
- resolved.add(toPosix(path6.relative(serviceDir, subpackageInit)));
1769
+ resolved.add(toPosix(path7.relative(serviceDir, subpackageInit)));
1637
1770
  } else {
1638
1771
  needModuleFile = true;
1639
1772
  }
1640
1773
  }
1641
1774
  if (needModuleFile) {
1642
- const moduleFileCandidates = imp.modulePath ? [`${moduleBase}.py`, path6.join(moduleBase, "__init__.py")] : [path6.join(moduleBase, "__init__.py")];
1775
+ const moduleFileCandidates = imp.modulePath ? [`${moduleBase}.py`, path7.join(moduleBase, "__init__.py")] : [path7.join(moduleBase, "__init__.py")];
1643
1776
  for (const candidate of moduleFileCandidates) {
1644
1777
  if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
1645
- resolved.add(toPosix(path6.relative(serviceDir, candidate)));
1778
+ resolved.add(toPosix(path7.relative(serviceDir, candidate)));
1646
1779
  break;
1647
1780
  }
1648
1781
  }
@@ -1652,11 +1785,11 @@ async function resolvePyImport(imp, importerPath, serviceDir) {
1652
1785
  async function resolveGoImport(specifier, modulePath, serviceDir) {
1653
1786
  if (specifier !== modulePath && !specifier.startsWith(`${modulePath}/`)) return null;
1654
1787
  const suffix = specifier === modulePath ? "" : specifier.slice(modulePath.length + 1);
1655
- const dir = path6.join(serviceDir, suffix);
1656
- const entries = await fs6.readdir(dir, { withFileTypes: true }).catch(() => []);
1657
- const candidates = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".go") && !entry.name.endsWith("_test.go")).map((entry) => path6.join(dir, entry.name));
1788
+ const dir = path7.join(serviceDir, suffix);
1789
+ const entries = await fs7.readdir(dir, { withFileTypes: true }).catch(() => []);
1790
+ const candidates = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".go") && !entry.name.endsWith("_test.go")).map((entry) => path7.join(dir, entry.name));
1658
1791
  if (candidates.length !== 1) return null;
1659
- return toPosix(path6.relative(serviceDir, candidates[0]));
1792
+ return toPosix(path7.relative(serviceDir, candidates[0]));
1660
1793
  }
1661
1794
  function emitImportEdge(graph, serviceName, importerFileId, importerRelPath, importeeRelPath, line, snippet2) {
1662
1795
  const importeeFileId = fileId2(serviceName, importeeRelPath);
@@ -1686,12 +1819,12 @@ async function addImports(graph, services) {
1686
1819
  const files = await loadSourceFiles(service.dir, service.excludeDirs);
1687
1820
  for (const file of files) {
1688
1821
  if (isTestPath(file.path)) continue;
1689
- const relFile = toPosix(path6.relative(service.dir, file.path));
1822
+ const relFile = toPosix(path7.relative(service.dir, file.path));
1690
1823
  const importerFileId = fileId2(service.pkg.name, relFile);
1691
- const isPython = path6.extname(file.path) === ".py";
1692
- const isGo = path6.extname(file.path) === ".go";
1693
- if (path6.extname(file.path) === ".rb") continue;
1694
- if (path6.extname(file.path) === ".php") continue;
1824
+ const isPython = path7.extname(file.path) === ".py";
1825
+ const isGo = path7.extname(file.path) === ".go";
1826
+ if (path7.extname(file.path) === ".rb") continue;
1827
+ if (path7.extname(file.path) === ".php") continue;
1695
1828
  if (isGo) {
1696
1829
  let goImports = [];
1697
1830
  try {
@@ -1701,7 +1834,7 @@ async function addImports(graph, services) {
1701
1834
  recordExtractionError("import extraction", file.path, err);
1702
1835
  continue;
1703
1836
  }
1704
- const goMod = await fs6.readFile(path6.join(service.dir, "go.mod"), "utf8").catch(() => "");
1837
+ const goMod = await fs7.readFile(path7.join(service.dir, "go.mod"), "utf8").catch(() => "");
1705
1838
  const modulePath = goMod.match(/^\s*module\s+(\S+)\s*$/m)?.[1];
1706
1839
  if (!modulePath) continue;
1707
1840
  for (const imp of goImports) {
@@ -1745,7 +1878,7 @@ async function addImports(graph, services) {
1745
1878
  continue;
1746
1879
  }
1747
1880
  for (const imp of jsImports) {
1748
- const resolved = await resolveJsImport(imp.specifier, path6.dirname(file.path), service.dir, tsPaths);
1881
+ const resolved = await resolveJsImport(imp.specifier, path7.dirname(file.path), service.dir, tsPaths);
1749
1882
  if (!resolved) continue;
1750
1883
  edgesAdded += emitImportEdge(
1751
1884
  graph,
@@ -1870,8 +2003,8 @@ function chiRoutesFromSource(source, parser) {
1870
2003
  chiWalk(tree.rootNode, "", out);
1871
2004
  return out;
1872
2005
  }
1873
- function stripChiRegex(path73) {
1874
- return path73.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
2006
+ function stripChiRegex(path74) {
2007
+ return path74.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
1875
2008
  }
1876
2009
  function chiWalk(node, prefix, out) {
1877
2010
  for (let i = 0; i < node.namedChildCount; i++) {
@@ -2211,7 +2344,7 @@ function isNextPagesApiFile(relFile) {
2211
2344
  if (pagesIdx === -1 || segs[pagesIdx + 1] !== "api") return false;
2212
2345
  const base = segs[segs.length - 1] ?? "";
2213
2346
  if (/^_(app|document|middleware)\./.test(base)) return false;
2214
- return JS_ROUTE_EXTENSIONS.has(path7.extname(base));
2347
+ return JS_ROUTE_EXTENSIONS.has(path8.extname(base));
2215
2348
  }
2216
2349
  function nextSegment(seg) {
2217
2350
  if (seg.startsWith("(") && seg.endsWith(")")) return null;
@@ -2543,9 +2676,9 @@ function rubyRocketRoute(args) {
2543
2676
  if (!pair || pair.type !== "pair") continue;
2544
2677
  const k = pair.childForFieldName("key");
2545
2678
  if (k?.type !== "string") continue;
2546
- const path73 = rubyLiteral(k);
2547
- if (path73 === null) continue;
2548
- return { path: path73, target: rubyLiteral(pair.childForFieldName("value")) };
2679
+ const path74 = rubyLiteral(k);
2680
+ if (path74 === null) continue;
2681
+ return { path: path74, target: rubyLiteral(pair.childForFieldName("value")) };
2549
2682
  }
2550
2683
  return null;
2551
2684
  }
@@ -3393,12 +3526,12 @@ async function expressMountPrefixes(files, serviceDir, tsPaths) {
3393
3526
  const jsParser = makeJsParser2();
3394
3527
  const fileInfo = /* @__PURE__ */ new Map();
3395
3528
  for (const f of files) {
3396
- if (!JS_ROUTE_EXTENSIONS.has(path7.extname(f.path))) continue;
3529
+ if (!JS_ROUTE_EXTENSIONS.has(path8.extname(f.path))) continue;
3397
3530
  if (isTestPath(f.path)) continue;
3398
- const rel = toPosix(path7.relative(serviceDir, f.path));
3531
+ const rel = toPosix(path8.relative(serviceDir, f.path));
3399
3532
  try {
3400
3533
  const tree = parseSource2(jsParser, f.content);
3401
- fileInfo.set(rel, analyzeExpressFile(tree.rootNode, path7.dirname(f.path)));
3534
+ fileInfo.set(rel, analyzeExpressFile(tree.rootNode, path8.dirname(f.path)));
3402
3535
  } catch {
3403
3536
  }
3404
3537
  }
@@ -3497,14 +3630,14 @@ async function addRoutes(graph, services) {
3497
3630
  const mountPrefixes = hasExpress ? await expressMountPrefixes(files, service.dir, await loadTsPathConfig(service.dir)) : /* @__PURE__ */ new Map();
3498
3631
  for (const file of files) {
3499
3632
  if (isTestPath(file.path)) continue;
3500
- const ext = path7.extname(file.path);
3633
+ const ext = path8.extname(file.path);
3501
3634
  const isPy = ext === ".py";
3502
3635
  const isGo = ext === ".go";
3503
3636
  const isRb = ext === ".rb";
3504
3637
  const isPhp = ext === ".php";
3505
3638
  const isRs = ext === ".rs";
3506
3639
  if (!JS_ROUTE_EXTENSIONS.has(ext) && !isPy && !isGo && !isRb && !isPhp && !isRs) continue;
3507
- const relFile = toPosix(path7.relative(service.dir, file.path));
3640
+ const relFile = toPosix(path8.relative(service.dir, file.path));
3508
3641
  let routes;
3509
3642
  try {
3510
3643
  if (isPhp) {
@@ -4009,7 +4142,7 @@ function languageForExt(relPath) {
4009
4142
  function relPathForRuntimeFile(filepath, serviceNode, scanPath) {
4010
4143
  let p = toPosix2(filepath).replace(/^file:\/\//, "");
4011
4144
  if (scanPath && scanPath.length > 0) {
4012
- const absRoot = toPosix2(path8.resolve(scanPath, serviceNode?.repoPath ?? ""));
4145
+ const absRoot = toPosix2(path9.resolve(scanPath, serviceNode?.repoPath ?? ""));
4013
4146
  const anchor = absRoot.endsWith("/") ? absRoot : `${absRoot}/`;
4014
4147
  if (p.startsWith(anchor)) return p.slice(anchor.length);
4015
4148
  }
@@ -4040,7 +4173,7 @@ function resolveDistToSrc(absFilepath, line) {
4040
4173
  if (existsSync(mapPath)) {
4041
4174
  const raw = JSON.parse(readFileSync(mapPath, "utf8"));
4042
4175
  const consumer = new sourceMapJs.SourceMapConsumer(raw);
4043
- entry = { consumer, dir: path8.dirname(mapPath) };
4176
+ entry = { consumer, dir: path9.dirname(mapPath) };
4044
4177
  }
4045
4178
  } catch {
4046
4179
  entry = null;
@@ -4060,7 +4193,7 @@ function resolveDistToSrc(absFilepath, line) {
4060
4193
  }
4061
4194
  if (!pos || !pos.source) return null;
4062
4195
  const root = entry.consumer.sourceRoot ?? "";
4063
- const resolved = path8.resolve(entry.dir, root, pos.source);
4196
+ const resolved = path9.resolve(entry.dir, root, pos.source);
4064
4197
  return { filepath: resolved, ...pos.line ? { line: pos.line } : {} };
4065
4198
  } catch {
4066
4199
  return null;
@@ -4604,8 +4737,8 @@ function emitIncidentEvent(project, ev) {
4604
4737
  });
4605
4738
  }
4606
4739
  async function appendErrorEvent(ctx, ev) {
4607
- await fs7.mkdir(path8.dirname(ctx.errorsPath), { recursive: true });
4608
- await fs7.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
4740
+ await fs8.mkdir(path9.dirname(ctx.errorsPath), { recursive: true });
4741
+ await fs8.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
4609
4742
  emitIncidentEvent(ctx.project ?? DEFAULT_PROJECT, ev);
4610
4743
  }
4611
4744
  async function appendConnectorIncident(errorsPath, input, project) {
@@ -4620,8 +4753,8 @@ async function appendConnectorIncident(errorsPath, input, project) {
4620
4753
  ...input.attributes && Object.keys(input.attributes).length > 0 ? { attributes: input.attributes } : {},
4621
4754
  affectedNode: input.affectedNode
4622
4755
  };
4623
- await fs7.mkdir(path8.dirname(errorsPath), { recursive: true });
4624
- await fs7.appendFile(errorsPath, JSON.stringify(ev) + "\n", "utf8");
4756
+ await fs8.mkdir(path9.dirname(errorsPath), { recursive: true });
4757
+ await fs8.appendFile(errorsPath, JSON.stringify(ev) + "\n", "utf8");
4625
4758
  if (project) emitIncidentEvent(project, ev);
4626
4759
  }
4627
4760
  function landIncidentCallSite(span, callSite, trusted, graph) {
@@ -4743,8 +4876,8 @@ function makeErrorSpanWriter(errorsPath, graph, scanPath, project = DEFAULT_PROJ
4743
4876
  return async (span) => {
4744
4877
  const ev = buildErrorEventForReceiver(span, graph, scanPath);
4745
4878
  if (!ev) return;
4746
- await fs7.mkdir(path8.dirname(errorsPath), { recursive: true });
4747
- await fs7.appendFile(errorsPath, JSON.stringify(ev) + "\n", "utf8");
4879
+ await fs8.mkdir(path9.dirname(errorsPath), { recursive: true });
4880
+ await fs8.appendFile(errorsPath, JSON.stringify(ev) + "\n", "utf8");
4748
4881
  emitIncidentEvent(project, ev);
4749
4882
  };
4750
4883
  }
@@ -5247,13 +5380,13 @@ async function markStaleEdges(graph, options = {}) {
5247
5380
  return { count: events.length, events };
5248
5381
  }
5249
5382
  async function appendStaleEvents(staleEventsPath, events) {
5250
- await fs7.mkdir(path8.dirname(staleEventsPath), { recursive: true });
5383
+ await fs8.mkdir(path9.dirname(staleEventsPath), { recursive: true });
5251
5384
  const lines = events.map((e) => JSON.stringify(e)).join("\n") + "\n";
5252
- await fs7.appendFile(staleEventsPath, lines, "utf8");
5385
+ await fs8.appendFile(staleEventsPath, lines, "utf8");
5253
5386
  }
5254
5387
  async function readStaleEvents(staleEventsPath) {
5255
5388
  try {
5256
- const raw = await fs7.readFile(staleEventsPath, "utf8");
5389
+ const raw = await fs8.readFile(staleEventsPath, "utf8");
5257
5390
  return raw.split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
5258
5391
  } catch (err) {
5259
5392
  if (err.code === "ENOENT") return [];
@@ -5289,7 +5422,7 @@ function startStalenessLoop(graph, options = {}) {
5289
5422
  var INCIDENT_READ_MAX_BYTES = 32 * 1024 * 1024;
5290
5423
  var INCIDENT_READ_MAX_EVENTS = 5e3;
5291
5424
  async function readErrorFileTail(errorsPath, maxBytes) {
5292
- const handle = await fs7.open(errorsPath, "r");
5425
+ const handle = await fs8.open(errorsPath, "r");
5293
5426
  try {
5294
5427
  const { size } = await handle.stat();
5295
5428
  if (size <= maxBytes) {
@@ -5546,19 +5679,19 @@ function confidenceFromMix(edges, now = Date.now()) {
5546
5679
  function longestIncomingWalk(graph, start, maxDepth) {
5547
5680
  let best = { path: [start], edges: [] };
5548
5681
  const visited = /* @__PURE__ */ new Set([start]);
5549
- function step(node, path73, edges) {
5550
- if (path73.length > best.path.length) {
5551
- best = { path: [...path73], edges: [...edges] };
5682
+ function step(node, path74, edges) {
5683
+ if (path74.length > best.path.length) {
5684
+ best = { path: [...path74], edges: [...edges] };
5552
5685
  }
5553
- if (path73.length - 1 >= maxDepth) return;
5686
+ if (path74.length - 1 >= maxDepth) return;
5554
5687
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
5555
5688
  for (const [srcId, edge] of incoming) {
5556
5689
  if (visited.has(srcId)) continue;
5557
5690
  visited.add(srcId);
5558
- path73.push(srcId);
5691
+ path74.push(srcId);
5559
5692
  edges.push(edge);
5560
- step(srcId, path73, edges);
5561
- path73.pop();
5693
+ step(srcId, path74, edges);
5694
+ path74.pop();
5562
5695
  edges.pop();
5563
5696
  visited.delete(srcId);
5564
5697
  }
@@ -5773,20 +5906,20 @@ function dominantFailingCall(graph, serviceId17, visited) {
5773
5906
  return best;
5774
5907
  }
5775
5908
  function followFailingCallChain(graph, originServiceId, maxDepth) {
5776
- const path73 = [originServiceId];
5909
+ const path74 = [originServiceId];
5777
5910
  const edges = [];
5778
5911
  const visited = /* @__PURE__ */ new Set([originServiceId]);
5779
5912
  let current = originServiceId;
5780
5913
  for (let depth = 0; depth < maxDepth; depth++) {
5781
5914
  const hop = dominantFailingCall(graph, current, visited);
5782
5915
  if (!hop) break;
5783
- path73.push(hop.nextService);
5916
+ path74.push(hop.nextService);
5784
5917
  edges.push(hop.edge);
5785
5918
  visited.add(hop.nextService);
5786
5919
  current = hop.nextService;
5787
5920
  }
5788
5921
  if (edges.length === 0) return null;
5789
- return { path: path73, edges, culprit: current };
5922
+ return { path: path74, edges, culprit: current };
5790
5923
  }
5791
5924
  function isStaleCallEdge(e) {
5792
5925
  return e.type === EdgeType6.CALLS && e.provenance === Provenance6.STALE;
@@ -5822,26 +5955,26 @@ function dominantStaleCall(graph, serviceId17, visited) {
5822
5955
  return best;
5823
5956
  }
5824
5957
  function followStaleCallChain(graph, originServiceId, maxDepth) {
5825
- const path73 = [originServiceId];
5958
+ const path74 = [originServiceId];
5826
5959
  const edges = [];
5827
5960
  const visited = /* @__PURE__ */ new Set([originServiceId]);
5828
5961
  let current = originServiceId;
5829
5962
  for (let depth = 0; depth < maxDepth; depth++) {
5830
5963
  const hop = dominantStaleCall(graph, current, visited);
5831
5964
  if (!hop) break;
5832
- path73.push(hop.nextService);
5965
+ path74.push(hop.nextService);
5833
5966
  edges.push(hop.edge);
5834
5967
  visited.add(hop.nextService);
5835
5968
  current = hop.nextService;
5836
5969
  }
5837
5970
  if (edges.length === 0) return null;
5838
- return { path: path73, edges, culprit: current };
5971
+ return { path: path74, edges, culprit: current };
5839
5972
  }
5840
5973
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
5841
5974
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
5842
5975
  if (!chain) return null;
5843
5976
  const culprit = chain.culprit;
5844
- const path73 = [...chain.path];
5977
+ const path74 = [...chain.path];
5845
5978
  const edgeProvenances = chain.edges.map((e) => e.provenance);
5846
5979
  const baseConfidence = confidenceFromMix(chain.edges);
5847
5980
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -5849,14 +5982,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
5849
5982
  if (loc) {
5850
5983
  let rootCauseNode = culprit;
5851
5984
  if (loc.fileNode) {
5852
- path73.push(loc.fileNode);
5985
+ path74.push(loc.fileNode);
5853
5986
  edgeProvenances.push(Provenance6.OBSERVED);
5854
5987
  rootCauseNode = loc.fileNode;
5855
5988
  }
5856
5989
  return RootCauseResultSchema.parse({
5857
5990
  rootCauseNode,
5858
5991
  rootCauseReason: loc.rootCauseReason,
5859
- traversalPath: path73,
5992
+ traversalPath: path74,
5860
5993
  edgeProvenances,
5861
5994
  confidence,
5862
5995
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -5868,7 +6001,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
5868
6001
  return RootCauseResultSchema.parse({
5869
6002
  rootCauseNode: culprit,
5870
6003
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
5871
- traversalPath: path73,
6004
+ traversalPath: path74,
5872
6005
  edgeProvenances,
5873
6006
  confidence,
5874
6007
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -6521,10 +6654,10 @@ function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
6521
6654
  traversalPath = staleChain.path;
6522
6655
  edgeProvenances = staleChain.edges.map((e) => e.provenance);
6523
6656
  } else if (top.node !== seedNode) {
6524
- const path73 = findPath(graph, errorNodeId, top.node, "up", ROOT_CAUSE_MAX_DEPTH);
6525
- if (path73) {
6526
- traversalPath = path73.nodes;
6527
- edgeProvenances = path73.edges.map((e) => e.provenance);
6657
+ const path74 = findPath(graph, errorNodeId, top.node, "up", ROOT_CAUSE_MAX_DEPTH);
6658
+ if (path74) {
6659
+ traversalPath = path74.nodes;
6660
+ edgeProvenances = path74.edges.map((e) => e.provenance);
6528
6661
  } else {
6529
6662
  traversalPath = [errorNodeId, top.node];
6530
6663
  edgeProvenances = [top.provenance ?? Provenance6.OBSERVED];
@@ -6559,15 +6692,15 @@ function fixRecommendationForTop(top, seedNode, legacy) {
6559
6692
  }
6560
6693
 
6561
6694
  // src/extract/services.ts
6562
- import { promises as fs18 } from "fs";
6563
- import path20 from "path";
6564
- import ignore from "ignore";
6695
+ import { promises as fs19 } from "fs";
6696
+ import path21 from "path";
6697
+ import ignore2 from "ignore";
6565
6698
  import { minimatch as minimatch2 } from "minimatch";
6566
6699
  import { NodeType as NodeType15, serviceId as serviceId11 } from "@neat.is/types";
6567
6700
 
6568
6701
  // src/extract/python.ts
6569
- import { promises as fs8 } from "fs";
6570
- import path9 from "path";
6702
+ import { promises as fs9 } from "fs";
6703
+ import path10 from "path";
6571
6704
  import { parse as parseToml } from "smol-toml";
6572
6705
  var REQUIREMENT_LINE = /^\s*([A-Za-z0-9_.-]+)(?:\[[^\]]*\])?\s*(?:(==)\s*([A-Za-z0-9_.+-]+))?/;
6573
6706
  function parseRequirementsTxt(content) {
@@ -6600,25 +6733,25 @@ function depsFromPyProject(pyproject) {
6600
6733
  return out;
6601
6734
  }
6602
6735
  async function discoverPythonService(serviceDir) {
6603
- const pyprojectPath = path9.join(serviceDir, "pyproject.toml");
6604
- const requirementsPath = path9.join(serviceDir, "requirements.txt");
6605
- const setupPath = path9.join(serviceDir, "setup.py");
6736
+ const pyprojectPath = path10.join(serviceDir, "pyproject.toml");
6737
+ const requirementsPath = path10.join(serviceDir, "requirements.txt");
6738
+ const setupPath = path10.join(serviceDir, "setup.py");
6606
6739
  const hasPyproject = await exists(pyprojectPath);
6607
6740
  const hasRequirements = await exists(requirementsPath);
6608
6741
  const hasSetup = await exists(setupPath);
6609
6742
  if (!hasPyproject && !hasRequirements && !hasSetup) return null;
6610
- let name = path9.basename(serviceDir);
6743
+ let name = path10.basename(serviceDir);
6611
6744
  let version;
6612
6745
  const dependencies = {};
6613
6746
  if (hasPyproject) {
6614
- const raw = await fs8.readFile(pyprojectPath, "utf8");
6747
+ const raw = await fs9.readFile(pyprojectPath, "utf8");
6615
6748
  const pyproject = parseToml(raw);
6616
6749
  name = pyproject.project?.name ?? pyproject.tool?.poetry?.name ?? name;
6617
6750
  version = pyproject.project?.version ?? pyproject.tool?.poetry?.version ?? void 0;
6618
6751
  Object.assign(dependencies, depsFromPyProject(pyproject));
6619
6752
  }
6620
6753
  if (hasRequirements) {
6621
- const raw = await fs8.readFile(requirementsPath, "utf8");
6754
+ const raw = await fs9.readFile(requirementsPath, "utf8");
6622
6755
  Object.assign(dependencies, parseRequirementsTxt(raw));
6623
6756
  }
6624
6757
  return { name, version, dependencies };
@@ -6632,8 +6765,8 @@ function pythonToPackage(service) {
6632
6765
  }
6633
6766
 
6634
6767
  // src/extract/go.ts
6635
- import { promises as fs9 } from "fs";
6636
- import path10 from "path";
6768
+ import { promises as fs10 } from "fs";
6769
+ import path11 from "path";
6637
6770
  import { NodeType as NodeType6, serviceId as serviceId2 } from "@neat.is/types";
6638
6771
  function parseGoMod(source) {
6639
6772
  const module = source.match(/^\s*module\s+(\S+)\s*$/m)?.[1];
@@ -6660,7 +6793,7 @@ function goFramework(deps) {
6660
6793
  async function discoverGoService(scanPath, dir) {
6661
6794
  let raw;
6662
6795
  try {
6663
- raw = await fs9.readFile(path10.join(dir, "go.mod"), "utf8");
6796
+ raw = await fs10.readFile(path11.join(dir, "go.mod"), "utf8");
6664
6797
  } catch {
6665
6798
  return null;
6666
6799
  }
@@ -6675,15 +6808,15 @@ async function discoverGoService(scanPath, dir) {
6675
6808
  name,
6676
6809
  language: "go",
6677
6810
  dependencies: mod.dependencies,
6678
- repoPath: path10.relative(scanPath, dir),
6811
+ repoPath: path11.relative(scanPath, dir),
6679
6812
  ...framework ? { framework } : {}
6680
6813
  };
6681
6814
  return { pkg, dir, node };
6682
6815
  }
6683
6816
 
6684
6817
  // src/extract/ruby.ts
6685
- import { promises as fs10 } from "fs";
6686
- import path11 from "path";
6818
+ import { promises as fs11 } from "fs";
6819
+ import path12 from "path";
6687
6820
  import { NodeType as NodeType7, serviceId as serviceId3 } from "@neat.is/types";
6688
6821
  function parseGemfile(source) {
6689
6822
  const dependencies = {};
@@ -6700,12 +6833,12 @@ function parseGemfile(source) {
6700
6833
  async function discoverRubyService(scanPath, dir) {
6701
6834
  let raw;
6702
6835
  try {
6703
- raw = await fs10.readFile(path11.join(dir, "Gemfile"), "utf8");
6836
+ raw = await fs11.readFile(path12.join(dir, "Gemfile"), "utf8");
6704
6837
  } catch {
6705
6838
  return null;
6706
6839
  }
6707
6840
  const gemfile = parseGemfile(raw);
6708
- const name = path11.basename(dir);
6841
+ const name = path12.basename(dir);
6709
6842
  const pkg = { name, dependencies: gemfile.dependencies };
6710
6843
  const node = {
6711
6844
  id: serviceId3(name),
@@ -6713,14 +6846,14 @@ async function discoverRubyService(scanPath, dir) {
6713
6846
  name,
6714
6847
  language: "ruby",
6715
6848
  dependencies: gemfile.dependencies,
6716
- repoPath: path11.relative(scanPath, dir),
6849
+ repoPath: path12.relative(scanPath, dir),
6717
6850
  ...gemfile.dependencies["rails"] !== void 0 ? { framework: "rails" } : {}
6718
6851
  };
6719
6852
  return { pkg, dir, node };
6720
6853
  }
6721
6854
 
6722
6855
  // src/extract/php.ts
6723
- import path12 from "path";
6856
+ import path13 from "path";
6724
6857
  import { NodeType as NodeType8, serviceId as serviceId4 } from "@neat.is/types";
6725
6858
  function parseComposerJson(composer) {
6726
6859
  return {
@@ -6733,12 +6866,12 @@ function parseComposerJson(composer) {
6733
6866
  async function discoverPhpService(scanPath, dir) {
6734
6867
  let composer;
6735
6868
  try {
6736
- composer = await readJson(path12.join(dir, "composer.json"));
6869
+ composer = await readJson(path13.join(dir, "composer.json"));
6737
6870
  } catch {
6738
6871
  return null;
6739
6872
  }
6740
6873
  const { dependencies } = parseComposerJson(composer);
6741
- const name = path12.basename(dir);
6874
+ const name = path13.basename(dir);
6742
6875
  const pkg = { name, dependencies };
6743
6876
  const node = {
6744
6877
  id: serviceId4(name),
@@ -6746,30 +6879,30 @@ async function discoverPhpService(scanPath, dir) {
6746
6879
  name,
6747
6880
  language: "php",
6748
6881
  dependencies,
6749
- repoPath: path12.relative(scanPath, dir),
6882
+ repoPath: path13.relative(scanPath, dir),
6750
6883
  ...dependencies["laravel/framework"] !== void 0 ? { framework: "laravel" } : {}
6751
6884
  };
6752
6885
  return { pkg, dir, node };
6753
6886
  }
6754
6887
 
6755
6888
  // src/extract/csharp.ts
6756
- import { promises as fs11 } from "fs";
6757
- import path13 from "path";
6889
+ import { promises as fs12 } from "fs";
6890
+ import path14 from "path";
6758
6891
  import { NodeType as NodeType9, serviceId as serviceId5 } from "@neat.is/types";
6759
6892
  var CSHARP_PROJECT_EXTS = /* @__PURE__ */ new Set([".csproj", ".sln"]);
6760
6893
  async function projectFiles(dir) {
6761
- const entries = await fs11.readdir(dir, { withFileTypes: true }).catch(() => []);
6894
+ const entries = await fs12.readdir(dir, { withFileTypes: true }).catch(() => []);
6762
6895
  const out = [];
6763
6896
  for (const entry of entries) {
6764
6897
  if (!entry.isFile()) continue;
6765
- if (CSHARP_PROJECT_EXTS.has(path13.extname(entry.name).toLowerCase())) out.push(entry.name);
6898
+ if (CSHARP_PROJECT_EXTS.has(path14.extname(entry.name).toLowerCase())) out.push(entry.name);
6766
6899
  }
6767
6900
  return out.sort();
6768
6901
  }
6769
6902
  var NESTED_PROJECT_DIR = "src";
6770
6903
  async function resolveProjectDir(dir) {
6771
6904
  if ((await projectFiles(dir)).length > 0) return dir;
6772
- const nested = path13.join(dir, NESTED_PROJECT_DIR);
6905
+ const nested = path14.join(dir, NESTED_PROJECT_DIR);
6773
6906
  if ((await projectFiles(nested)).length > 0) return nested;
6774
6907
  return null;
6775
6908
  }
@@ -6794,10 +6927,10 @@ async function discoverCsharpService(scanPath, dir) {
6794
6927
  if (projectDir === null) return null;
6795
6928
  const projects = await projectFiles(projectDir);
6796
6929
  const csproj = projects.find((p) => p.toLowerCase().endsWith(".csproj"));
6797
- const name = csproj ? path13.basename(csproj, path13.extname(csproj)) : path13.basename(dir);
6930
+ const name = csproj ? path14.basename(csproj, path14.extname(csproj)) : path14.basename(dir);
6798
6931
  let dependencies = {};
6799
6932
  if (csproj) {
6800
- const raw = await fs11.readFile(path13.join(projectDir, csproj), "utf8").catch(() => "");
6933
+ const raw = await fs12.readFile(path14.join(projectDir, csproj), "utf8").catch(() => "");
6801
6934
  dependencies = parseCsproj(raw).dependencies;
6802
6935
  }
6803
6936
  const pkg = { name, dependencies };
@@ -6809,19 +6942,19 @@ async function discoverCsharpService(scanPath, dir) {
6809
6942
  dependencies,
6810
6943
  // Anchor on the directory that holds the project and its `.cs` files, so file
6811
6944
  // attribution reaches the source whether it sits at the root or under `src/`.
6812
- repoPath: path13.relative(scanPath, projectDir)
6945
+ repoPath: path14.relative(scanPath, projectDir)
6813
6946
  };
6814
6947
  return { pkg, dir: projectDir, node };
6815
6948
  }
6816
6949
 
6817
6950
  // src/extract/java.ts
6818
- import { promises as fs12 } from "fs";
6819
- import path14 from "path";
6951
+ import { promises as fs13 } from "fs";
6952
+ import path15 from "path";
6820
6953
  import { NodeType as NodeType10, serviceId as serviceId6 } from "@neat.is/types";
6821
6954
  var JAVA_MANIFESTS = ["pom.xml", "build.gradle", "build.gradle.kts"];
6822
6955
  async function hasJavaManifest(dir) {
6823
6956
  for (const manifest of JAVA_MANIFESTS) {
6824
- if (await exists(path14.join(dir, manifest))) return true;
6957
+ if (await exists(path15.join(dir, manifest))) return true;
6825
6958
  }
6826
6959
  return false;
6827
6960
  }
@@ -6856,19 +6989,19 @@ function parseGradle(source) {
6856
6989
  return { dependencies };
6857
6990
  }
6858
6991
  async function discoverJavaService(scanPath, dir) {
6859
- const pomPath = path14.join(dir, "pom.xml");
6860
- const gradlePath = path14.join(dir, "build.gradle");
6861
- const gradleKtsPath = path14.join(dir, "build.gradle.kts");
6992
+ const pomPath = path15.join(dir, "pom.xml");
6993
+ const gradlePath = path15.join(dir, "build.gradle");
6994
+ const gradleKtsPath = path15.join(dir, "build.gradle.kts");
6862
6995
  let manifest = null;
6863
6996
  if (await exists(pomPath)) {
6864
- manifest = parsePomXml(await fs12.readFile(pomPath, "utf8").catch(() => ""));
6997
+ manifest = parsePomXml(await fs13.readFile(pomPath, "utf8").catch(() => ""));
6865
6998
  } else if (await exists(gradlePath)) {
6866
- manifest = parseGradle(await fs12.readFile(gradlePath, "utf8").catch(() => ""));
6999
+ manifest = parseGradle(await fs13.readFile(gradlePath, "utf8").catch(() => ""));
6867
7000
  } else if (await exists(gradleKtsPath)) {
6868
- manifest = parseGradle(await fs12.readFile(gradleKtsPath, "utf8").catch(() => ""));
7001
+ manifest = parseGradle(await fs13.readFile(gradleKtsPath, "utf8").catch(() => ""));
6869
7002
  }
6870
7003
  if (!manifest) return null;
6871
- const name = manifest.name?.trim() || path14.basename(dir);
7004
+ const name = manifest.name?.trim() || path15.basename(dir);
6872
7005
  const dependencies = manifest.dependencies;
6873
7006
  const pkg = { name, dependencies };
6874
7007
  const node = {
@@ -6877,14 +7010,14 @@ async function discoverJavaService(scanPath, dir) {
6877
7010
  name,
6878
7011
  language: "java",
6879
7012
  dependencies,
6880
- repoPath: path14.relative(scanPath, dir)
7013
+ repoPath: path15.relative(scanPath, dir)
6881
7014
  };
6882
7015
  return { pkg, dir, node };
6883
7016
  }
6884
7017
 
6885
7018
  // src/extract/kotlin.ts
6886
- import { promises as fs13 } from "fs";
6887
- import path15 from "path";
7019
+ import { promises as fs14 } from "fs";
7020
+ import path16 from "path";
6888
7021
  import { NodeType as NodeType11, serviceId as serviceId7 } from "@neat.is/types";
6889
7022
  var KOTLIN_SOURCE_EXT = ".kt";
6890
7023
  var JAVA_SOURCE_EXT = ".java";
@@ -6894,15 +7027,15 @@ async function dominantJvmSourceLanguage(dir) {
6894
7027
  let java = 0;
6895
7028
  async function recurse(current, depth) {
6896
7029
  if (depth > SOURCE_PROBE_MAX_DEPTH) return;
6897
- const entries = await fs13.readdir(current, { withFileTypes: true }).catch(() => []);
7030
+ const entries = await fs14.readdir(current, { withFileTypes: true }).catch(() => []);
6898
7031
  for (const entry of entries) {
6899
7032
  if (entry.isDirectory()) {
6900
7033
  if (IGNORED_DIRS.has(entry.name)) continue;
6901
- await recurse(path15.join(current, entry.name), depth + 1);
7034
+ await recurse(path16.join(current, entry.name), depth + 1);
6902
7035
  continue;
6903
7036
  }
6904
7037
  if (!entry.isFile()) continue;
6905
- const ext = path15.extname(entry.name).toLowerCase();
7038
+ const ext = path16.extname(entry.name).toLowerCase();
6906
7039
  if (ext === KOTLIN_SOURCE_EXT) kt++;
6907
7040
  else if (ext === JAVA_SOURCE_EXT) java++;
6908
7041
  }
@@ -6913,17 +7046,17 @@ async function dominantJvmSourceLanguage(dir) {
6913
7046
  return void 0;
6914
7047
  }
6915
7048
  async function readJvmManifest(dir) {
6916
- const pomPath = path15.join(dir, JAVA_MANIFESTS[0]);
6917
- const gradlePath = path15.join(dir, JAVA_MANIFESTS[1]);
6918
- const gradleKtsPath = path15.join(dir, JAVA_MANIFESTS[2]);
7049
+ const pomPath = path16.join(dir, JAVA_MANIFESTS[0]);
7050
+ const gradlePath = path16.join(dir, JAVA_MANIFESTS[1]);
7051
+ const gradleKtsPath = path16.join(dir, JAVA_MANIFESTS[2]);
6919
7052
  if (await exists(pomPath)) {
6920
- return parsePomXml(await fs13.readFile(pomPath, "utf8").catch(() => ""));
7053
+ return parsePomXml(await fs14.readFile(pomPath, "utf8").catch(() => ""));
6921
7054
  }
6922
7055
  if (await exists(gradlePath)) {
6923
- return parseGradle(await fs13.readFile(gradlePath, "utf8").catch(() => ""));
7056
+ return parseGradle(await fs14.readFile(gradlePath, "utf8").catch(() => ""));
6924
7057
  }
6925
7058
  if (await exists(gradleKtsPath)) {
6926
- return parseGradle(await fs13.readFile(gradleKtsPath, "utf8").catch(() => ""));
7059
+ return parseGradle(await fs14.readFile(gradleKtsPath, "utf8").catch(() => ""));
6927
7060
  }
6928
7061
  return null;
6929
7062
  }
@@ -6932,7 +7065,7 @@ async function discoverKotlinService(scanPath, dir) {
6932
7065
  if (await dominantJvmSourceLanguage(dir) !== "kotlin") return null;
6933
7066
  const manifest = await readJvmManifest(dir);
6934
7067
  if (!manifest) return null;
6935
- const name = manifest.name?.trim() || path15.basename(dir);
7068
+ const name = manifest.name?.trim() || path16.basename(dir);
6936
7069
  const dependencies = manifest.dependencies;
6937
7070
  const pkg = { name, dependencies };
6938
7071
  const node = {
@@ -6941,19 +7074,19 @@ async function discoverKotlinService(scanPath, dir) {
6941
7074
  name,
6942
7075
  language: "kotlin",
6943
7076
  dependencies,
6944
- repoPath: path15.relative(scanPath, dir)
7077
+ repoPath: path16.relative(scanPath, dir)
6945
7078
  };
6946
7079
  return { pkg, dir, node };
6947
7080
  }
6948
7081
 
6949
7082
  // src/extract/rust.ts
6950
- import { promises as fs14 } from "fs";
6951
- import path16 from "path";
7083
+ import { promises as fs15 } from "fs";
7084
+ import path17 from "path";
6952
7085
  import { parse as parseToml2 } from "smol-toml";
6953
7086
  import { NodeType as NodeType12, serviceId as serviceId8 } from "@neat.is/types";
6954
7087
  var CARGO_MANIFEST = "Cargo.toml";
6955
7088
  async function hasRustManifest(dir) {
6956
- return exists(path16.join(dir, CARGO_MANIFEST));
7089
+ return exists(path17.join(dir, CARGO_MANIFEST));
6957
7090
  }
6958
7091
  function parseCargoToml(source) {
6959
7092
  const dependencies = {};
@@ -6978,10 +7111,10 @@ function parseCargoToml(source) {
6978
7111
  return { name, dependencies };
6979
7112
  }
6980
7113
  async function discoverRustService(scanPath, dir) {
6981
- const cargoPath = path16.join(dir, CARGO_MANIFEST);
7114
+ const cargoPath = path17.join(dir, CARGO_MANIFEST);
6982
7115
  if (!await exists(cargoPath)) return null;
6983
- const manifest = parseCargoToml(await fs14.readFile(cargoPath, "utf8").catch(() => ""));
6984
- const name = manifest.name?.trim() || path16.basename(dir);
7116
+ const manifest = parseCargoToml(await fs15.readFile(cargoPath, "utf8").catch(() => ""));
7117
+ const name = manifest.name?.trim() || path17.basename(dir);
6985
7118
  const dependencies = manifest.dependencies;
6986
7119
  const pkg = { name, dependencies };
6987
7120
  const node = {
@@ -6990,18 +7123,18 @@ async function discoverRustService(scanPath, dir) {
6990
7123
  name,
6991
7124
  language: "rust",
6992
7125
  dependencies,
6993
- repoPath: path16.relative(scanPath, dir)
7126
+ repoPath: path17.relative(scanPath, dir)
6994
7127
  };
6995
7128
  return { pkg, dir, node };
6996
7129
  }
6997
7130
 
6998
7131
  // src/extract/cpp.ts
6999
- import { promises as fs15 } from "fs";
7000
- import path17 from "path";
7132
+ import { promises as fs16 } from "fs";
7133
+ import path18 from "path";
7001
7134
  import { NodeType as NodeType13, serviceId as serviceId9 } from "@neat.is/types";
7002
7135
  var CMAKE_MANIFEST = "CMakeLists.txt";
7003
7136
  async function hasCppManifest(dir) {
7004
- return exists(path17.join(dir, CMAKE_MANIFEST));
7137
+ return exists(path18.join(dir, CMAKE_MANIFEST));
7005
7138
  }
7006
7139
  function parseCMakeProjectName(source) {
7007
7140
  const withoutComments = source.replace(/#.*$/gm, "");
@@ -7009,10 +7142,10 @@ function parseCMakeProjectName(source) {
7009
7142
  return m?.[1];
7010
7143
  }
7011
7144
  async function discoverCppService(scanPath, dir) {
7012
- const cmakePath = path17.join(dir, CMAKE_MANIFEST);
7145
+ const cmakePath = path18.join(dir, CMAKE_MANIFEST);
7013
7146
  if (!await exists(cmakePath)) return null;
7014
- const source = await fs15.readFile(cmakePath, "utf8").catch(() => "");
7015
- const name = parseCMakeProjectName(source)?.trim() || path17.basename(dir);
7147
+ const source = await fs16.readFile(cmakePath, "utf8").catch(() => "");
7148
+ const name = parseCMakeProjectName(source)?.trim() || path18.basename(dir);
7016
7149
  const dependencies = {};
7017
7150
  const pkg = { name, dependencies };
7018
7151
  const node = {
@@ -7021,14 +7154,14 @@ async function discoverCppService(scanPath, dir) {
7021
7154
  name,
7022
7155
  language: "cpp",
7023
7156
  dependencies,
7024
- repoPath: path17.relative(scanPath, dir)
7157
+ repoPath: path18.relative(scanPath, dir)
7025
7158
  };
7026
7159
  return { pkg, dir, node };
7027
7160
  }
7028
7161
 
7029
7162
  // src/extract/dockerfile-service.ts
7030
- import { promises as fs16 } from "fs";
7031
- import path18 from "path";
7163
+ import { promises as fs17 } from "fs";
7164
+ import path19 from "path";
7032
7165
  import { NodeType as NodeType14, serviceId as serviceId10 } from "@neat.is/types";
7033
7166
  function languageForSourceExt(ext) {
7034
7167
  switch (ext) {
@@ -7070,12 +7203,12 @@ function languageForSourceExt(ext) {
7070
7203
  }
7071
7204
  var LANGUAGE_PRECEDENCE = ["typescript", "javascript", "python", "go", "ruby", "php", "csharp", "java", "kotlin", "rust", "cpp"];
7072
7205
  async function inferServiceLanguage(dir) {
7073
- const entries = await fs16.readdir(dir, { withFileTypes: true }).catch(() => []);
7206
+ const entries = await fs17.readdir(dir, { withFileTypes: true }).catch(() => []);
7074
7207
  const counts = /* @__PURE__ */ new Map();
7075
7208
  for (const entry of entries) {
7076
7209
  if (!entry.isFile()) continue;
7077
7210
  if (isNeatAuthoredSourceFile(entry.name)) continue;
7078
- const ext = path18.extname(entry.name).toLowerCase();
7211
+ const ext = path19.extname(entry.name).toLowerCase();
7079
7212
  if (!SERVICE_FILE_EXTENSIONS.has(ext)) continue;
7080
7213
  const lang = languageForSourceExt(ext);
7081
7214
  if (!lang) continue;
@@ -7094,7 +7227,7 @@ async function inferServiceLanguage(dir) {
7094
7227
  return best;
7095
7228
  }
7096
7229
  async function hasLanguageManifest(dir) {
7097
- return await exists(path18.join(dir, "package.json")) || await exists(path18.join(dir, "pyproject.toml")) || await exists(path18.join(dir, "requirements.txt")) || await exists(path18.join(dir, "setup.py")) || await exists(path18.join(dir, "go.mod")) || await exists(path18.join(dir, "Gemfile")) || await exists(path18.join(dir, "composer.json")) || // C#'s marker is a glob (`*.csproj` / `*.sln`), so it reads the directory
7230
+ return await exists(path19.join(dir, "package.json")) || await exists(path19.join(dir, "pyproject.toml")) || await exists(path19.join(dir, "requirements.txt")) || await exists(path19.join(dir, "setup.py")) || await exists(path19.join(dir, "go.mod")) || await exists(path19.join(dir, "Gemfile")) || await exists(path19.join(dir, "composer.json")) || // C#'s marker is a glob (`*.csproj` / `*.sln`), so it reads the directory
7098
7231
  // rather than probing a fixed filename (ADR-196).
7099
7232
  await hasCsharpProject(dir) || // Java (and its JVM sibling Kotlin, ADR-199) key on the same Maven / Gradle
7100
7233
  // build manifest (ADR-197), so this one check stands the Dockerfile fallback
@@ -7104,15 +7237,15 @@ async function hasLanguageManifest(dir) {
7104
7237
  await hasCppManifest(dir);
7105
7238
  }
7106
7239
  async function hasDockerfileDeclaredService(dir) {
7107
- if (!await exists(path18.join(dir, "Dockerfile"))) return false;
7240
+ if (!await exists(path19.join(dir, "Dockerfile"))) return false;
7108
7241
  return await inferServiceLanguage(dir) !== null;
7109
7242
  }
7110
7243
  async function discoverDockerfileService(scanPath, dir) {
7111
- if (!await exists(path18.join(dir, "Dockerfile"))) return null;
7244
+ if (!await exists(path19.join(dir, "Dockerfile"))) return null;
7112
7245
  if (await hasLanguageManifest(dir)) return null;
7113
7246
  const language = await inferServiceLanguage(dir);
7114
7247
  if (!language) return null;
7115
- const name = path18.basename(dir);
7248
+ const name = path19.basename(dir);
7116
7249
  const pkg = { name, dependencies: {} };
7117
7250
  const node = {
7118
7251
  id: serviceId10(name),
@@ -7120,23 +7253,23 @@ async function discoverDockerfileService(scanPath, dir) {
7120
7253
  name,
7121
7254
  language,
7122
7255
  dependencies: {},
7123
- repoPath: path18.relative(scanPath, dir)
7256
+ repoPath: path19.relative(scanPath, dir)
7124
7257
  };
7125
7258
  return { pkg, dir, node };
7126
7259
  }
7127
7260
 
7128
7261
  // src/extract/owners.ts
7129
- import { promises as fs17 } from "fs";
7130
- import path19 from "path";
7262
+ import { promises as fs18 } from "fs";
7263
+ import path20 from "path";
7131
7264
  import { minimatch } from "minimatch";
7132
7265
  async function loadCodeowners(scanPath) {
7133
7266
  const candidates = [
7134
- path19.join(scanPath, "CODEOWNERS"),
7135
- path19.join(scanPath, ".github", "CODEOWNERS")
7267
+ path20.join(scanPath, "CODEOWNERS"),
7268
+ path20.join(scanPath, ".github", "CODEOWNERS")
7136
7269
  ];
7137
7270
  for (const file of candidates) {
7138
7271
  if (await exists(file)) {
7139
- const raw = await fs17.readFile(file, "utf8");
7272
+ const raw = await fs18.readFile(file, "utf8");
7140
7273
  return parseCodeowners(raw);
7141
7274
  }
7142
7275
  }
@@ -7154,7 +7287,7 @@ function parseCodeowners(raw) {
7154
7287
  return { rules };
7155
7288
  }
7156
7289
  function matchOwner(file, repoPath) {
7157
- const normalized = repoPath.split(path19.sep).join("/");
7290
+ const normalized = repoPath.split(path20.sep).join("/");
7158
7291
  for (const rule of file.rules) {
7159
7292
  if (matchesPattern(rule.pattern, normalized)) return rule.owners;
7160
7293
  }
@@ -7170,7 +7303,7 @@ function matchesPattern(rawPattern, repoPath) {
7170
7303
  return false;
7171
7304
  }
7172
7305
  async function readPackageJsonAuthor(serviceDir) {
7173
- const pkgPath = path19.join(serviceDir, "package.json");
7306
+ const pkgPath = path20.join(serviceDir, "package.json");
7174
7307
  if (!await exists(pkgPath)) return null;
7175
7308
  try {
7176
7309
  const pkg = await readJson(pkgPath);
@@ -7207,36 +7340,36 @@ function workspaceGlobs(pkg) {
7207
7340
  return null;
7208
7341
  }
7209
7342
  async function hasPythonManifest(dir) {
7210
- return await exists(path20.join(dir, "pyproject.toml")) || await exists(path20.join(dir, "requirements.txt")) || await exists(path20.join(dir, "setup.py"));
7343
+ return await exists(path21.join(dir, "pyproject.toml")) || await exists(path21.join(dir, "requirements.txt")) || await exists(path21.join(dir, "setup.py"));
7211
7344
  }
7212
7345
  async function hasGoManifest(dir) {
7213
- return exists(path20.join(dir, "go.mod"));
7346
+ return exists(path21.join(dir, "go.mod"));
7214
7347
  }
7215
7348
  async function hasRubyManifest(dir) {
7216
- return exists(path20.join(dir, "Gemfile"));
7349
+ return exists(path21.join(dir, "Gemfile"));
7217
7350
  }
7218
7351
  async function hasPhpManifest(dir) {
7219
- return exists(path20.join(dir, "composer.json"));
7352
+ return exists(path21.join(dir, "composer.json"));
7220
7353
  }
7221
7354
  async function hasCsharpManifest(dir) {
7222
7355
  return hasCsharpProject(dir);
7223
7356
  }
7224
7357
  async function loadGitignore(scanPath) {
7225
- const gitignorePath = path20.join(scanPath, ".gitignore");
7358
+ const gitignorePath = path21.join(scanPath, ".gitignore");
7226
7359
  if (!await exists(gitignorePath)) return null;
7227
- const raw = await fs18.readFile(gitignorePath, "utf8");
7228
- return ignore().add(raw);
7360
+ const raw = await fs19.readFile(gitignorePath, "utf8");
7361
+ return ignore2().add(raw);
7229
7362
  }
7230
7363
  async function walkDirs(start, scanPath, options, visit) {
7231
7364
  async function recurse(current, depth) {
7232
7365
  if (depth > options.maxDepth) return;
7233
- const entries = await fs18.readdir(current, { withFileTypes: true }).catch(() => []);
7366
+ const entries = await fs19.readdir(current, { withFileTypes: true }).catch(() => []);
7234
7367
  for (const entry of entries) {
7235
7368
  if (!entry.isDirectory()) continue;
7236
7369
  if (IGNORED_DIRS.has(entry.name)) continue;
7237
- const child = path20.join(current, entry.name);
7370
+ const child = path21.join(current, entry.name);
7238
7371
  if (options.ig) {
7239
- const rel = path20.relative(scanPath, child).split(path20.sep).join("/");
7372
+ const rel = path21.relative(scanPath, child).split(path21.sep).join("/");
7240
7373
  if (rel && options.ig.ignores(rel + "/")) continue;
7241
7374
  }
7242
7375
  if (await isPythonVenvDir(child)) continue;
@@ -7252,8 +7385,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
7252
7385
  for (const raw of globs) {
7253
7386
  const pattern = raw.replace(/^\.\//, "");
7254
7387
  if (!pattern.includes("*")) {
7255
- const candidate = path20.join(scanPath, pattern);
7256
- if (await exists(path20.join(candidate, "package.json"))) found.add(candidate);
7388
+ const candidate = path21.join(scanPath, pattern);
7389
+ if (await exists(path21.join(candidate, "package.json"))) found.add(candidate);
7257
7390
  continue;
7258
7391
  }
7259
7392
  const segments = pattern.split("/");
@@ -7262,13 +7395,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
7262
7395
  if (seg.includes("*")) break;
7263
7396
  staticSegments.push(seg);
7264
7397
  }
7265
- const start = path20.join(scanPath, ...staticSegments);
7398
+ const start = path21.join(scanPath, ...staticSegments);
7266
7399
  if (!await exists(start)) continue;
7267
7400
  const hasDoubleStar = pattern.includes("**");
7268
7401
  const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
7269
7402
  await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
7270
- const rel = path20.relative(scanPath, dir).split(path20.sep).join("/");
7271
- if (minimatch2(rel, pattern) && await exists(path20.join(dir, "package.json"))) {
7403
+ const rel = path21.relative(scanPath, dir).split(path21.sep).join("/");
7404
+ if (minimatch2(rel, pattern) && await exists(path21.join(dir, "package.json"))) {
7272
7405
  found.add(dir);
7273
7406
  }
7274
7407
  });
@@ -7291,18 +7424,18 @@ function detectJsFramework(pkg) {
7291
7424
  async function detectJsServiceLanguage(dir, pkg) {
7292
7425
  const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
7293
7426
  if (deps["typescript"] !== void 0) return "typescript";
7294
- const entries = await fs18.readdir(dir).catch(() => []);
7427
+ const entries = await fs19.readdir(dir).catch(() => []);
7295
7428
  if (entries.some((name) => /^tsconfig(\..+)?\.json$/.test(name))) return "typescript";
7296
7429
  return "javascript";
7297
7430
  }
7298
7431
  async function discoverNodeService(scanPath, dir) {
7299
- const pkgPath = path20.join(dir, "package.json");
7432
+ const pkgPath = path21.join(dir, "package.json");
7300
7433
  if (!await exists(pkgPath)) return null;
7301
7434
  let pkg;
7302
7435
  try {
7303
7436
  pkg = await readJson(pkgPath);
7304
7437
  } catch (err) {
7305
- recordExtractionError("services", path20.relative(scanPath, pkgPath), err);
7438
+ recordExtractionError("services", path21.relative(scanPath, pkgPath), err);
7306
7439
  return null;
7307
7440
  }
7308
7441
  if (!pkg.name) return null;
@@ -7315,7 +7448,7 @@ async function discoverNodeService(scanPath, dir) {
7315
7448
  language,
7316
7449
  version: pkg.version,
7317
7450
  dependencies: pkg.dependencies ?? {},
7318
- repoPath: path20.relative(scanPath, dir),
7451
+ repoPath: path21.relative(scanPath, dir),
7319
7452
  ...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {},
7320
7453
  ...framework ? { framework } : {}
7321
7454
  };
@@ -7332,12 +7465,12 @@ async function discoverPyService(scanPath, dir) {
7332
7465
  language: "python",
7333
7466
  version: py.version,
7334
7467
  dependencies: py.dependencies,
7335
- repoPath: path20.relative(scanPath, dir)
7468
+ repoPath: path21.relative(scanPath, dir)
7336
7469
  };
7337
7470
  return { pkg, dir, node };
7338
7471
  }
7339
7472
  async function discoverServices(scanPath) {
7340
- const rootPkgPath = path20.join(scanPath, "package.json");
7473
+ const rootPkgPath = path21.join(scanPath, "package.json");
7341
7474
  let rootPkg = null;
7342
7475
  if (await exists(rootPkgPath)) {
7343
7476
  try {
@@ -7345,7 +7478,7 @@ async function discoverServices(scanPath) {
7345
7478
  } catch (err) {
7346
7479
  recordExtractionError(
7347
7480
  "services workspaces",
7348
- path20.relative(scanPath, rootPkgPath),
7481
+ path21.relative(scanPath, rootPkgPath),
7349
7482
  err
7350
7483
  );
7351
7484
  }
@@ -7366,7 +7499,7 @@ async function discoverServices(scanPath) {
7366
7499
  scanPath,
7367
7500
  { maxDepth: parseScanDepth(), ig },
7368
7501
  async (dir) => {
7369
- if (await exists(path20.join(dir, "package.json"))) {
7502
+ if (await exists(path21.join(dir, "package.json"))) {
7370
7503
  candidateDirs.push(dir);
7371
7504
  } else if (await hasPythonManifest(dir) || await hasGoManifest(dir) || await hasRubyManifest(dir) || await hasPhpManifest(dir) || await hasCsharpManifest(dir) || await hasJavaManifest(dir) || await hasRustManifest(dir) || await hasCppManifest(dir)) {
7372
7505
  candidateDirs.push(dir);
@@ -7394,8 +7527,8 @@ async function discoverServices(scanPath) {
7394
7527
  seenDirs.add(service.dir);
7395
7528
  const existingDir = seen.get(service.node.name);
7396
7529
  if (existingDir !== void 0) {
7397
- const a = path20.relative(scanPath, existingDir) || ".";
7398
- const b = path20.relative(scanPath, dir) || ".";
7530
+ const a = path21.relative(scanPath, existingDir) || ".";
7531
+ const b = path21.relative(scanPath, dir) || ".";
7399
7532
  console.warn(
7400
7533
  `[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
7401
7534
  );
@@ -7409,14 +7542,14 @@ async function discoverServices(scanPath) {
7409
7542
  const owner = await computeServiceOwner(codeowners, service.node.repoPath, service.dir);
7410
7543
  if (owner !== void 0) service.node.owner = owner;
7411
7544
  }
7412
- const resolvedDirs = out.map((s) => path20.resolve(s.dir));
7545
+ const resolvedDirs = out.map((s) => path21.resolve(s.dir));
7413
7546
  for (let i = 0; i < out.length; i++) {
7414
7547
  const self = resolvedDirs[i];
7415
7548
  const nested = [];
7416
7549
  for (let j = 0; j < out.length; j++) {
7417
7550
  if (i === j) continue;
7418
7551
  const other = resolvedDirs[j];
7419
- if (other.startsWith(self + path20.sep)) nested.push(other);
7552
+ if (other.startsWith(self + path21.sep)) nested.push(other);
7420
7553
  }
7421
7554
  out[i].excludeDirs = nested;
7422
7555
  }
@@ -7442,8 +7575,8 @@ function addServiceNodes(graph, services) {
7442
7575
  }
7443
7576
 
7444
7577
  // src/extract/aliases.ts
7445
- import path21 from "path";
7446
- import { promises as fs19 } from "fs";
7578
+ import path22 from "path";
7579
+ import { promises as fs20 } from "fs";
7447
7580
  import { parseAllDocuments } from "yaml";
7448
7581
  import { NodeType as NodeType16 } from "@neat.is/types";
7449
7582
  var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
@@ -7470,14 +7603,14 @@ function indexServicesByName(services) {
7470
7603
  const map = /* @__PURE__ */ new Map();
7471
7604
  for (const s of services) {
7472
7605
  map.set(s.node.name, s.node.id);
7473
- map.set(path21.basename(s.dir), s.node.id);
7606
+ map.set(path22.basename(s.dir), s.node.id);
7474
7607
  }
7475
7608
  return map;
7476
7609
  }
7477
7610
  async function collectComposeAliases(graph, scanPath, serviceIndex) {
7478
7611
  let composePath = null;
7479
7612
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
7480
- const abs = path21.join(scanPath, name);
7613
+ const abs = path22.join(scanPath, name);
7481
7614
  if (await exists(abs)) {
7482
7615
  composePath = abs;
7483
7616
  break;
@@ -7490,7 +7623,7 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
7490
7623
  } catch (err) {
7491
7624
  recordExtractionError(
7492
7625
  "aliases compose",
7493
- path21.relative(scanPath, composePath),
7626
+ path22.relative(scanPath, composePath),
7494
7627
  err
7495
7628
  );
7496
7629
  return;
@@ -7533,11 +7666,11 @@ function parseDockerfileLabels(content) {
7533
7666
  }
7534
7667
  async function collectDockerfileAliases(graph, services) {
7535
7668
  for (const service of services) {
7536
- const dockerfilePath = path21.join(service.dir, "Dockerfile");
7669
+ const dockerfilePath = path22.join(service.dir, "Dockerfile");
7537
7670
  if (!await exists(dockerfilePath)) continue;
7538
7671
  let content;
7539
7672
  try {
7540
- content = await fs19.readFile(dockerfilePath, "utf8");
7673
+ content = await fs20.readFile(dockerfilePath, "utf8");
7541
7674
  } catch (err) {
7542
7675
  recordExtractionError("aliases dockerfile", dockerfilePath, err);
7543
7676
  continue;
@@ -7549,15 +7682,15 @@ async function collectDockerfileAliases(graph, services) {
7549
7682
  async function walkYamlFiles(start, depth = 0, max = 5) {
7550
7683
  if (depth > max) return [];
7551
7684
  const out = [];
7552
- const entries = await fs19.readdir(start, { withFileTypes: true }).catch(() => []);
7685
+ const entries = await fs20.readdir(start, { withFileTypes: true }).catch(() => []);
7553
7686
  for (const entry of entries) {
7554
7687
  if (entry.isDirectory()) {
7555
7688
  if (IGNORED_DIRS.has(entry.name)) continue;
7556
- const child = path21.join(start, entry.name);
7689
+ const child = path22.join(start, entry.name);
7557
7690
  if (await isPythonVenvDir(child)) continue;
7558
7691
  out.push(...await walkYamlFiles(child, depth + 1, max));
7559
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path21.extname(entry.name))) {
7560
- out.push(path21.join(start, entry.name));
7692
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path22.extname(entry.name))) {
7693
+ out.push(path22.join(start, entry.name));
7561
7694
  }
7562
7695
  }
7563
7696
  return out;
@@ -7584,7 +7717,7 @@ function k8sServiceTarget(doc, byName) {
7584
7717
  async function collectK8sAliases(graph, scanPath, serviceIndex) {
7585
7718
  const files = await walkYamlFiles(scanPath);
7586
7719
  for (const file of files) {
7587
- const content = await fs19.readFile(file, "utf8");
7720
+ const content = await fs20.readFile(file, "utf8");
7588
7721
  let docs;
7589
7722
  try {
7590
7723
  docs = parseAllDocuments(content).map((d) => d.toJSON());
@@ -7608,14 +7741,14 @@ async function addServiceAliases(graph, scanPath, services) {
7608
7741
  }
7609
7742
 
7610
7743
  // src/extract/files.ts
7611
- import path22 from "path";
7744
+ import path23 from "path";
7612
7745
  async function addFiles(graph, services) {
7613
7746
  let nodesAdded = 0;
7614
7747
  let edgesAdded = 0;
7615
7748
  for (const service of services) {
7616
7749
  const filePaths = await walkSourceFiles(service.dir, service.excludeDirs);
7617
7750
  for (const filePath of filePaths) {
7618
- const relPath = toPosix(path22.relative(service.dir, filePath));
7751
+ const relPath = toPosix(path23.relative(service.dir, filePath));
7619
7752
  const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
7620
7753
  graph,
7621
7754
  service.pkg.name,
@@ -7630,7 +7763,7 @@ async function addFiles(graph, services) {
7630
7763
  }
7631
7764
 
7632
7765
  // src/extract/symbols.ts
7633
- import path23 from "path";
7766
+ import path24 from "path";
7634
7767
  import Parser3 from "tree-sitter";
7635
7768
  import JavaScript3 from "tree-sitter-javascript";
7636
7769
  import TypeScript from "tree-sitter-typescript";
@@ -8389,10 +8522,10 @@ async function addSymbols(graph, services) {
8389
8522
  for (const service of services) {
8390
8523
  const files = await loadSourceFiles(service.dir, service.excludeDirs);
8391
8524
  for (const file of files) {
8392
- const ext = path23.extname(file.path);
8525
+ const ext = path24.extname(file.path);
8393
8526
  const parser = parserForExt5(ext);
8394
8527
  if (!parser) continue;
8395
- const relPath = toPosix(path23.relative(service.dir, file.path));
8528
+ const relPath = toPosix(path24.relative(service.dir, file.path));
8396
8529
  let defs;
8397
8530
  try {
8398
8531
  const tree = parseSource3(parser, file.content);
@@ -8451,7 +8584,7 @@ async function addSymbols(graph, services) {
8451
8584
  }
8452
8585
 
8453
8586
  // src/extract/symbol-edges.ts
8454
- import path24 from "path";
8587
+ import path25 from "path";
8455
8588
  import Parser4 from "tree-sitter";
8456
8589
  import {
8457
8590
  EdgeType as EdgeType8,
@@ -8565,10 +8698,10 @@ async function addSymbolEdges(graph, services) {
8565
8698
  const tsPaths = await loadTsPathConfig(service.dir);
8566
8699
  const files = await loadSourceFiles(service.dir, service.excludeDirs);
8567
8700
  for (const file of files) {
8568
- const parser = parserForExt5(path24.extname(file.path));
8701
+ const parser = parserForExt5(path25.extname(file.path));
8569
8702
  if (!parser) continue;
8570
- const relPath = toPosix(path24.relative(service.dir, file.path));
8571
- const fileDir = path24.dirname(file.path);
8703
+ const relPath = toPosix(path25.relative(service.dir, file.path));
8704
+ const fileDir = path25.dirname(file.path);
8572
8705
  let root;
8573
8706
  try {
8574
8707
  root = parseSource3(parser, file.content).rootNode;
@@ -8702,7 +8835,7 @@ async function addSymbolEdges(graph, services) {
8702
8835
  }
8703
8836
 
8704
8837
  // src/extract/actions.ts
8705
- import path25 from "path";
8838
+ import path26 from "path";
8706
8839
  import Parser5 from "tree-sitter";
8707
8840
  import {
8708
8841
  EdgeType as EdgeType9,
@@ -8856,9 +8989,9 @@ async function addServerActions(graph, services) {
8856
8989
  const files = await loadSourceFiles(service.dir, service.excludeDirs);
8857
8990
  for (const file of files) {
8858
8991
  if (isTestPath(file.path)) continue;
8859
- const parser = parserForExt5(path25.extname(file.path));
8992
+ const parser = parserForExt5(path26.extname(file.path));
8860
8993
  if (!parser) continue;
8861
- const relPath = toPosix(path25.relative(service.dir, file.path));
8994
+ const relPath = toPosix(path26.relative(service.dir, file.path));
8862
8995
  let root;
8863
8996
  try {
8864
8997
  root = parseSource3(parser, file.content).rootNode;
@@ -8919,10 +9052,10 @@ async function addServerActions(graph, services) {
8919
9052
  }
8920
9053
  for (const file of files) {
8921
9054
  if (isTestPath(file.path)) continue;
8922
- const parser = parserForExt5(path25.extname(file.path));
9055
+ const parser = parserForExt5(path26.extname(file.path));
8923
9056
  if (!parser) continue;
8924
- const relPath = toPosix(path25.relative(service.dir, file.path));
8925
- const fileDir = path25.dirname(file.path);
9057
+ const relPath = toPosix(path26.relative(service.dir, file.path));
9058
+ const fileDir = path26.dirname(file.path);
8926
9059
  let root;
8927
9060
  try {
8928
9061
  root = parseSource3(parser, file.content).rootNode;
@@ -8974,7 +9107,7 @@ async function addServerActions(graph, services) {
8974
9107
  }
8975
9108
 
8976
9109
  // src/extract/databases/index.ts
8977
- import path34 from "path";
9110
+ import path35 from "path";
8978
9111
  import {
8979
9112
  EdgeType as EdgeType10,
8980
9113
  NodeType as NodeType20,
@@ -8985,9 +9118,9 @@ import {
8985
9118
  } from "@neat.is/types";
8986
9119
 
8987
9120
  // src/extract/databases/db-config-yaml.ts
8988
- import path26 from "path";
9121
+ import path27 from "path";
8989
9122
  async function parse(serviceDir) {
8990
- const yamlPath = path26.join(serviceDir, "db-config.yaml");
9123
+ const yamlPath = path27.join(serviceDir, "db-config.yaml");
8991
9124
  if (!await exists(yamlPath)) return [];
8992
9125
  const raw = await readYaml(yamlPath);
8993
9126
  return [
@@ -9004,12 +9137,12 @@ async function parse(serviceDir) {
9004
9137
  var dbConfigYamlParser = { name: "db-config.yaml", parse };
9005
9138
 
9006
9139
  // src/extract/databases/dotenv.ts
9007
- import { promises as fs21 } from "fs";
9008
- import path28 from "path";
9140
+ import { promises as fs22 } from "fs";
9141
+ import path29 from "path";
9009
9142
 
9010
9143
  // src/extract/databases/shared.ts
9011
- import { promises as fs20 } from "fs";
9012
- import path27 from "path";
9144
+ import { promises as fs21 } from "fs";
9145
+ import path28 from "path";
9013
9146
  function schemeToEngine(scheme) {
9014
9147
  const s = scheme.toLowerCase().split("+")[0];
9015
9148
  switch (s) {
@@ -9048,18 +9181,18 @@ function parseConnectionString(url) {
9048
9181
  }
9049
9182
  async function readIfExists(filePath) {
9050
9183
  try {
9051
- return await fs20.readFile(filePath, "utf8");
9184
+ return await fs21.readFile(filePath, "utf8");
9052
9185
  } catch {
9053
9186
  return null;
9054
9187
  }
9055
9188
  }
9056
9189
  async function resolveEnvVar(serviceDir, name) {
9057
- const entries = await fs20.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
9190
+ const entries = await fs21.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
9058
9191
  const envNames = entries.filter((e) => e.isFile() && (e.name === ".env" || e.name.startsWith(".env."))).map((e) => e.name);
9059
9192
  const rank = (n) => n === ".env.local" ? 0 : n === ".env" ? 1 : 2;
9060
9193
  envNames.sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
9061
9194
  for (const fileName of envNames) {
9062
- const content = await readIfExists(path27.join(serviceDir, fileName));
9195
+ const content = await readIfExists(path28.join(serviceDir, fileName));
9063
9196
  if (!content) continue;
9064
9197
  for (const line of content.split("\n")) {
9065
9198
  const trimmed = line.trim();
@@ -9078,7 +9211,7 @@ async function resolveEnvVar(serviceDir, name) {
9078
9211
  }
9079
9212
  async function findFirst(serviceDir, candidates) {
9080
9213
  for (const rel of candidates) {
9081
- const abs = path27.join(serviceDir, rel);
9214
+ const abs = path28.join(serviceDir, rel);
9082
9215
  const content = await readIfExists(abs);
9083
9216
  if (content !== null) return abs;
9084
9217
  }
@@ -9136,15 +9269,15 @@ function parseDotenvLine(line) {
9136
9269
  return { key, value };
9137
9270
  }
9138
9271
  async function parse2(serviceDir) {
9139
- const entries = await fs21.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
9272
+ const entries = await fs22.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
9140
9273
  const configs = [];
9141
9274
  const seen = /* @__PURE__ */ new Set();
9142
9275
  for (const entry of entries) {
9143
9276
  if (!entry.isFile()) continue;
9144
9277
  const match = isConfigFile(entry.name);
9145
9278
  if (!match.match || match.fileType !== "env") continue;
9146
- const filePath = path28.join(serviceDir, entry.name);
9147
- const content = await fs21.readFile(filePath, "utf8");
9279
+ const filePath = path29.join(serviceDir, entry.name);
9280
+ const content = await fs22.readFile(filePath, "utf8");
9148
9281
  for (const line of content.split("\n")) {
9149
9282
  const parsed = parseDotenvLine(line);
9150
9283
  if (!parsed) continue;
@@ -9162,9 +9295,9 @@ async function parse2(serviceDir) {
9162
9295
  var dotenvParser = { name: ".env", parse: parse2 };
9163
9296
 
9164
9297
  // src/extract/databases/prisma.ts
9165
- import path29 from "path";
9298
+ import path30 from "path";
9166
9299
  async function parse3(serviceDir) {
9167
- const schemaPath = path29.join(serviceDir, "prisma", "schema.prisma");
9300
+ const schemaPath = path30.join(serviceDir, "prisma", "schema.prisma");
9168
9301
  const content = await readIfExists(schemaPath);
9169
9302
  if (!content) return [];
9170
9303
  const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
@@ -9319,10 +9452,10 @@ async function parse5(serviceDir) {
9319
9452
  var knexParser = { name: "knex", parse: parse5 };
9320
9453
 
9321
9454
  // src/extract/databases/ormconfig.ts
9322
- import path30 from "path";
9455
+ import path31 from "path";
9323
9456
  async function parse6(serviceDir) {
9324
9457
  for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
9325
- const abs = path30.join(serviceDir, candidate);
9458
+ const abs = path31.join(serviceDir, candidate);
9326
9459
  if (!await exists(abs)) continue;
9327
9460
  const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
9328
9461
  const entries = Array.isArray(raw) ? raw : [raw];
@@ -9380,9 +9513,9 @@ async function parse7(serviceDir) {
9380
9513
  var typeormParser = { name: "typeorm", parse: parse7 };
9381
9514
 
9382
9515
  // src/extract/databases/sequelize.ts
9383
- import path31 from "path";
9516
+ import path32 from "path";
9384
9517
  async function parse8(serviceDir) {
9385
- const configPath = path31.join(serviceDir, "config", "config.json");
9518
+ const configPath = path32.join(serviceDir, "config", "config.json");
9386
9519
  if (!await exists(configPath)) return [];
9387
9520
  const raw = await readJson(configPath);
9388
9521
  const out = [];
@@ -9408,8 +9541,8 @@ async function parse8(serviceDir) {
9408
9541
  var sequelizeParser = { name: "sequelize", parse: parse8 };
9409
9542
 
9410
9543
  // src/extract/databases/csharp.ts
9411
- import { promises as fs22 } from "fs";
9412
- import path32 from "path";
9544
+ import { promises as fs23 } from "fs";
9545
+ import path33 from "path";
9413
9546
  var CS_EXT = ".cs";
9414
9547
  var NPGSQL_GATE = /\bUseNpgsql\b|\bNpgsql\b/;
9415
9548
  var REDIS_GATE = /\bConnectionMultiplexer\b|\bStackExchange\.Redis\b|\bConfigurationOptions\.Parse\b|\bAddStackExchangeRedisCache\b/;
@@ -9456,12 +9589,12 @@ function parseRedisEndpoint(raw) {
9456
9589
  return { host, port, database: "", engine: "redis", engineVersion: "unknown" };
9457
9590
  }
9458
9591
  async function resolveEnvUpTree(startDir, name) {
9459
- let dir = path32.resolve(startDir);
9592
+ let dir = path33.resolve(startDir);
9460
9593
  for (let depth = 0; depth < 12; depth++) {
9461
9594
  const value = await resolveEnvVar(dir, name);
9462
9595
  if (value !== null) return value;
9463
- const atRepoRoot = await fs22.access(path32.join(dir, ".git")).then(() => true).catch(() => false);
9464
- const parent = path32.dirname(dir);
9596
+ const atRepoRoot = await fs23.access(path33.join(dir, ".git")).then(() => true).catch(() => false);
9597
+ const parent = path33.dirname(dir);
9465
9598
  if (atRepoRoot || parent === dir) break;
9466
9599
  dir = parent;
9467
9600
  }
@@ -9516,12 +9649,12 @@ async function resolveConfigs(literals, keys, serviceDir, looksLike, parse11) {
9516
9649
  }
9517
9650
  async function parse9(serviceDir) {
9518
9651
  const files = (await walkSourceFiles(serviceDir).catch(() => [])).filter(
9519
- (f) => path32.extname(f) === CS_EXT
9652
+ (f) => path33.extname(f) === CS_EXT
9520
9653
  );
9521
9654
  if (files.length === 0) return [];
9522
9655
  const sources = [];
9523
9656
  for (const file of files) {
9524
- const content = await fs22.readFile(file, "utf8").catch(() => null);
9657
+ const content = await fs23.readFile(file, "utf8").catch(() => null);
9525
9658
  if (content !== null) sources.push({ file, content });
9526
9659
  }
9527
9660
  let pgGateFile = null;
@@ -9561,7 +9694,7 @@ async function parse9(serviceDir) {
9561
9694
  var csharpParser = { name: "csharp", parse: parse9 };
9562
9695
 
9563
9696
  // src/extract/databases/docker-compose.ts
9564
- import path33 from "path";
9697
+ import path34 from "path";
9565
9698
  function portFromService(svc) {
9566
9699
  for (const raw of svc.ports ?? []) {
9567
9700
  const str = String(raw);
@@ -9588,7 +9721,7 @@ function databaseFromEnv(svc) {
9588
9721
  }
9589
9722
  async function parse10(serviceDir) {
9590
9723
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
9591
- const abs = path33.join(serviceDir, name);
9724
+ const abs = path34.join(serviceDir, name);
9592
9725
  if (!await exists(abs)) continue;
9593
9726
  const raw = await readYaml(abs);
9594
9727
  if (!raw?.services) return [];
@@ -9761,7 +9894,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
9761
9894
  discoveredVia: mergedDiscoveredVia
9762
9895
  });
9763
9896
  }
9764
- const relConfigFile = toPosix(path34.relative(service.dir, config.sourceFile));
9897
+ const relConfigFile = toPosix(path35.relative(service.dir, config.sourceFile));
9765
9898
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
9766
9899
  graph,
9767
9900
  service.pkg.name,
@@ -9770,7 +9903,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
9770
9903
  );
9771
9904
  nodesAdded += fn;
9772
9905
  edgesAdded += fe;
9773
- const evidenceFile = toPosix(path34.relative(scanPath, config.sourceFile));
9906
+ const evidenceFile = toPosix(path35.relative(scanPath, config.sourceFile));
9774
9907
  const edge = {
9775
9908
  id: extractedEdgeId(fileNodeId, dbNode.id, EdgeType10.CONNECTS_TO),
9776
9909
  source: fileNodeId,
@@ -9791,15 +9924,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
9791
9924
  if (allConfigs.length === 1) {
9792
9925
  const primary = allConfigs[0];
9793
9926
  service.node.dbConnectionTarget = primary.port ? `${primary.host}:${primary.port}` : primary.host;
9794
- const relPath = path34.relative(scanPath, primary.sourceFile);
9927
+ const relPath = path35.relative(scanPath, primary.sourceFile);
9795
9928
  const cfgId = configId(relPath);
9796
9929
  if (!graph.hasNode(cfgId)) {
9797
9930
  const cfgNode = {
9798
9931
  id: cfgId,
9799
9932
  type: NodeType20.ConfigNode,
9800
- name: path34.basename(primary.sourceFile),
9933
+ name: path35.basename(primary.sourceFile),
9801
9934
  path: relPath,
9802
- fileType: isConfigFile(path34.basename(primary.sourceFile)).fileType || "config"
9935
+ fileType: isConfigFile(path35.basename(primary.sourceFile)).fileType || "config"
9803
9936
  };
9804
9937
  graph.addNode(cfgId, cfgNode);
9805
9938
  nodesAdded++;
@@ -9839,8 +9972,8 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
9839
9972
  }
9840
9973
 
9841
9974
  // src/extract/configs.ts
9842
- import { promises as fs23 } from "fs";
9843
- import path35 from "path";
9975
+ import { promises as fs24 } from "fs";
9976
+ import path36 from "path";
9844
9977
  import {
9845
9978
  EdgeType as EdgeType11,
9846
9979
  NodeType as NodeType21,
@@ -9849,15 +9982,15 @@ import {
9849
9982
  confidenceForExtracted as confidenceForExtracted8
9850
9983
  } from "@neat.is/types";
9851
9984
  async function walkConfigFiles(dir, excludeDirs = []) {
9852
- const excluded = new Set(excludeDirs.map((d) => path35.resolve(d)));
9985
+ const excluded = new Set(excludeDirs.map((d) => path36.resolve(d)));
9853
9986
  const out = [];
9854
9987
  async function walk11(current) {
9855
- const entries = await fs23.readdir(current, { withFileTypes: true });
9988
+ const entries = await fs24.readdir(current, { withFileTypes: true });
9856
9989
  for (const entry of entries) {
9857
- const full = path35.join(current, entry.name);
9990
+ const full = path36.join(current, entry.name);
9858
9991
  if (entry.isDirectory()) {
9859
9992
  if (IGNORED_DIRS.has(entry.name)) continue;
9860
- if (excluded.has(path35.resolve(full))) continue;
9993
+ if (excluded.has(path36.resolve(full))) continue;
9861
9994
  if (await isPythonVenvDir(full)) continue;
9862
9995
  await walk11(full);
9863
9996
  } else if (entry.isFile() && isConfigFile(entry.name).match) {
@@ -9874,19 +10007,19 @@ async function addConfigNodes(graph, services, scanPath) {
9874
10007
  for (const service of services) {
9875
10008
  const configFiles = await walkConfigFiles(service.dir, service.excludeDirs);
9876
10009
  for (const file of configFiles) {
9877
- const relPath = path35.relative(scanPath, file);
10010
+ const relPath = path36.relative(scanPath, file);
9878
10011
  const node = {
9879
10012
  id: configId2(relPath),
9880
10013
  type: NodeType21.ConfigNode,
9881
- name: path35.basename(file),
10014
+ name: path36.basename(file),
9882
10015
  path: relPath,
9883
- fileType: isConfigFile(path35.basename(file)).fileType
10016
+ fileType: isConfigFile(path36.basename(file)).fileType
9884
10017
  };
9885
10018
  if (!graph.hasNode(node.id)) {
9886
10019
  graph.addNode(node.id, node);
9887
10020
  nodesAdded++;
9888
10021
  }
9889
- const relToService = toPosix(path35.relative(service.dir, file));
10022
+ const relToService = toPosix(path36.relative(service.dir, file));
9890
10023
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
9891
10024
  graph,
9892
10025
  service.pkg.name,
@@ -9902,7 +10035,7 @@ async function addConfigNodes(graph, services, scanPath) {
9902
10035
  type: EdgeType11.CONFIGURED_BY,
9903
10036
  provenance: Provenance11.EXTRACTED,
9904
10037
  confidence: confidenceForExtracted8("structural"),
9905
- evidence: { file: relPath.split(path35.sep).join("/") }
10038
+ evidence: { file: relPath.split(path36.sep).join("/") }
9906
10039
  };
9907
10040
  if (!graph.hasEdge(edge.id)) {
9908
10041
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -9914,8 +10047,8 @@ async function addConfigNodes(graph, services, scanPath) {
9914
10047
  }
9915
10048
 
9916
10049
  // src/extract/proto.ts
9917
- import { promises as fs24 } from "fs";
9918
- import path36 from "path";
10050
+ import { promises as fs25 } from "fs";
10051
+ import path37 from "path";
9919
10052
  import {
9920
10053
  EdgeType as EdgeType12,
9921
10054
  NodeType as NodeType22,
@@ -9960,18 +10093,18 @@ function grpcMethodsFromProto(content, fqPackage) {
9960
10093
  return out;
9961
10094
  }
9962
10095
  async function walkProtoFiles(dir, excludeDirs = []) {
9963
- const excluded = new Set(excludeDirs.map((d) => path36.resolve(d)));
10096
+ const excluded = new Set(excludeDirs.map((d) => path37.resolve(d)));
9964
10097
  const out = [];
9965
10098
  async function walk11(current) {
9966
- const entries = await fs24.readdir(current, { withFileTypes: true }).catch(() => []);
10099
+ const entries = await fs25.readdir(current, { withFileTypes: true }).catch(() => []);
9967
10100
  for (const entry of entries) {
9968
- const full = path36.join(current, entry.name);
10101
+ const full = path37.join(current, entry.name);
9969
10102
  if (entry.isDirectory()) {
9970
10103
  if (IGNORED_DIRS.has(entry.name)) continue;
9971
- if (excluded.has(path36.resolve(full))) continue;
10104
+ if (excluded.has(path37.resolve(full))) continue;
9972
10105
  if (await isPythonVenvDir(full)) continue;
9973
10106
  await walk11(full);
9974
- } else if (entry.isFile() && path36.extname(entry.name) === PROTO_EXTENSION) {
10107
+ } else if (entry.isFile() && path37.extname(entry.name) === PROTO_EXTENSION) {
9975
10108
  out.push(full);
9976
10109
  }
9977
10110
  }
@@ -9986,10 +10119,10 @@ async function addGrpcMethods(graph, services) {
9986
10119
  const protoPaths = await walkProtoFiles(service.dir, service.excludeDirs);
9987
10120
  for (const protoPath of protoPaths) {
9988
10121
  if (isTestPath(protoPath)) continue;
9989
- const relFile = toPosix(path36.relative(service.dir, protoPath));
10122
+ const relFile = toPosix(path37.relative(service.dir, protoPath));
9990
10123
  let content;
9991
10124
  try {
9992
- content = await fs24.readFile(protoPath, "utf8");
10125
+ content = await fs25.readFile(protoPath, "utf8");
9993
10126
  } catch (err) {
9994
10127
  recordExtractionError("proto extraction", protoPath, err);
9995
10128
  continue;
@@ -10052,7 +10185,7 @@ import {
10052
10185
  } from "@neat.is/types";
10053
10186
 
10054
10187
  // src/extract/calls/http.ts
10055
- import path37 from "path";
10188
+ import path38 from "path";
10056
10189
  import Parser6 from "tree-sitter";
10057
10190
  import JavaScript4 from "tree-sitter-javascript";
10058
10191
  import TypeScript2 from "tree-sitter-typescript";
@@ -10140,7 +10273,7 @@ async function addHttpCallEdges(graph, services) {
10140
10273
  const seen = /* @__PURE__ */ new Set();
10141
10274
  for (const file of files) {
10142
10275
  if (isTestPath(file.path)) continue;
10143
- const parser = parserForExt(path37.extname(file.path), parserCache);
10276
+ const parser = parserForExt(path38.extname(file.path), parserCache);
10144
10277
  let sites;
10145
10278
  try {
10146
10279
  sites = callsFromSource(file.content, parser, knownHosts);
@@ -10149,7 +10282,7 @@ async function addHttpCallEdges(graph, services) {
10149
10282
  continue;
10150
10283
  }
10151
10284
  if (sites.length === 0) continue;
10152
- const relFile = toPosix(path37.relative(service.dir, file.path));
10285
+ const relFile = toPosix(path38.relative(service.dir, file.path));
10153
10286
  for (const site of sites) {
10154
10287
  const targetId = hostToNodeId.get(site.host);
10155
10288
  if (!targetId || targetId === service.node.id) continue;
@@ -10202,7 +10335,7 @@ async function addHttpCallEdges(graph, services) {
10202
10335
  }
10203
10336
 
10204
10337
  // src/extract/calls/route-match.ts
10205
- import path38 from "path";
10338
+ import path39 from "path";
10206
10339
  import Parser7 from "tree-sitter";
10207
10340
  import JavaScript5 from "tree-sitter-javascript";
10208
10341
  import {
@@ -10487,7 +10620,7 @@ async function addRouteCallEdges(graph, services) {
10487
10620
  const seen = /* @__PURE__ */ new Set();
10488
10621
  for (const file of files) {
10489
10622
  if (isTestPath(file.path)) continue;
10490
- if (!JS_CLIENT_EXTENSIONS.has(path38.extname(file.path))) continue;
10623
+ if (!JS_CLIENT_EXTENSIONS.has(path39.extname(file.path))) continue;
10491
10624
  let sites;
10492
10625
  try {
10493
10626
  sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
@@ -10496,7 +10629,7 @@ async function addRouteCallEdges(graph, services) {
10496
10629
  continue;
10497
10630
  }
10498
10631
  if (sites.length === 0) continue;
10499
- const relFile = toPosix(path38.relative(service.dir, file.path));
10632
+ const relFile = toPosix(path39.relative(service.dir, file.path));
10500
10633
  for (const site of sites) {
10501
10634
  if (site.host === null) {
10502
10635
  const dedupKey2 = `${relFile}|unresolved-host`;
@@ -10588,7 +10721,7 @@ async function addRouteCallEdges(graph, services) {
10588
10721
  }
10589
10722
 
10590
10723
  // src/extract/calls/kafka.ts
10591
- import path39 from "path";
10724
+ import path40 from "path";
10592
10725
  import { infraId as infraId2 } from "@neat.is/types";
10593
10726
  var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
10594
10727
  var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
@@ -10676,13 +10809,13 @@ function kafkaEndpointsFromFile(file, serviceDir) {
10676
10809
  // call sites — verified-call-site tier (ADR-066).
10677
10810
  confidenceKind: "verified-call-site",
10678
10811
  evidence: {
10679
- file: path39.relative(serviceDir, file.path),
10812
+ file: path40.relative(serviceDir, file.path),
10680
10813
  line,
10681
10814
  snippet: snippet(file.content, line)
10682
10815
  }
10683
10816
  });
10684
10817
  };
10685
- if (path39.extname(file.path) === ".go") {
10818
+ if (path40.extname(file.path) === ".go") {
10686
10819
  goSaramaEndpoints(file.content, make);
10687
10820
  } else {
10688
10821
  for (const { topic } of findAll(PRODUCER_TOPIC_RE, file.content)) make(topic, "PUBLISHES_TO");
@@ -10692,7 +10825,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
10692
10825
  }
10693
10826
 
10694
10827
  // src/extract/calls/redis.ts
10695
- import path40 from "path";
10828
+ import path41 from "path";
10696
10829
  import { infraId as infraId3 } from "@neat.is/types";
10697
10830
  var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
10698
10831
  function redisEndpointsFromFile(file, serviceDir) {
@@ -10715,7 +10848,7 @@ function redisEndpointsFromFile(file, serviceDir) {
10715
10848
  // support tier (ADR-066).
10716
10849
  confidenceKind: "url-with-structural-support",
10717
10850
  evidence: {
10718
- file: path40.relative(serviceDir, file.path),
10851
+ file: path41.relative(serviceDir, file.path),
10719
10852
  line,
10720
10853
  snippet: snippet(file.content, line)
10721
10854
  }
@@ -10725,7 +10858,7 @@ function redisEndpointsFromFile(file, serviceDir) {
10725
10858
  }
10726
10859
 
10727
10860
  // src/extract/calls/aws.ts
10728
- import path41 from "path";
10861
+ import path42 from "path";
10729
10862
  import { infraId as infraId4 } from "@neat.is/types";
10730
10863
  var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
10731
10864
  var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
@@ -10759,7 +10892,7 @@ function awsEndpointsFromFile(file, serviceDir) {
10759
10892
  // (ADR-066).
10760
10893
  confidenceKind: "verified-call-site",
10761
10894
  evidence: {
10762
- file: path41.relative(serviceDir, file.path),
10895
+ file: path42.relative(serviceDir, file.path),
10763
10896
  line,
10764
10897
  snippet: snippet(file.content, line)
10765
10898
  }
@@ -10783,7 +10916,7 @@ function awsEndpointsFromFile(file, serviceDir) {
10783
10916
  }
10784
10917
 
10785
10918
  // src/extract/calls/grpc.ts
10786
- import path42 from "path";
10919
+ import path43 from "path";
10787
10920
  import { infraId as infraId5 } from "@neat.is/types";
10788
10921
  var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
10789
10922
  var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
@@ -10842,7 +10975,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
10842
10975
  // tier (ADR-066).
10843
10976
  confidenceKind: "verified-call-site",
10844
10977
  evidence: {
10845
- file: path42.relative(serviceDir, file.path),
10978
+ file: path43.relative(serviceDir, file.path),
10846
10979
  line,
10847
10980
  snippet: snippet(file.content, line)
10848
10981
  }
@@ -10852,7 +10985,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
10852
10985
  }
10853
10986
 
10854
10987
  // src/extract/calls/supabase.ts
10855
- import path43 from "path";
10988
+ import path44 from "path";
10856
10989
  import { infraId as infraId6 } from "@neat.is/types";
10857
10990
  var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
10858
10991
  var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
@@ -10911,7 +11044,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
10911
11044
  // tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
10912
11045
  confidenceKind: "verified-call-site",
10913
11046
  evidence: {
10914
- file: path43.relative(serviceDir, file.path),
11047
+ file: path44.relative(serviceDir, file.path),
10915
11048
  line,
10916
11049
  snippet: snippet(file.content, line)
10917
11050
  }
@@ -10938,7 +11071,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
10938
11071
  edgeType: "CALLS",
10939
11072
  confidenceKind: "verified-call-site",
10940
11073
  evidence: {
10941
- file: path43.relative(serviceDir, file.path),
11074
+ file: path44.relative(serviceDir, file.path),
10942
11075
  line,
10943
11076
  snippet: snippet(file.content, line)
10944
11077
  }
@@ -10949,7 +11082,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
10949
11082
  }
10950
11083
 
10951
11084
  // src/extract/calls/firestore.ts
10952
- import path44 from "path";
11085
+ import path45 from "path";
10953
11086
  import Parser8 from "tree-sitter";
10954
11087
  import JavaScript6 from "tree-sitter-javascript";
10955
11088
  import { infraId as infraId7 } from "@neat.is/types";
@@ -11122,7 +11255,7 @@ function firestoreEndpointsFromFile(file, serviceDir, imported) {
11122
11255
  if (!hasClient && !hasAdmin && importedNames.size === 0) return [];
11123
11256
  const ownSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
11124
11257
  const fileSdk = !hasClient && !hasAdmin ? imported?.sdk ?? null : ownSdk;
11125
- const tree = parseSource3(parserForExt2(path44.extname(file.path)), file.content);
11258
+ const tree = parseSource3(parserForExt2(path45.extname(file.path)), file.content);
11126
11259
  const clientVars = firestoreClientVars(tree.rootNode);
11127
11260
  for (const n of importedNames) clientVars.add(n);
11128
11261
  const collLine = /* @__PURE__ */ new Map();
@@ -11227,7 +11360,7 @@ function firestoreEndpointsFromFile(file, serviceDir, imported) {
11227
11360
  ...columnSet.size > 0 ? { columns: [...columnSet] } : {},
11228
11361
  ...sdkWrites ? { sdkWrites } : {},
11229
11362
  evidence: {
11230
- file: path44.relative(serviceDir, file.path),
11363
+ file: path45.relative(serviceDir, file.path),
11231
11364
  line,
11232
11365
  snippet: snippet(file.content, line)
11233
11366
  }
@@ -11287,7 +11420,7 @@ function sdkOf(content) {
11287
11420
  return hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
11288
11421
  }
11289
11422
  function parseOrNull(file) {
11290
- const ext = path44.extname(file.path);
11423
+ const ext = path45.extname(file.path);
11291
11424
  if (!(ext in GRAMMAR_BY_EXT)) return null;
11292
11425
  try {
11293
11426
  return parseSource3(parserForExt2(ext), file.content).rootNode;
@@ -11303,7 +11436,7 @@ async function firestoreCrossFileEndpoints(files, serviceDir) {
11303
11436
  if (!root) continue;
11304
11437
  const names = exportedFirestoreClients(root);
11305
11438
  if (names.size === 0) continue;
11306
- registry.set(toPosix(path44.relative(serviceDir, f.path)), { names, sdk: sdkOf(f.content) });
11439
+ registry.set(toPosix(path45.relative(serviceDir, f.path)), { names, sdk: sdkOf(f.content) });
11307
11440
  }
11308
11441
  if (registry.size === 0) return [];
11309
11442
  const out = [];
@@ -11316,7 +11449,7 @@ async function firestoreCrossFileEndpoints(files, serviceDir) {
11316
11449
  const names = /* @__PURE__ */ new Set();
11317
11450
  let sdk = null;
11318
11451
  for (const b of bindings) {
11319
- const rel = await resolveJsImport(b.specifier, path44.dirname(f.path), serviceDir, null);
11452
+ const rel = await resolveJsImport(b.specifier, path45.dirname(f.path), serviceDir, null);
11320
11453
  if (!rel) continue;
11321
11454
  const entry = registry.get(rel);
11322
11455
  if (!entry || !entry.names.has(b.imported)) continue;
@@ -11330,7 +11463,7 @@ async function firestoreCrossFileEndpoints(files, serviceDir) {
11330
11463
  }
11331
11464
 
11332
11465
  // src/extract/calls/mongoose.ts
11333
- import path45 from "path";
11466
+ import path46 from "path";
11334
11467
  import { infraId as infraId8 } from "@neat.is/types";
11335
11468
  var MONGOOSE_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongoose['"`]/;
11336
11469
  var MONGODB_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongodb['"`]/;
@@ -11481,7 +11614,7 @@ function endpoint(r, file, serviceDir, matchText) {
11481
11614
  kind: r.kind,
11482
11615
  edgeType: "CALLS",
11483
11616
  confidenceKind: "verified-call-site",
11484
- evidence: { file: path45.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
11617
+ evidence: { file: path46.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
11485
11618
  };
11486
11619
  }
11487
11620
  function mongooseEndpointsFromFile(file, serviceDir) {
@@ -11571,7 +11704,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
11571
11704
  const registry = /* @__PURE__ */ new Map();
11572
11705
  for (const f of mongooseFiles) {
11573
11706
  const fx = fileExportsOf(f.content, pluralizeOn);
11574
- if (fx) registry.set(toPosix(path45.relative(serviceDir, f.path)), fx);
11707
+ if (fx) registry.set(toPosix(path46.relative(serviceDir, f.path)), fx);
11575
11708
  }
11576
11709
  if (registry.size === 0) return [];
11577
11710
  const out = [];
@@ -11582,7 +11715,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
11582
11715
  const directColl = /* @__PURE__ */ new Map();
11583
11716
  const nsExports = /* @__PURE__ */ new Map();
11584
11717
  for (const b of bindings) {
11585
- const resolvedRel = await resolveJsImport(b.specifier, path45.dirname(f.path), serviceDir, null);
11718
+ const resolvedRel = await resolveJsImport(b.specifier, path46.dirname(f.path), serviceDir, null);
11586
11719
  if (!resolvedRel) continue;
11587
11720
  const fx = registry.get(resolvedRel);
11588
11721
  if (!fx) continue;
@@ -11625,7 +11758,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
11625
11758
  }
11626
11759
 
11627
11760
  // src/extract/calls/sqlalchemy.ts
11628
- import path46 from "path";
11761
+ import path47 from "path";
11629
11762
  import Parser9 from "tree-sitter";
11630
11763
  import Python5 from "tree-sitter-python";
11631
11764
  import { infraId as infraId9 } from "@neat.is/types";
@@ -11764,7 +11897,7 @@ function sqlalchemyForeignKeys(file, serviceDir) {
11764
11897
  childTable,
11765
11898
  parentTable,
11766
11899
  evidence: {
11767
- file: path46.relative(serviceDir, file.path),
11900
+ file: path47.relative(serviceDir, file.path),
11768
11901
  line,
11769
11902
  snippet: snippet(file.content, line)
11770
11903
  }
@@ -11789,7 +11922,7 @@ function sqlalchemyEndpointsFromFile(file, serviceDir) {
11789
11922
  confidenceKind: "verified-call-site",
11790
11923
  ...columns && columns.length > 0 ? { columns } : {},
11791
11924
  evidence: {
11792
- file: path46.relative(serviceDir, file.path),
11925
+ file: path47.relative(serviceDir, file.path),
11793
11926
  line,
11794
11927
  snippet: snippet(file.content, line)
11795
11928
  }
@@ -11896,7 +12029,7 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
11896
12029
  edgeType: "CALLS",
11897
12030
  confidenceKind: "verified-call-site",
11898
12031
  evidence: {
11899
- file: path46.relative(serviceDir, file.path),
12032
+ file: path47.relative(serviceDir, file.path),
11900
12033
  line,
11901
12034
  snippet: snippet(file.content, line)
11902
12035
  }
@@ -11907,7 +12040,7 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
11907
12040
  }
11908
12041
 
11909
12042
  // src/extract/calls/socket.ts
11910
- import path47 from "path";
12043
+ import path48 from "path";
11911
12044
  import Parser10 from "tree-sitter";
11912
12045
  import Python6 from "tree-sitter-python";
11913
12046
  import { infraId as infraId10 } from "@neat.is/types";
@@ -11983,7 +12116,7 @@ function socketEndpointsFromFile(file, serviceDir) {
11983
12116
  // A verified `socket.socket(...)` + `.connect((host, port))` call site.
11984
12117
  confidenceKind: "verified-call-site",
11985
12118
  evidence: {
11986
- file: path47.relative(serviceDir, file.path),
12119
+ file: path48.relative(serviceDir, file.path),
11987
12120
  line,
11988
12121
  snippet: snippet(file.content, line)
11989
12122
  }
@@ -11993,7 +12126,7 @@ function socketEndpointsFromFile(file, serviceDir) {
11993
12126
  }
11994
12127
 
11995
12128
  // src/extract/calls/django-orm.ts
11996
- import path48 from "path";
12129
+ import path49 from "path";
11997
12130
  import Parser11 from "tree-sitter";
11998
12131
  import Python7 from "tree-sitter-python";
11999
12132
  import { infraId as infraId11 } from "@neat.is/types";
@@ -12063,7 +12196,7 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
12063
12196
  const tree = parseSource8(makePyParser5(), file.content);
12064
12197
  const out = [];
12065
12198
  const seen = /* @__PURE__ */ new Set();
12066
- const defaultAppLabel = path48.basename(path48.dirname(file.path));
12199
+ const defaultAppLabel = path49.basename(path49.dirname(file.path));
12067
12200
  walk5(tree.rootNode, (node) => {
12068
12201
  if (node.type !== "class_definition") return;
12069
12202
  if (!extendsDjangoModel(node)) return;
@@ -12081,14 +12214,14 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
12081
12214
  kind: "sql-table",
12082
12215
  edgeType: "CALLS",
12083
12216
  confidenceKind: "verified-call-site",
12084
- evidence: { file: path48.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
12217
+ evidence: { file: path49.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
12085
12218
  });
12086
12219
  });
12087
12220
  return out;
12088
12221
  }
12089
12222
 
12090
12223
  // src/extract/calls/drizzle.ts
12091
- import path49 from "path";
12224
+ import path50 from "path";
12092
12225
  import Parser12 from "tree-sitter";
12093
12226
  import JavaScript7 from "tree-sitter-javascript";
12094
12227
  import { infraId as infraId12 } from "@neat.is/types";
@@ -12166,7 +12299,7 @@ function columnsFromObject(obj) {
12166
12299
  }
12167
12300
  function drizzleEndpointsFromFile(file, serviceDir) {
12168
12301
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
12169
- const tree = parseSource3(parserForExt3(path49.extname(file.path)), file.content);
12302
+ const tree = parseSource3(parserForExt3(path50.extname(file.path)), file.content);
12170
12303
  const out = [];
12171
12304
  const seen = /* @__PURE__ */ new Set();
12172
12305
  const walk11 = (node) => {
@@ -12189,7 +12322,7 @@ function drizzleEndpointsFromFile(file, serviceDir) {
12189
12322
  confidenceKind: "structural",
12190
12323
  columns,
12191
12324
  evidence: {
12192
- file: path49.relative(serviceDir, file.path),
12325
+ file: path50.relative(serviceDir, file.path),
12193
12326
  line,
12194
12327
  snippet: snippet(file.content, line)
12195
12328
  }
@@ -12255,7 +12388,7 @@ function referencesTargetVar(call) {
12255
12388
  }
12256
12389
  function drizzleForeignKeys(file, serviceDir) {
12257
12390
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
12258
- const tree = parseSource3(parserForExt3(path49.extname(file.path)), file.content);
12391
+ const tree = parseSource3(parserForExt3(path50.extname(file.path)), file.content);
12259
12392
  const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
12260
12393
  const out = [];
12261
12394
  const seen = /* @__PURE__ */ new Set();
@@ -12274,7 +12407,7 @@ function drizzleForeignKeys(file, serviceDir) {
12274
12407
  childTable: table.tableName,
12275
12408
  parentTable,
12276
12409
  evidence: {
12277
- file: path49.relative(serviceDir, file.path),
12410
+ file: path50.relative(serviceDir, file.path),
12278
12411
  line,
12279
12412
  snippet: snippet(file.content, line)
12280
12413
  }
@@ -12290,7 +12423,7 @@ function drizzleForeignKeys(file, serviceDir) {
12290
12423
  }
12291
12424
 
12292
12425
  // src/extract/calls/prisma.ts
12293
- import path50 from "path";
12426
+ import path51 from "path";
12294
12427
  import { infraId as infraId13 } from "@neat.is/types";
12295
12428
  var SCALAR_TYPES = /* @__PURE__ */ new Set([
12296
12429
  "Int",
@@ -12346,7 +12479,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
12346
12479
  confidenceKind: "structural",
12347
12480
  columns: b.columns,
12348
12481
  evidence: {
12349
- file: path50.relative(serviceDir, file.path),
12482
+ file: path51.relative(serviceDir, file.path),
12350
12483
  line: b.startLine,
12351
12484
  snippet: snippet(content, b.startLine)
12352
12485
  }
@@ -12407,7 +12540,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
12407
12540
  }
12408
12541
  async function prismaColumnEndpoints(serviceDir) {
12409
12542
  const schemaPath = await findFirst(serviceDir, [
12410
- path50.join("prisma", "schema.prisma"),
12543
+ path51.join("prisma", "schema.prisma"),
12411
12544
  "schema.prisma"
12412
12545
  ]);
12413
12546
  if (!schemaPath) return [];
@@ -12482,7 +12615,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
12482
12615
  childTable: current.table,
12483
12616
  parentTable,
12484
12617
  evidence: {
12485
- file: path50.relative(serviceDir, file.path),
12618
+ file: path51.relative(serviceDir, file.path),
12486
12619
  line: lineNo,
12487
12620
  snippet: snippet(content, lineNo)
12488
12621
  }
@@ -12497,7 +12630,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
12497
12630
  }
12498
12631
  async function prismaForeignKeys(serviceDir) {
12499
12632
  const schemaPath = await findFirst(serviceDir, [
12500
- path50.join("prisma", "schema.prisma"),
12633
+ path51.join("prisma", "schema.prisma"),
12501
12634
  "schema.prisma"
12502
12635
  ]);
12503
12636
  if (!schemaPath) return [];
@@ -12507,7 +12640,7 @@ async function prismaForeignKeys(serviceDir) {
12507
12640
  }
12508
12641
 
12509
12642
  // src/extract/calls/activerecord.ts
12510
- import path51 from "path";
12643
+ import path52 from "path";
12511
12644
  import Parser13 from "tree-sitter";
12512
12645
  import Ruby3 from "tree-sitter-ruby";
12513
12646
  import { infraId as infraId14 } from "@neat.is/types";
@@ -12715,7 +12848,7 @@ function railsSchemaEndpointsFromFile(file, serviceDir) {
12715
12848
  confidenceKind: "structural",
12716
12849
  ...table.columns.length > 0 ? { columns: table.columns } : {},
12717
12850
  evidence: {
12718
- file: path51.relative(serviceDir, file.path),
12851
+ file: path52.relative(serviceDir, file.path),
12719
12852
  line: table.line,
12720
12853
  snippet: snippet(file.content, table.line)
12721
12854
  }
@@ -12737,7 +12870,7 @@ function railsSchemaForeignKeys(file, serviceDir) {
12737
12870
  childTable,
12738
12871
  parentTable,
12739
12872
  evidence: {
12740
- file: path51.relative(serviceDir, file.path),
12873
+ file: path52.relative(serviceDir, file.path),
12741
12874
  line,
12742
12875
  snippet: snippet(file.content, line)
12743
12876
  }
@@ -12820,7 +12953,7 @@ function railsModelEndpointsFromFile(file, serviceDir) {
12820
12953
  edgeType: "CALLS",
12821
12954
  confidenceKind: "verified-call-site",
12822
12955
  evidence: {
12823
- file: path51.relative(serviceDir, file.path),
12956
+ file: path52.relative(serviceDir, file.path),
12824
12957
  line,
12825
12958
  snippet: snippet(file.content, line)
12826
12959
  }
@@ -12857,7 +12990,7 @@ function railsModelForeignKeys(file, serviceDir) {
12857
12990
  childTable,
12858
12991
  parentTable,
12859
12992
  evidence: {
12860
- file: path51.relative(serviceDir, file.path),
12993
+ file: path52.relative(serviceDir, file.path),
12861
12994
  line,
12862
12995
  snippet: snippet(file.content, line)
12863
12996
  }
@@ -12868,7 +13001,7 @@ function railsModelForeignKeys(file, serviceDir) {
12868
13001
  }
12869
13002
 
12870
13003
  // src/extract/calls/eloquent.ts
12871
- import path52 from "path";
13004
+ import path53 from "path";
12872
13005
  import Parser14 from "tree-sitter";
12873
13006
  import Php3 from "tree-sitter-php";
12874
13007
  import { infraId as infraId15 } from "@neat.is/types";
@@ -13125,7 +13258,7 @@ function laravelMigrationEndpointsFromFile(file, serviceDir) {
13125
13258
  confidenceKind: "structural",
13126
13259
  ...columns.length > 0 ? { columns } : {},
13127
13260
  evidence: {
13128
- file: path52.relative(serviceDir, file.path),
13261
+ file: path53.relative(serviceDir, file.path),
13129
13262
  line: bp.line,
13130
13263
  snippet: snippet(file.content, bp.line)
13131
13264
  }
@@ -13147,7 +13280,7 @@ function laravelMigrationForeignKeys(file, serviceDir) {
13147
13280
  childTable,
13148
13281
  parentTable,
13149
13282
  evidence: {
13150
- file: path52.relative(serviceDir, file.path),
13283
+ file: path53.relative(serviceDir, file.path),
13151
13284
  line,
13152
13285
  snippet: snippet(file.content, line)
13153
13286
  }
@@ -13267,7 +13400,7 @@ function laravelModelEndpointsFromFile(file, serviceDir) {
13267
13400
  edgeType: "CALLS",
13268
13401
  confidenceKind: "verified-call-site",
13269
13402
  evidence: {
13270
- file: path52.relative(serviceDir, file.path),
13403
+ file: path53.relative(serviceDir, file.path),
13271
13404
  line,
13272
13405
  snippet: snippet(file.content, line)
13273
13406
  }
@@ -13303,7 +13436,7 @@ function laravelModelForeignKeys(file, serviceDir) {
13303
13436
  childTable,
13304
13437
  parentTable,
13305
13438
  evidence: {
13306
- file: path52.relative(serviceDir, file.path),
13439
+ file: path53.relative(serviceDir, file.path),
13307
13440
  line,
13308
13441
  snippet: snippet(file.content, line)
13309
13442
  }
@@ -13314,7 +13447,7 @@ function laravelModelForeignKeys(file, serviceDir) {
13314
13447
  }
13315
13448
 
13316
13449
  // src/extract/calls/go.ts
13317
- import path53 from "path";
13450
+ import path54 from "path";
13318
13451
  import Parser15 from "tree-sitter";
13319
13452
  import Go4 from "tree-sitter-go";
13320
13453
  import { infraId as infraId16 } from "@neat.is/types";
@@ -13388,7 +13521,7 @@ function firstStringLiteralArg(argsNode) {
13388
13521
  return null;
13389
13522
  }
13390
13523
  function goSqlEndpointsFromFile(file, serviceDir) {
13391
- if (path53.extname(file.path) !== ".go") return [];
13524
+ if (path54.extname(file.path) !== ".go") return [];
13392
13525
  if (!file.content.includes(DATABASE_SQL_IMPORT) && !file.content.includes(SQLX_IMPORT)) return [];
13393
13526
  const tree = parseSource11(makeGoParser3(), file.content);
13394
13527
  const importsDatabaseSql = goImportsAny(tree.rootNode, /* @__PURE__ */ new Set([DATABASE_SQL_IMPORT]));
@@ -13417,7 +13550,7 @@ function goSqlEndpointsFromFile(file, serviceDir) {
13417
13550
  confidenceKind: "verified-call-site",
13418
13551
  ...columns.length > 0 ? { columns } : {},
13419
13552
  evidence: {
13420
- file: toPosix(path53.relative(serviceDir, file.path)),
13553
+ file: toPosix(path54.relative(serviceDir, file.path)),
13421
13554
  line,
13422
13555
  snippet: snippet(file.content, line)
13423
13556
  }
@@ -13427,7 +13560,7 @@ function goSqlEndpointsFromFile(file, serviceDir) {
13427
13560
  }
13428
13561
 
13429
13562
  // src/extract/calls/gorm.ts
13430
- import path54 from "path";
13563
+ import path55 from "path";
13431
13564
  import Parser16 from "tree-sitter";
13432
13565
  import Go5 from "tree-sitter-go";
13433
13566
  import { infraId as infraId17 } from "@neat.is/types";
@@ -13860,7 +13993,7 @@ function collectColumns(struct, structs, seen, prefix, out, emitted) {
13860
13993
  seen.delete(struct.name);
13861
13994
  }
13862
13995
  function gormEndpointsFromFile(file, serviceDir) {
13863
- if (path54.extname(file.path) !== ".go") return [];
13996
+ if (path55.extname(file.path) !== ".go") return [];
13864
13997
  if (!GORM_IMPORT_RE.test(file.content)) return [];
13865
13998
  const tree = parseSource12(makeGoParser4(), file.content);
13866
13999
  const { structs, models, tableFor } = analyze(tree);
@@ -13882,7 +14015,7 @@ function gormEndpointsFromFile(file, serviceDir) {
13882
14015
  confidenceKind: "structural",
13883
14016
  ...columns.length > 0 ? { columns } : {},
13884
14017
  evidence: {
13885
- file: toPosix(path54.relative(serviceDir, file.path)),
14018
+ file: toPosix(path55.relative(serviceDir, file.path)),
13886
14019
  line: struct.line,
13887
14020
  snippet: snippet(file.content, struct.line)
13888
14021
  }
@@ -13891,7 +14024,7 @@ function gormEndpointsFromFile(file, serviceDir) {
13891
14024
  return out;
13892
14025
  }
13893
14026
  function gormForeignKeys(file, serviceDir) {
13894
- if (path54.extname(file.path) !== ".go") return [];
14027
+ if (path55.extname(file.path) !== ".go") return [];
13895
14028
  if (!GORM_IMPORT_RE.test(file.content)) return [];
13896
14029
  const tree = parseSource12(makeGoParser4(), file.content);
13897
14030
  const { structs, models, tableFor } = analyze(tree);
@@ -13906,7 +14039,7 @@ function gormForeignKeys(file, serviceDir) {
13906
14039
  childTable,
13907
14040
  parentTable,
13908
14041
  evidence: {
13909
- file: toPosix(path54.relative(serviceDir, file.path)),
14042
+ file: toPosix(path55.relative(serviceDir, file.path)),
13910
14043
  line,
13911
14044
  snippet: snippet(file.content, line)
13912
14045
  }
@@ -13942,7 +14075,7 @@ function gormForeignKeys(file, serviceDir) {
13942
14075
  }
13943
14076
 
13944
14077
  // src/extract/calls/efcore.ts
13945
- import path55 from "path";
14078
+ import path56 from "path";
13946
14079
  import Parser17 from "tree-sitter";
13947
14080
  import CSharp2 from "tree-sitter-c-sharp";
13948
14081
  import { infraId as infraId18 } from "@neat.is/types";
@@ -14021,7 +14154,7 @@ function tableFromToTable(call) {
14021
14154
  return value ? csStringLiteral(value) : null;
14022
14155
  }
14023
14156
  function efcoreEndpointsFromFile(file, serviceDir) {
14024
- if (path55.extname(file.path) !== ".cs") return [];
14157
+ if (path56.extname(file.path) !== ".cs") return [];
14025
14158
  if (!EFCORE_GATE.test(file.content)) return [];
14026
14159
  const tree = parseSource13(makeCsParser(), file.content);
14027
14160
  const out = [];
@@ -14036,7 +14169,7 @@ function efcoreEndpointsFromFile(file, serviceDir) {
14036
14169
  edgeType: "CALLS",
14037
14170
  confidenceKind: "structural",
14038
14171
  evidence: {
14039
- file: toPosix(path55.relative(serviceDir, file.path)),
14172
+ file: toPosix(path56.relative(serviceDir, file.path)),
14040
14173
  line,
14041
14174
  snippet: snippet(file.content, line)
14042
14175
  }
@@ -14293,7 +14426,7 @@ function ensureTableNode(graph, id, name) {
14293
14426
  }
14294
14427
 
14295
14428
  // src/extract/infra/docker-compose.ts
14296
- import path56 from "path";
14429
+ import path57 from "path";
14297
14430
  import { EdgeType as EdgeType17, Provenance as Provenance18, confidenceForExtracted as confidenceForExtracted15 } from "@neat.is/types";
14298
14431
 
14299
14432
  // src/extract/infra/shared.ts
@@ -14363,7 +14496,7 @@ function dependsOnList(value) {
14363
14496
  }
14364
14497
  function serviceNameToServiceNode(name, services) {
14365
14498
  for (const s of services) {
14366
- if (s.node.name === name || path56.basename(s.dir) === name) return s.node.id;
14499
+ if (s.node.name === name || path57.basename(s.dir) === name) return s.node.id;
14367
14500
  }
14368
14501
  return null;
14369
14502
  }
@@ -14372,7 +14505,7 @@ async function addComposeInfra(graph, scanPath, services) {
14372
14505
  let edgesAdded = 0;
14373
14506
  let composePath = null;
14374
14507
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
14375
- const abs = path56.join(scanPath, name);
14508
+ const abs = path57.join(scanPath, name);
14376
14509
  if (await exists(abs)) {
14377
14510
  composePath = abs;
14378
14511
  break;
@@ -14385,13 +14518,13 @@ async function addComposeInfra(graph, scanPath, services) {
14385
14518
  } catch (err) {
14386
14519
  recordExtractionError(
14387
14520
  "infra docker-compose",
14388
- path56.relative(scanPath, composePath),
14521
+ path57.relative(scanPath, composePath),
14389
14522
  err
14390
14523
  );
14391
14524
  return { nodesAdded, edgesAdded };
14392
14525
  }
14393
14526
  if (!compose?.services) return { nodesAdded, edgesAdded };
14394
- const evidenceFile = path56.relative(scanPath, composePath).split(path56.sep).join("/");
14527
+ const evidenceFile = path57.relative(scanPath, composePath).split(path57.sep).join("/");
14395
14528
  const composeNameToNodeId = /* @__PURE__ */ new Map();
14396
14529
  for (const [composeName, svc] of Object.entries(compose.services)) {
14397
14530
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -14432,8 +14565,8 @@ async function addComposeInfra(graph, scanPath, services) {
14432
14565
  }
14433
14566
 
14434
14567
  // src/extract/infra/dockerfile.ts
14435
- import path57 from "path";
14436
- import { promises as fs25 } from "fs";
14568
+ import path58 from "path";
14569
+ import { promises as fs26 } from "fs";
14437
14570
  import { EdgeType as EdgeType18, Provenance as Provenance19, confidenceForExtracted as confidenceForExtracted16 } from "@neat.is/types";
14438
14571
  function readDockerfile(content) {
14439
14572
  let image = null;
@@ -14463,15 +14596,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
14463
14596
  let nodesAdded = 0;
14464
14597
  let edgesAdded = 0;
14465
14598
  for (const service of services) {
14466
- const dockerfilePath = path57.join(service.dir, "Dockerfile");
14599
+ const dockerfilePath = path58.join(service.dir, "Dockerfile");
14467
14600
  if (!await exists(dockerfilePath)) continue;
14468
14601
  let content;
14469
14602
  try {
14470
- content = await fs25.readFile(dockerfilePath, "utf8");
14603
+ content = await fs26.readFile(dockerfilePath, "utf8");
14471
14604
  } catch (err) {
14472
14605
  recordExtractionError(
14473
14606
  "infra dockerfile",
14474
- path57.relative(scanPath, dockerfilePath),
14607
+ path58.relative(scanPath, dockerfilePath),
14475
14608
  err
14476
14609
  );
14477
14610
  continue;
@@ -14483,8 +14616,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
14483
14616
  graph.addNode(node.id, node);
14484
14617
  nodesAdded++;
14485
14618
  }
14486
- const relDockerfile = toPosix(path57.relative(service.dir, dockerfilePath));
14487
- const evidenceFile = toPosix(path57.relative(scanPath, dockerfilePath));
14619
+ const relDockerfile = toPosix(path58.relative(service.dir, dockerfilePath));
14620
+ const evidenceFile = toPosix(path58.relative(scanPath, dockerfilePath));
14488
14621
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
14489
14622
  graph,
14490
14623
  service.pkg.name,
@@ -14535,23 +14668,23 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
14535
14668
  }
14536
14669
 
14537
14670
  // src/extract/infra/terraform.ts
14538
- import { promises as fs26 } from "fs";
14539
- import path58 from "path";
14671
+ import { promises as fs27 } from "fs";
14672
+ import path59 from "path";
14540
14673
  import { EdgeType as EdgeType19, Provenance as Provenance20, confidenceForExtracted as confidenceForExtracted17 } from "@neat.is/types";
14541
14674
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
14542
14675
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
14543
14676
  async function walkTfFiles(start, depth = 0, max = 5) {
14544
14677
  if (depth > max) return [];
14545
14678
  const out = [];
14546
- const entries = await fs26.readdir(start, { withFileTypes: true }).catch(() => []);
14679
+ const entries = await fs27.readdir(start, { withFileTypes: true }).catch(() => []);
14547
14680
  for (const entry of entries) {
14548
14681
  if (entry.isDirectory()) {
14549
14682
  if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
14550
- const child = path58.join(start, entry.name);
14683
+ const child = path59.join(start, entry.name);
14551
14684
  if (await isPythonVenvDir(child)) continue;
14552
14685
  out.push(...await walkTfFiles(child, depth + 1, max));
14553
14686
  } else if (entry.isFile() && entry.name.endsWith(".tf")) {
14554
- out.push(path58.join(start, entry.name));
14687
+ out.push(path59.join(start, entry.name));
14555
14688
  }
14556
14689
  }
14557
14690
  return out;
@@ -14582,8 +14715,8 @@ async function addTerraformResources(graph, scanPath) {
14582
14715
  let edgesAdded = 0;
14583
14716
  const files = await walkTfFiles(scanPath);
14584
14717
  for (const file of files) {
14585
- const content = await fs26.readFile(file, "utf8");
14586
- const evidenceFile = toPosix(path58.relative(scanPath, file));
14718
+ const content = await fs27.readFile(file, "utf8");
14719
+ const evidenceFile = toPosix(path59.relative(scanPath, file));
14587
14720
  const resources = [];
14588
14721
  const byKey = /* @__PURE__ */ new Map();
14589
14722
  RESOURCE_RE.lastIndex = 0;
@@ -14639,8 +14772,8 @@ async function addTerraformResources(graph, scanPath) {
14639
14772
  }
14640
14773
 
14641
14774
  // src/extract/infra/k8s.ts
14642
- import { promises as fs27 } from "fs";
14643
- import path59 from "path";
14775
+ import { promises as fs28 } from "fs";
14776
+ import path60 from "path";
14644
14777
  import { parseAllDocuments as parseAllDocuments2 } from "yaml";
14645
14778
  import { EdgeType as EdgeType20, serviceId as serviceId13 } from "@neat.is/types";
14646
14779
  var K8S_KIND_TO_INFRA_KIND = {
@@ -14656,15 +14789,15 @@ var WORKLOAD_KINDS = /* @__PURE__ */ new Set(["Deployment", "StatefulSet", "Daem
14656
14789
  async function walkYamlFiles2(start, depth = 0, max = 5) {
14657
14790
  if (depth > max) return [];
14658
14791
  const out = [];
14659
- const entries = await fs27.readdir(start, { withFileTypes: true }).catch(() => []);
14792
+ const entries = await fs28.readdir(start, { withFileTypes: true }).catch(() => []);
14660
14793
  for (const entry of entries) {
14661
14794
  if (entry.isDirectory()) {
14662
14795
  if (IGNORED_DIRS.has(entry.name)) continue;
14663
- const child = path59.join(start, entry.name);
14796
+ const child = path60.join(start, entry.name);
14664
14797
  if (await isPythonVenvDir(child)) continue;
14665
14798
  out.push(...await walkYamlFiles2(child, depth + 1, max));
14666
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path59.extname(entry.name))) {
14667
- out.push(path59.join(start, entry.name));
14799
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path60.extname(entry.name))) {
14800
+ out.push(path60.join(start, entry.name));
14668
14801
  }
14669
14802
  }
14670
14803
  return out;
@@ -14674,14 +14807,14 @@ async function addK8sResources(graph, scanPath) {
14674
14807
  let edgesAdded = 0;
14675
14808
  const files = await walkYamlFiles2(scanPath);
14676
14809
  for (const file of files) {
14677
- const content = await fs27.readFile(file, "utf8");
14810
+ const content = await fs28.readFile(file, "utf8");
14678
14811
  let docs;
14679
14812
  try {
14680
14813
  docs = parseAllDocuments2(content).map((d) => d.toJSON());
14681
14814
  } catch {
14682
14815
  continue;
14683
14816
  }
14684
- const relFile = toPosix(path59.relative(scanPath, file));
14817
+ const relFile = toPosix(path60.relative(scanPath, file));
14685
14818
  for (const doc of docs) {
14686
14819
  if (!doc?.kind || !doc.metadata?.name) continue;
14687
14820
  const infraKind = K8S_KIND_TO_INFRA_KIND[doc.kind];
@@ -14729,16 +14862,16 @@ async function addK8sResources(graph, scanPath) {
14729
14862
  }
14730
14863
 
14731
14864
  // src/extract/infra/cloudflare.ts
14732
- import { promises as fs28 } from "fs";
14733
- import path60 from "path";
14865
+ import { promises as fs29 } from "fs";
14866
+ import path61 from "path";
14734
14867
  import { parse as parseToml3 } from "smol-toml";
14735
14868
  import { EdgeType as EdgeType21, Provenance as Provenance21, confidenceForExtracted as confidenceForExtracted18 } from "@neat.is/types";
14736
14869
  var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
14737
14870
  async function readWranglerConfig(dir) {
14738
14871
  for (const filename of WRANGLER_FILENAMES) {
14739
- const abs = path60.join(dir, filename);
14872
+ const abs = path61.join(dir, filename);
14740
14873
  if (!await exists(abs)) continue;
14741
- const raw = await fs28.readFile(abs, "utf8");
14874
+ const raw = await fs29.readFile(abs, "utf8");
14742
14875
  const config = filename === "wrangler.toml" ? parseToml3(raw) : JSON.parse(maskCommentsInSource(raw));
14743
14876
  return { config, relFile: filename, raw };
14744
14877
  }
@@ -14799,11 +14932,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
14799
14932
  try {
14800
14933
  read = await readWranglerConfig(service.dir);
14801
14934
  } catch (err) {
14802
- recordExtractionError("infra cloudflare", path60.relative(scanPath, service.dir), err);
14935
+ recordExtractionError("infra cloudflare", path61.relative(scanPath, service.dir), err);
14803
14936
  continue;
14804
14937
  }
14805
14938
  if (!read || !read.config.name) continue;
14806
- const evidenceFile = toPosix(path60.relative(scanPath, path60.join(service.dir, read.relFile)));
14939
+ const evidenceFile = toPosix(path61.relative(scanPath, path61.join(service.dir, read.relFile)));
14807
14940
  discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
14808
14941
  }
14809
14942
  for (const worker of discovered) {
@@ -14815,7 +14948,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
14815
14948
  }
14816
14949
  let anchorId = service.node.id;
14817
14950
  if (config.main) {
14818
- const entryRelPath = toPosix(path60.normalize(config.main));
14951
+ const entryRelPath = toPosix(path61.normalize(config.main));
14819
14952
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
14820
14953
  graph,
14821
14954
  service.pkg.name,
@@ -14961,24 +15094,24 @@ async function addCloudflareWorkers(graph, services, scanPath) {
14961
15094
  }
14962
15095
 
14963
15096
  // src/extract/infra/vercel.ts
14964
- import { promises as fs29 } from "fs";
14965
- import path61 from "path";
15097
+ import { promises as fs30 } from "fs";
15098
+ import path62 from "path";
14966
15099
  import { EdgeType as EdgeType22 } from "@neat.is/types";
14967
15100
  var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
14968
15101
  async function readVercelConfig(dir) {
14969
15102
  for (const filename of VERCEL_CONFIG_FILENAMES) {
14970
- const abs = path61.join(dir, filename);
15103
+ const abs = path62.join(dir, filename);
14971
15104
  if (!await exists(abs)) continue;
14972
- const raw = await fs29.readFile(abs, "utf8");
15105
+ const raw = await fs30.readFile(abs, "utf8");
14973
15106
  const config = JSON.parse(maskCommentsInSource(raw));
14974
15107
  return { config, relFile: filename, raw };
14975
15108
  }
14976
15109
  return null;
14977
15110
  }
14978
15111
  async function readLinkedProjectName(dir) {
14979
- const abs = path61.join(dir, ".vercel", "project.json");
15112
+ const abs = path62.join(dir, ".vercel", "project.json");
14980
15113
  if (!await exists(abs)) return void 0;
14981
- const parsed = JSON.parse(await fs29.readFile(abs, "utf8"));
15114
+ const parsed = JSON.parse(await fs30.readFile(abs, "utf8"));
14982
15115
  return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
14983
15116
  }
14984
15117
  function routeSource(route) {
@@ -14994,7 +15127,7 @@ async function addVercelServices(graph, services, scanPath) {
14994
15127
  read = await readVercelConfig(service.dir);
14995
15128
  projectName = await readLinkedProjectName(service.dir);
14996
15129
  } catch (err) {
14997
- recordExtractionError("infra vercel", path61.relative(scanPath, service.dir), err);
15130
+ recordExtractionError("infra vercel", path62.relative(scanPath, service.dir), err);
14998
15131
  continue;
14999
15132
  }
15000
15133
  if (!read && !projectName) continue;
@@ -15010,7 +15143,7 @@ async function addVercelServices(graph, services, scanPath) {
15010
15143
  const anchorId = service.node.id;
15011
15144
  if (!read) continue;
15012
15145
  const { config, relFile, raw } = read;
15013
- const evidenceFile = toPosix(path61.relative(scanPath, path61.join(service.dir, relFile)));
15146
+ const evidenceFile = toPosix(path62.relative(scanPath, path62.join(service.dir, relFile)));
15014
15147
  const add = (edgeType, kind, name) => {
15015
15148
  if (!name) return;
15016
15149
  const result = emitPlatformResourceEdge(
@@ -15038,16 +15171,16 @@ async function addVercelServices(graph, services, scanPath) {
15038
15171
  }
15039
15172
 
15040
15173
  // src/extract/infra/railway.ts
15041
- import { promises as fs30 } from "fs";
15042
- import path62 from "path";
15174
+ import { promises as fs31 } from "fs";
15175
+ import path63 from "path";
15043
15176
  import { parse as parseToml4 } from "smol-toml";
15044
15177
  import { EdgeType as EdgeType23 } from "@neat.is/types";
15045
15178
  var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
15046
15179
  async function readRailwayConfig(dir) {
15047
15180
  for (const filename of RAILWAY_FILENAMES) {
15048
- const abs = path62.join(dir, filename);
15181
+ const abs = path63.join(dir, filename);
15049
15182
  if (!await exists(abs)) continue;
15050
- const raw = await fs30.readFile(abs, "utf8");
15183
+ const raw = await fs31.readFile(abs, "utf8");
15051
15184
  const config = filename === "railway.toml" ? parseToml4(raw) : JSON.parse(maskCommentsInSource(raw));
15052
15185
  return { config, relFile: filename, raw };
15053
15186
  }
@@ -15061,7 +15194,7 @@ async function addRailwayServices(graph, services, scanPath) {
15061
15194
  try {
15062
15195
  read = await readRailwayConfig(service.dir);
15063
15196
  } catch (err) {
15064
- recordExtractionError("infra railway", path62.relative(scanPath, service.dir), err);
15197
+ recordExtractionError("infra railway", path63.relative(scanPath, service.dir), err);
15065
15198
  continue;
15066
15199
  }
15067
15200
  if (!read) continue;
@@ -15071,7 +15204,7 @@ async function addRailwayServices(graph, services, scanPath) {
15071
15204
  }
15072
15205
  const anchorId = service.node.id;
15073
15206
  const { config, relFile, raw } = read;
15074
- const evidenceFile = toPosix(path62.relative(scanPath, path62.join(service.dir, relFile)));
15207
+ const evidenceFile = toPosix(path63.relative(scanPath, path63.join(service.dir, relFile)));
15075
15208
  const add = (edgeType, kind, name) => {
15076
15209
  if (!name) return;
15077
15210
  const result = emitPlatformResourceEdge(
@@ -15095,15 +15228,15 @@ async function addRailwayServices(graph, services, scanPath) {
15095
15228
  }
15096
15229
 
15097
15230
  // src/extract/infra/supabase.ts
15098
- import { promises as fs31 } from "fs";
15099
- import path63 from "path";
15231
+ import { promises as fs32 } from "fs";
15232
+ import path64 from "path";
15100
15233
  import { parse as parseToml5 } from "smol-toml";
15101
15234
  import { EdgeType as EdgeType24 } from "@neat.is/types";
15102
15235
  async function readSupabaseConfig(dir) {
15103
- const relFile = path63.join("supabase", "config.toml");
15104
- const abs = path63.join(dir, relFile);
15236
+ const relFile = path64.join("supabase", "config.toml");
15237
+ const abs = path64.join(dir, relFile);
15105
15238
  if (!await exists(abs)) return null;
15106
- const raw = await fs31.readFile(abs, "utf8");
15239
+ const raw = await fs32.readFile(abs, "utf8");
15107
15240
  const config = parseToml5(raw);
15108
15241
  return { config, relFile, raw };
15109
15242
  }
@@ -15115,7 +15248,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
15115
15248
  try {
15116
15249
  read = await readSupabaseConfig(service.dir);
15117
15250
  } catch (err) {
15118
- recordExtractionError("infra supabase", path63.relative(scanPath, service.dir), err);
15251
+ recordExtractionError("infra supabase", path64.relative(scanPath, service.dir), err);
15119
15252
  continue;
15120
15253
  }
15121
15254
  if (!read) continue;
@@ -15130,7 +15263,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
15130
15263
  });
15131
15264
  }
15132
15265
  const anchorId = service.node.id;
15133
- const evidenceFile = toPosix(path63.relative(scanPath, path63.join(service.dir, relFile)));
15266
+ const evidenceFile = toPosix(path64.relative(scanPath, path64.join(service.dir, relFile)));
15134
15267
  const add = (edgeType, kind, name) => {
15135
15268
  if (!name) return;
15136
15269
  const result = emitPlatformResourceEdge(
@@ -15171,7 +15304,7 @@ async function addInfra(graph, scanPath, services) {
15171
15304
  }
15172
15305
 
15173
15306
  // src/extract/zod-shapes.ts
15174
- import path64 from "path";
15307
+ import path65 from "path";
15175
15308
  import Parser18 from "tree-sitter";
15176
15309
  import JavaScript8 from "tree-sitter-javascript";
15177
15310
  import {
@@ -15273,7 +15406,7 @@ function topLevelSchemas(root) {
15273
15406
  }
15274
15407
  function zodShapesFromFile(file, serviceDir) {
15275
15408
  if (!ZOD_IMPORT_RE.test(file.content)) return [];
15276
- const tree = parseSource3(parserForExt4(path64.extname(file.path)), file.content);
15409
+ const tree = parseSource3(parserForExt4(path65.extname(file.path)), file.content);
15277
15410
  const out = [];
15278
15411
  const seen = /* @__PURE__ */ new Set();
15279
15412
  for (const { name, call } of topLevelSchemas(tree.rootNode)) {
@@ -15291,7 +15424,7 @@ function zodShapesFromFile(file, serviceDir) {
15291
15424
  name,
15292
15425
  fields,
15293
15426
  evidence: {
15294
- file: path64.relative(serviceDir, file.path),
15427
+ file: path65.relative(serviceDir, file.path),
15295
15428
  line,
15296
15429
  snippet: snippet(file.content, line)
15297
15430
  }
@@ -15558,11 +15691,11 @@ async function addFirestoreRules(graph, services) {
15558
15691
  }
15559
15692
 
15560
15693
  // src/extract/index.ts
15561
- import path66 from "path";
15694
+ import path67 from "path";
15562
15695
 
15563
15696
  // src/extract/retire.ts
15564
15697
  import { existsSync as existsSync2 } from "fs";
15565
- import path65 from "path";
15698
+ import path66 from "path";
15566
15699
  import { NodeType as NodeType29, Provenance as Provenance23 } from "@neat.is/types";
15567
15700
  function dropOrphanedFileNodes(graph) {
15568
15701
  const orphans = [];
@@ -15596,11 +15729,11 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
15596
15729
  if (edge.provenance !== Provenance23.EXTRACTED) return;
15597
15730
  const evidenceFile = edge.evidence?.file;
15598
15731
  if (!evidenceFile) return;
15599
- if (path65.isAbsolute(evidenceFile)) {
15732
+ if (path66.isAbsolute(evidenceFile)) {
15600
15733
  if (!existsSync2(evidenceFile)) toDrop.push(id);
15601
15734
  return;
15602
15735
  }
15603
- const found = bases.some((base) => existsSync2(path65.join(base, evidenceFile)));
15736
+ const found = bases.some((base) => existsSync2(path66.join(base, evidenceFile)));
15604
15737
  if (!found) toDrop.push(id);
15605
15738
  });
15606
15739
  for (const id of toDrop) graph.dropEdge(id);
@@ -15612,6 +15745,7 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
15612
15745
  async function extractFromDirectory(graph, scanPath, opts = {}) {
15613
15746
  await ensureCompatLoaded();
15614
15747
  drainExtractionErrors();
15748
+ drainSkippedFiles();
15615
15749
  const services = await discoverServices(scanPath);
15616
15750
  const phase1Nodes = addServiceNodes(graph, services);
15617
15751
  await addServiceAliases(graph, scanPath, services);
@@ -15656,8 +15790,9 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
15656
15790
  }
15657
15791
  }
15658
15792
  const droppedEntries = drainDroppedExtracted();
15793
+ const skippedEntries = drainSkippedFiles();
15659
15794
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
15660
- const rejectedPath = path66.join(path66.dirname(opts.errorsPath), "rejected.ndjson");
15795
+ const rejectedPath = path67.join(path67.dirname(opts.errorsPath), "rejected.ndjson");
15661
15796
  try {
15662
15797
  await writeRejectedExtracted(droppedEntries, rejectedPath);
15663
15798
  } catch (err) {
@@ -15674,7 +15809,9 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
15674
15809
  errorEntries,
15675
15810
  ghostsRetired,
15676
15811
  extractedDropped: droppedEntries.length,
15677
- droppedEntries
15812
+ droppedEntries,
15813
+ skippedFiles: skippedEntries.length,
15814
+ skippedEntries
15678
15815
  };
15679
15816
  emitNeatEvent({
15680
15817
  type: "extraction-complete",
@@ -16376,8 +16513,8 @@ function computeDivergences(graph, opts = {}) {
16376
16513
  }
16377
16514
 
16378
16515
  // src/persist.ts
16379
- import { promises as fs32 } from "fs";
16380
- import path67 from "path";
16516
+ import { promises as fs33 } from "fs";
16517
+ import path68 from "path";
16381
16518
  import { NodeType as NodeType31, Provenance as Provenance25, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
16382
16519
  var SCHEMA_VERSION = 7;
16383
16520
  function migrateV1ToV2(payload) {
@@ -16432,7 +16569,43 @@ function migrateV2ToV3(payload) {
16432
16569
  return { ...payload, schemaVersion: 3 };
16433
16570
  }
16434
16571
  async function ensureDir(filePath) {
16435
- await fs32.mkdir(path67.dirname(filePath), { recursive: true });
16572
+ await fs33.mkdir(path68.dirname(filePath), { recursive: true });
16573
+ }
16574
+ var WRITE_FLUSH_CHARS = 4 * 1024 * 1024;
16575
+ function jsonOrUndefined(value) {
16576
+ return JSON.stringify(value);
16577
+ }
16578
+ function* serializeArray(items) {
16579
+ yield "[";
16580
+ for (let i = 0; i < items.length; i++) {
16581
+ if (i > 0) yield ",";
16582
+ yield jsonOrUndefined(items[i]) ?? "null";
16583
+ }
16584
+ yield "]";
16585
+ }
16586
+ function isPlainObject(value) {
16587
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
16588
+ const proto = Object.getPrototypeOf(value);
16589
+ return proto === Object.prototype || proto === null;
16590
+ }
16591
+ function* serializeObject(obj) {
16592
+ yield "{";
16593
+ let first = true;
16594
+ for (const key of Object.keys(obj)) {
16595
+ const value = obj[key];
16596
+ const descend = Array.isArray(value) || isPlainObject(value);
16597
+ if (!descend && jsonOrUndefined(value) === void 0) continue;
16598
+ if (!first) yield ",";
16599
+ first = false;
16600
+ yield `${JSON.stringify(key)}:`;
16601
+ if (Array.isArray(value)) yield* serializeArray(value);
16602
+ else if (isPlainObject(value)) yield* serializeObject(value);
16603
+ else yield jsonOrUndefined(value);
16604
+ }
16605
+ yield "}";
16606
+ }
16607
+ function* serializeGraphChunks(payload) {
16608
+ yield* serializeObject(payload);
16436
16609
  }
16437
16610
  async function saveGraphToDisk(graph, outPath) {
16438
16611
  await ensureDir(outPath);
@@ -16442,13 +16615,26 @@ async function saveGraphToDisk(graph, outPath) {
16442
16615
  graph: graph.export()
16443
16616
  };
16444
16617
  const tmp = `${outPath}.tmp`;
16445
- await fs32.writeFile(tmp, JSON.stringify(payload), "utf8");
16446
- await fs32.rename(tmp, outPath);
16618
+ const handle = await fs33.open(tmp, "w");
16619
+ try {
16620
+ let buffered = "";
16621
+ for (const chunk of serializeGraphChunks(payload)) {
16622
+ buffered += chunk;
16623
+ if (buffered.length >= WRITE_FLUSH_CHARS) {
16624
+ await handle.write(buffered, null, "utf8");
16625
+ buffered = "";
16626
+ }
16627
+ }
16628
+ if (buffered.length > 0) await handle.write(buffered, null, "utf8");
16629
+ } finally {
16630
+ await handle.close();
16631
+ }
16632
+ await fs33.rename(tmp, outPath);
16447
16633
  }
16448
16634
  async function loadGraphFromDisk(graph, outPath) {
16449
16635
  let raw;
16450
16636
  try {
16451
- raw = await fs32.readFile(outPath, "utf8");
16637
+ raw = await fs33.readFile(outPath, "utf8");
16452
16638
  } catch (err) {
16453
16639
  if (err.code === "ENOENT") return;
16454
16640
  throw err;
@@ -16521,7 +16707,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
16521
16707
  }
16522
16708
 
16523
16709
  // src/diff.ts
16524
- import { promises as fs33 } from "fs";
16710
+ import { promises as fs34 } from "fs";
16525
16711
  async function loadSnapshotForDiff(target) {
16526
16712
  if (/^https?:\/\//i.test(target)) {
16527
16713
  const res = await fetch(target);
@@ -16530,7 +16716,7 @@ async function loadSnapshotForDiff(target) {
16530
16716
  }
16531
16717
  return await res.json();
16532
16718
  }
16533
- const raw = await fs33.readFile(target, "utf8");
16719
+ const raw = await fs34.readFile(target, "utf8");
16534
16720
  return JSON.parse(raw);
16535
16721
  }
16536
16722
  function indexEntries(entries) {
@@ -16597,23 +16783,23 @@ function canonicalJson(value) {
16597
16783
  }
16598
16784
 
16599
16785
  // src/projects.ts
16600
- import path68 from "path";
16786
+ import path69 from "path";
16601
16787
  function pathsForProject(project, baseDir) {
16602
16788
  if (project === DEFAULT_PROJECT) {
16603
16789
  return {
16604
- snapshotPath: path68.join(baseDir, "graph.json"),
16605
- errorsPath: path68.join(baseDir, "errors.ndjson"),
16606
- staleEventsPath: path68.join(baseDir, "stale-events.ndjson"),
16607
- embeddingsCachePath: path68.join(baseDir, "embeddings.json"),
16608
- policyViolationsPath: path68.join(baseDir, "policy-violations.ndjson")
16790
+ snapshotPath: path69.join(baseDir, "graph.json"),
16791
+ errorsPath: path69.join(baseDir, "errors.ndjson"),
16792
+ staleEventsPath: path69.join(baseDir, "stale-events.ndjson"),
16793
+ embeddingsCachePath: path69.join(baseDir, "embeddings.json"),
16794
+ policyViolationsPath: path69.join(baseDir, "policy-violations.ndjson")
16609
16795
  };
16610
16796
  }
16611
16797
  return {
16612
- snapshotPath: path68.join(baseDir, `${project}.json`),
16613
- errorsPath: path68.join(baseDir, `errors.${project}.ndjson`),
16614
- staleEventsPath: path68.join(baseDir, `stale-events.${project}.ndjson`),
16615
- embeddingsCachePath: path68.join(baseDir, `embeddings.${project}.json`),
16616
- policyViolationsPath: path68.join(baseDir, `policy-violations.${project}.ndjson`)
16798
+ snapshotPath: path69.join(baseDir, `${project}.json`),
16799
+ errorsPath: path69.join(baseDir, `errors.${project}.ndjson`),
16800
+ staleEventsPath: path69.join(baseDir, `stale-events.${project}.ndjson`),
16801
+ embeddingsCachePath: path69.join(baseDir, `embeddings.${project}.json`),
16802
+ policyViolationsPath: path69.join(baseDir, `policy-violations.${project}.ndjson`)
16617
16803
  };
16618
16804
  }
16619
16805
  var Projects = class {
@@ -16652,9 +16838,9 @@ function parseExtraProjects(raw) {
16652
16838
  }
16653
16839
 
16654
16840
  // src/registry.ts
16655
- import { promises as fs34 } from "fs";
16841
+ import { promises as fs35 } from "fs";
16656
16842
  import os2 from "os";
16657
- import path69 from "path";
16843
+ import path70 from "path";
16658
16844
  import {
16659
16845
  RegistryFileSchema
16660
16846
  } from "@neat.is/types";
@@ -16662,20 +16848,20 @@ var LOCK_TIMEOUT_MS = 5e3;
16662
16848
  var LOCK_RETRY_MS = 50;
16663
16849
  function neatHome() {
16664
16850
  const override = process.env.NEAT_HOME;
16665
- if (override && override.length > 0) return path69.resolve(override);
16666
- return path69.join(os2.homedir(), ".neat");
16851
+ if (override && override.length > 0) return path70.resolve(override);
16852
+ return path70.join(os2.homedir(), ".neat");
16667
16853
  }
16668
16854
  function registryPath() {
16669
- return path69.join(neatHome(), "projects.json");
16855
+ return path70.join(neatHome(), "projects.json");
16670
16856
  }
16671
16857
  function registryLockPath() {
16672
- return path69.join(neatHome(), "projects.json.lock");
16858
+ return path70.join(neatHome(), "projects.json.lock");
16673
16859
  }
16674
16860
  function daemonPidPath() {
16675
- return path69.join(neatHome(), "neatd.pid");
16861
+ return path70.join(neatHome(), "neatd.pid");
16676
16862
  }
16677
16863
  function daemonsDir() {
16678
- return path69.join(neatHome(), "daemons");
16864
+ return path70.join(neatHome(), "daemons");
16679
16865
  }
16680
16866
  function isFiniteInt(v) {
16681
16867
  return typeof v === "number" && Number.isFinite(v);
@@ -16708,7 +16894,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
16708
16894
  const dir = daemonsDir();
16709
16895
  let names;
16710
16896
  try {
16711
- names = await fs34.readdir(dir);
16897
+ names = await fs35.readdir(dir);
16712
16898
  } catch (err) {
16713
16899
  if (err.code === "ENOENT") return [];
16714
16900
  throw err;
@@ -16716,10 +16902,10 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
16716
16902
  const out = [];
16717
16903
  for (const name of names) {
16718
16904
  if (!name.endsWith(".json")) continue;
16719
- const file = path69.join(dir, name);
16905
+ const file = path70.join(dir, name);
16720
16906
  let raw;
16721
16907
  try {
16722
- raw = await fs34.readFile(file, "utf8");
16908
+ raw = await fs35.readFile(file, "utf8");
16723
16909
  } catch {
16724
16910
  continue;
16725
16911
  }
@@ -16732,7 +16918,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
16732
16918
  return out;
16733
16919
  }
16734
16920
  async function removeDaemonRecord(source) {
16735
- await fs34.unlink(source).catch(() => {
16921
+ await fs35.unlink(source).catch(() => {
16736
16922
  });
16737
16923
  }
16738
16924
  async function listMachineProjects(probe = defaultDiscoveryProbe) {
@@ -16789,7 +16975,7 @@ function isPidAliveDefault(pid) {
16789
16975
  }
16790
16976
  async function readPidFile(file) {
16791
16977
  try {
16792
- const raw = await fs34.readFile(file, "utf8");
16978
+ const raw = await fs35.readFile(file, "utf8");
16793
16979
  const pid = Number.parseInt(raw.trim(), 10);
16794
16980
  return Number.isInteger(pid) && pid > 0 ? pid : void 0;
16795
16981
  } catch {
@@ -16815,14 +17001,12 @@ async function readLockPid(lockPath) {
16815
17001
  async function classifyLockHolder(lockPath, probe = defaultLockHolderProbe) {
16816
17002
  const lockPid = await readLockPid(lockPath);
16817
17003
  const daemonPid = await probe.daemonPidFromFile();
16818
- if (daemonPid !== void 0 && daemonPid !== process.pid && probe.isPidAlive(daemonPid) && // The lock already names the daemon, or the daemon answers on its port.
16819
- // Either confirms a live daemon is in the picture (the second guards
16820
- // against a stale pidfile whose PID got reused).
16821
- (daemonPid === lockPid || await probe.daemonResponds())) {
16822
- return { kind: "daemon", pid: daemonPid };
16823
- }
16824
17004
  if (lockPid !== void 0 && lockPid !== process.pid && probe.isPidAlive(lockPid)) {
16825
- return { kind: "command", pid: lockPid };
17005
+ return { kind: lockPid === daemonPid ? "daemon" : "command", pid: lockPid };
17006
+ }
17007
+ if (daemonPid !== void 0 && daemonPid !== process.pid && probe.isPidAlive(daemonPid) && // Guards against a stale pidfile whose PID got reused.
17008
+ await probe.daemonResponds()) {
17009
+ return { kind: "daemon", pid: daemonPid };
16826
17010
  }
16827
17011
  return { kind: "stale" };
16828
17012
  }
@@ -16833,36 +17017,36 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
16833
17017
  case "command":
16834
17018
  return `Another neat command (pid ${holder.pid}) is holding the registry lock. Wait for it to finish, or check \`ps\` if you're not sure what's running.`;
16835
17019
  case "stale":
16836
- return `neat registry: timed out after ${timeoutMs}ms waiting for ${lockPath}. Another neat process is holding the lock; if no such process exists, remove the file by hand.`;
17020
+ return `timed out after ${timeoutMs}ms waiting for ${lockPath}. Another neat process is holding the lock; if no such process exists, remove the file by hand.`;
16837
17021
  }
16838
17022
  }
16839
17023
  async function normalizeProjectPath(input) {
16840
- const resolved = path69.resolve(input);
17024
+ const resolved = path70.resolve(input);
16841
17025
  try {
16842
- return await fs34.realpath(resolved);
17026
+ return await fs35.realpath(resolved);
16843
17027
  } catch {
16844
17028
  return resolved;
16845
17029
  }
16846
17030
  }
16847
17031
  async function writeAtomically(target, contents) {
16848
- await fs34.mkdir(path69.dirname(target), { recursive: true });
17032
+ await fs35.mkdir(path70.dirname(target), { recursive: true });
16849
17033
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
16850
- const fd = await fs34.open(tmp, "w");
17034
+ const fd = await fs35.open(tmp, "w");
16851
17035
  try {
16852
17036
  await fd.writeFile(contents, "utf8");
16853
17037
  await fd.sync();
16854
17038
  } finally {
16855
17039
  await fd.close();
16856
17040
  }
16857
- await fs34.rename(tmp, target);
17041
+ await fs35.rename(tmp, target);
16858
17042
  }
16859
17043
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
16860
17044
  const deadline = Date.now() + timeoutMs;
16861
- await fs34.mkdir(path69.dirname(lockPath), { recursive: true });
17045
+ await fs35.mkdir(path70.dirname(lockPath), { recursive: true });
16862
17046
  let probedHolder = false;
16863
17047
  while (true) {
16864
17048
  try {
16865
- const fd = await fs34.open(lockPath, "wx");
17049
+ const fd = await fs35.open(lockPath, "wx");
16866
17050
  try {
16867
17051
  await fd.writeFile(`${process.pid}
16868
17052
  `, "utf8");
@@ -16876,18 +17060,36 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
16876
17060
  if (!probedHolder) {
16877
17061
  probedHolder = true;
16878
17062
  const holder = await classifyLockHolder(lockPath, probe);
16879
- if (holder.kind === "daemon") throw new Error(lockHolderMessage(holder, lockPath, timeoutMs));
17063
+ if (holder.kind === "daemon") {
17064
+ throw new RegistryLockError(lockHolderMessage(holder, lockPath, timeoutMs));
17065
+ }
17066
+ if (holder.kind === "stale" && await isReclaimableLock(lockPath)) {
17067
+ await fs35.unlink(lockPath).catch(() => {
17068
+ });
17069
+ continue;
17070
+ }
16880
17071
  }
16881
17072
  if (Date.now() >= deadline) {
16882
17073
  const holder = await classifyLockHolder(lockPath, probe);
16883
- throw new Error(lockHolderMessage(holder, lockPath, timeoutMs));
17074
+ throw new RegistryLockError(lockHolderMessage(holder, lockPath, timeoutMs));
16884
17075
  }
16885
17076
  await new Promise((r) => setTimeout(r, LOCK_RETRY_MS));
16886
17077
  }
16887
17078
  }
16888
17079
  }
17080
+ var LOCK_UNSTAMPED_GRACE_MS = 5e3;
17081
+ async function isReclaimableLock(lockPath) {
17082
+ const pid = await readLockPid(lockPath);
17083
+ if (pid !== void 0) return true;
17084
+ try {
17085
+ const stat = await fs35.stat(lockPath);
17086
+ return Date.now() - stat.mtimeMs > LOCK_UNSTAMPED_GRACE_MS;
17087
+ } catch {
17088
+ return false;
17089
+ }
17090
+ }
16889
17091
  async function releaseLock(lockPath) {
16890
- await fs34.unlink(lockPath).catch(() => {
17092
+ await fs35.unlink(lockPath).catch(() => {
16891
17093
  });
16892
17094
  }
16893
17095
  async function withLock(fn) {
@@ -16903,7 +17105,7 @@ async function readRegistry() {
16903
17105
  const file = registryPath();
16904
17106
  let raw;
16905
17107
  try {
16906
- raw = await fs34.readFile(file, "utf8");
17108
+ raw = await fs35.readFile(file, "utf8");
16907
17109
  } catch (err) {
16908
17110
  if (err.code === "ENOENT") {
16909
17111
  return { version: 1, projects: [] };
@@ -16917,10 +17119,22 @@ async function writeRegistry(reg) {
16917
17119
  const validated = RegistryFileSchema.parse(reg);
16918
17120
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
16919
17121
  }
16920
- var ProjectNameCollisionError = class extends Error {
17122
+ var RegistryError = class extends Error {
17123
+ constructor(message) {
17124
+ super(message);
17125
+ this.name = "RegistryError";
17126
+ }
17127
+ };
17128
+ var RegistryLockError = class extends RegistryError {
17129
+ constructor(message) {
17130
+ super(message);
17131
+ this.name = "RegistryLockError";
17132
+ }
17133
+ };
17134
+ var ProjectNameCollisionError = class extends RegistryError {
16921
17135
  projectName;
16922
17136
  constructor(name) {
16923
- super(`neat registry: a project named "${name}" is already registered`);
17137
+ super(`a project named "${name}" is already registered`);
16924
17138
  this.name = "ProjectNameCollisionError";
16925
17139
  this.projectName = name;
16926
17140
  }
@@ -16961,6 +17175,16 @@ async function getProject(name) {
16961
17175
  const reg = await readRegistry();
16962
17176
  return reg.projects.find((p) => p.name === name);
16963
17177
  }
17178
+ async function findProjectByPath(dir) {
17179
+ const here = await normalizeProjectPath(dir);
17180
+ const { projects } = await readRegistry();
17181
+ let best;
17182
+ for (const entry of projects) {
17183
+ const inside = here === entry.path || here.startsWith(entry.path + path70.sep);
17184
+ if (inside && (!best || entry.path.length > best.path.length)) best = entry;
17185
+ }
17186
+ return best;
17187
+ }
16964
17188
  async function listProjects() {
16965
17189
  const reg = await readRegistry();
16966
17190
  return reg.projects;
@@ -16969,7 +17193,7 @@ async function setStatus(name, status) {
16969
17193
  return withLock(async () => {
16970
17194
  const reg = await readRegistry();
16971
17195
  const entry = reg.projects.find((p) => p.name === name);
16972
- if (!entry) throw new Error(`neat registry: no project named "${name}"`);
17196
+ if (!entry) throw new RegistryError(`no project named "${name}"`);
16973
17197
  entry.status = status;
16974
17198
  await writeRegistry(reg);
16975
17199
  return entry;
@@ -17008,7 +17232,7 @@ function pruneTtlMs() {
17008
17232
  }
17009
17233
  async function statPathStatus(p) {
17010
17234
  try {
17011
- const stat = await fs34.stat(p);
17235
+ const stat = await fs35.stat(p);
17012
17236
  return stat.isDirectory() ? "present" : "unknown";
17013
17237
  } catch (err) {
17014
17238
  return err.code === "ENOENT" ? "gone" : "unknown";
@@ -17053,14 +17277,14 @@ import cors from "@fastify/cors";
17053
17277
  import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } from "@neat.is/types";
17054
17278
 
17055
17279
  // src/extend/index.ts
17056
- import { promises as fs36 } from "fs";
17057
- import path71 from "path";
17280
+ import { promises as fs37 } from "fs";
17281
+ import path72 from "path";
17058
17282
  import os3 from "os";
17059
17283
  import { resolve as registryResolve, list as registryList } from "@neat.is/instrumentation-registry";
17060
17284
 
17061
17285
  // src/installers/package-manager.ts
17062
- import { promises as fs35 } from "fs";
17063
- import path70 from "path";
17286
+ import { promises as fs36 } from "fs";
17287
+ import path71 from "path";
17064
17288
  import { spawn } from "child_process";
17065
17289
  var LOCKFILE_PRIORITY = [
17066
17290
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -17075,29 +17299,29 @@ var LOCKFILE_PRIORITY = [
17075
17299
  var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
17076
17300
  async function exists2(p) {
17077
17301
  try {
17078
- await fs35.access(p);
17302
+ await fs36.access(p);
17079
17303
  return true;
17080
17304
  } catch {
17081
17305
  return false;
17082
17306
  }
17083
17307
  }
17084
17308
  async function detectPackageManager(serviceDir) {
17085
- let dir = path70.resolve(serviceDir);
17309
+ let dir = path71.resolve(serviceDir);
17086
17310
  const stops = /* @__PURE__ */ new Set();
17087
17311
  for (let i = 0; i < 64; i++) {
17088
17312
  if (stops.has(dir)) break;
17089
17313
  stops.add(dir);
17090
17314
  for (const candidate of LOCKFILE_PRIORITY) {
17091
- const lockPath = path70.join(dir, candidate.lockfile);
17315
+ const lockPath = path71.join(dir, candidate.lockfile);
17092
17316
  if (await exists2(lockPath)) {
17093
17317
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
17094
17318
  }
17095
17319
  }
17096
- const parent = path70.dirname(dir);
17320
+ const parent = path71.dirname(dir);
17097
17321
  if (parent === dir) break;
17098
17322
  dir = parent;
17099
17323
  }
17100
- return { pm: "npm", cwd: path70.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
17324
+ return { pm: "npm", cwd: path71.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
17101
17325
  }
17102
17326
  async function runPackageManagerInstall(cmd) {
17103
17327
  return new Promise((resolve) => {
@@ -17139,15 +17363,15 @@ ${err.message}`
17139
17363
  // src/extend/index.ts
17140
17364
  async function fileExists2(p) {
17141
17365
  try {
17142
- await fs36.access(p);
17366
+ await fs37.access(p);
17143
17367
  return true;
17144
17368
  } catch {
17145
17369
  return false;
17146
17370
  }
17147
17371
  }
17148
17372
  async function readPackageJson(scanPath) {
17149
- const pkgPath = path71.join(scanPath, "package.json");
17150
- const raw = await fs36.readFile(pkgPath, "utf8");
17373
+ const pkgPath = path72.join(scanPath, "package.json");
17374
+ const raw = await fs37.readFile(pkgPath, "utf8");
17151
17375
  return JSON.parse(raw);
17152
17376
  }
17153
17377
  var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
@@ -17161,15 +17385,15 @@ var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
17161
17385
  async function findHookFiles(scanPath) {
17162
17386
  const found = [];
17163
17387
  const walk11 = async (dir) => {
17164
- const entries = await fs36.readdir(dir, { withFileTypes: true }).catch(() => []);
17388
+ const entries = await fs37.readdir(dir, { withFileTypes: true }).catch(() => []);
17165
17389
  for (const entry of entries) {
17166
17390
  if (entry.isDirectory()) {
17167
17391
  if (entry.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry.name)) continue;
17168
- await walk11(path71.join(dir, entry.name));
17392
+ await walk11(path72.join(dir, entry.name));
17169
17393
  } else if (entry.isFile()) {
17170
17394
  if ((entry.name.startsWith("instrumentation") || entry.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry.name)) {
17171
- const rel = path71.relative(scanPath, path71.join(dir, entry.name));
17172
- found.push(rel.split(path71.sep).join("/"));
17395
+ const rel = path72.relative(scanPath, path72.join(dir, entry.name));
17396
+ found.push(rel.split(path72.sep).join("/"));
17173
17397
  }
17174
17398
  }
17175
17399
  }
@@ -17180,7 +17404,7 @@ async function findHookFiles(scanPath) {
17180
17404
  async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
17181
17405
  let fallback = null;
17182
17406
  for (const file of hookFiles) {
17183
- const content = await fs36.readFile(path71.join(scanPath, file), "utf8");
17407
+ const content = await fs37.readFile(path72.join(scanPath, file), "utf8");
17184
17408
  const patched = splicedContent(content, snippet2);
17185
17409
  if (patched !== null) return { file, content, patched };
17186
17410
  if (fallback === null) fallback = { file, content };
@@ -17188,12 +17412,12 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
17188
17412
  return { file: fallback.file, content: fallback.content, patched: null };
17189
17413
  }
17190
17414
  function extendLogPath() {
17191
- return process.env.NEAT_EXTEND_LOG ?? path71.join(os3.homedir(), ".neat", "extend-log.ndjson");
17415
+ return process.env.NEAT_EXTEND_LOG ?? path72.join(os3.homedir(), ".neat", "extend-log.ndjson");
17192
17416
  }
17193
17417
  async function appendExtendLog(entry) {
17194
17418
  const logPath = extendLogPath();
17195
- await fs36.mkdir(path71.dirname(logPath), { recursive: true });
17196
- await fs36.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
17419
+ await fs37.mkdir(path72.dirname(logPath), { recursive: true });
17420
+ await fs37.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
17197
17421
  }
17198
17422
  function splicedContent(fileContent, snippet2) {
17199
17423
  if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
@@ -17251,7 +17475,7 @@ function lookupInstrumentation(library, installedVersion) {
17251
17475
  }
17252
17476
  async function describeProjectInstrumentation(ctx) {
17253
17477
  const hookFiles = await findHookFiles(ctx.scanPath);
17254
- const envNeat = await fileExists2(path71.join(ctx.scanPath, ".env.neat"));
17478
+ const envNeat = await fileExists2(path72.join(ctx.scanPath, ".env.neat"));
17255
17479
  const registryInstrPackages = new Set(
17256
17480
  registryList().map((e) => e.instrumentation_package).filter((p) => !!p)
17257
17481
  );
@@ -17273,7 +17497,7 @@ async function applyExtension(ctx, args, options) {
17273
17497
  );
17274
17498
  }
17275
17499
  for (const file of hookFiles) {
17276
- const content = await fs36.readFile(path71.join(ctx.scanPath, file), "utf8");
17500
+ const content = await fs37.readFile(path72.join(ctx.scanPath, file), "utf8");
17277
17501
  if (content.includes(args.registration_snippet)) {
17278
17502
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
17279
17503
  }
@@ -17285,18 +17509,18 @@ async function applyExtension(ctx, args, options) {
17285
17509
  );
17286
17510
  }
17287
17511
  const primaryFile = primary.file;
17288
- const primaryPath = path71.join(ctx.scanPath, primaryFile);
17512
+ const primaryPath = path72.join(ctx.scanPath, primaryFile);
17289
17513
  const filesTouched = [];
17290
17514
  const depsAdded = [];
17291
- const pkgPath = path71.join(ctx.scanPath, "package.json");
17515
+ const pkgPath = path72.join(ctx.scanPath, "package.json");
17292
17516
  const pkg = await readPackageJson(ctx.scanPath);
17293
17517
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
17294
17518
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
17295
- await fs36.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
17519
+ await fs37.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
17296
17520
  filesTouched.push("package.json");
17297
17521
  depsAdded.push(`${args.instrumentation_package}@${args.version}`);
17298
17522
  }
17299
- await fs36.writeFile(primaryPath, primary.patched, "utf8");
17523
+ await fs37.writeFile(primaryPath, primary.patched, "utf8");
17300
17524
  filesTouched.push(primaryFile);
17301
17525
  const cmd = await detectPackageManager(ctx.scanPath);
17302
17526
  const installer = options?.runInstall ?? runPackageManagerInstall;
@@ -17327,7 +17551,7 @@ async function dryRunExtension(ctx, args) {
17327
17551
  };
17328
17552
  }
17329
17553
  for (const file of hookFiles) {
17330
- const content = await fs36.readFile(path71.join(ctx.scanPath, file), "utf8");
17554
+ const content = await fs37.readFile(path72.join(ctx.scanPath, file), "utf8");
17331
17555
  if (content.includes(args.registration_snippet)) {
17332
17556
  return {
17333
17557
  library: args.library,
@@ -17362,28 +17586,28 @@ async function rollbackExtension(ctx, args) {
17362
17586
  if (!await fileExists2(logPath)) {
17363
17587
  return { undone: false, message: "no apply found for library" };
17364
17588
  }
17365
- const raw = await fs36.readFile(logPath, "utf8");
17589
+ const raw = await fs37.readFile(logPath, "utf8");
17366
17590
  const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
17367
17591
  const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
17368
17592
  if (!match) {
17369
17593
  return { undone: false, message: "no apply found for library" };
17370
17594
  }
17371
- const pkgPath = path71.join(ctx.scanPath, "package.json");
17595
+ const pkgPath = path72.join(ctx.scanPath, "package.json");
17372
17596
  if (await fileExists2(pkgPath)) {
17373
17597
  const pkg = await readPackageJson(ctx.scanPath);
17374
17598
  if (pkg.dependencies?.[match.instrumentation_package]) {
17375
17599
  const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
17376
17600
  pkg.dependencies = rest;
17377
- await fs36.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
17601
+ await fs37.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
17378
17602
  }
17379
17603
  }
17380
17604
  const hookFiles = await findHookFiles(ctx.scanPath);
17381
17605
  for (const file of hookFiles) {
17382
- const filePath = path71.join(ctx.scanPath, file);
17383
- const content = await fs36.readFile(filePath, "utf8");
17606
+ const filePath = path72.join(ctx.scanPath, file);
17607
+ const content = await fs37.readFile(filePath, "utf8");
17384
17608
  if (content.includes(match.registration_snippet)) {
17385
17609
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
17386
- await fs36.writeFile(filePath, filtered, "utf8");
17610
+ await fs37.writeFile(filePath, filtered, "utf8");
17387
17611
  break;
17388
17612
  }
17389
17613
  }
@@ -18399,8 +18623,8 @@ data: ${JSON.stringify(envelope.payload)}
18399
18623
 
18400
18624
  // src/connectors-config.ts
18401
18625
  import os4 from "os";
18402
- import path72 from "path";
18403
- import { promises as fs37 } from "fs";
18626
+ import path73 from "path";
18627
+ import { promises as fs38 } from "fs";
18404
18628
  var CONNECTORS_CONFIG_VERSION = 1;
18405
18629
  var EnvRefUnsetError = class extends Error {
18406
18630
  ref;
@@ -18414,17 +18638,17 @@ var EnvRefUnsetError = class extends Error {
18414
18638
  };
18415
18639
  function neatHome2() {
18416
18640
  const override = process.env.NEAT_HOME;
18417
- if (override && override.length > 0) return path72.resolve(override);
18418
- return path72.join(os4.homedir(), ".neat");
18641
+ if (override && override.length > 0) return path73.resolve(override);
18642
+ return path73.join(os4.homedir(), ".neat");
18419
18643
  }
18420
18644
  function connectorsConfigPath(home = neatHome2()) {
18421
- return path72.join(home, "connectors.json");
18645
+ return path73.join(home, "connectors.json");
18422
18646
  }
18423
18647
  var MODE_MASK_LOOSER_THAN_0600 = 63;
18424
18648
  async function warnIfModeLooserThan0600(file) {
18425
18649
  if (process.platform === "win32") return;
18426
18650
  try {
18427
- const stat = await fs37.stat(file);
18651
+ const stat = await fs38.stat(file);
18428
18652
  if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
18429
18653
  const mode = (stat.mode & 511).toString(8).padStart(3, "0");
18430
18654
  console.warn(
@@ -18438,7 +18662,7 @@ async function readConnectorsConfig(home = neatHome2()) {
18438
18662
  const file = connectorsConfigPath(home);
18439
18663
  let raw;
18440
18664
  try {
18441
- raw = await fs37.readFile(file, "utf8");
18665
+ raw = await fs38.readFile(file, "utf8");
18442
18666
  } catch (err) {
18443
18667
  if (err.code === "ENOENT") {
18444
18668
  return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
@@ -18549,7 +18773,7 @@ function connectorMatchesProject(entry, project) {
18549
18773
  var CONNECTORS_LOCK_TIMEOUT_MS = 5e3;
18550
18774
  var CONNECTORS_LOCK_RETRY_MS = 50;
18551
18775
  function connectorsConfigLockPath(home = neatHome2()) {
18552
- return path72.join(home, "connectors.json.lock");
18776
+ return path73.join(home, "connectors.json.lock");
18553
18777
  }
18554
18778
  function isEnvRef(value) {
18555
18779
  return value.length > 1 && value.startsWith("$");
@@ -18562,9 +18786,9 @@ function redactCredentialRef(ref) {
18562
18786
  return out;
18563
18787
  }
18564
18788
  async function writeConfigAtomically0600(file, contents) {
18565
- await fs37.mkdir(path72.dirname(file), { recursive: true });
18789
+ await fs38.mkdir(path73.dirname(file), { recursive: true });
18566
18790
  const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
18567
- const fd = await fs37.open(tmp, "w", 384);
18791
+ const fd = await fs38.open(tmp, "w", 384);
18568
18792
  try {
18569
18793
  await fd.writeFile(contents, "utf8");
18570
18794
  await fd.chmod(384);
@@ -18572,14 +18796,14 @@ async function writeConfigAtomically0600(file, contents) {
18572
18796
  } finally {
18573
18797
  await fd.close();
18574
18798
  }
18575
- await fs37.rename(tmp, file);
18799
+ await fs38.rename(tmp, file);
18576
18800
  }
18577
18801
  async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEOUT_MS) {
18578
18802
  const deadline = Date.now() + timeoutMs;
18579
- await fs37.mkdir(path72.dirname(lockPath), { recursive: true });
18803
+ await fs38.mkdir(path73.dirname(lockPath), { recursive: true });
18580
18804
  for (; ; ) {
18581
18805
  try {
18582
- const fd = await fs37.open(lockPath, "wx");
18806
+ const fd = await fs38.open(lockPath, "wx");
18583
18807
  try {
18584
18808
  await fd.writeFile(`${process.pid}
18585
18809
  `, "utf8");
@@ -18599,7 +18823,7 @@ async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEO
18599
18823
  }
18600
18824
  }
18601
18825
  async function releaseConnectorsLock(lockPath) {
18602
- await fs37.unlink(lockPath).catch(() => {
18826
+ await fs38.unlink(lockPath).catch(() => {
18603
18827
  });
18604
18828
  }
18605
18829
  async function withConnectorsLock(home, fn) {
@@ -19339,10 +19563,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
19339
19563
  // src/connectors/supabase/map.ts
19340
19564
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
19341
19565
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
19342
- function targetFromRestPath(path73) {
19343
- const rpcMatch = REST_RPC_PATH_RE.exec(path73);
19566
+ function targetFromRestPath(path74) {
19567
+ const rpcMatch = REST_RPC_PATH_RE.exec(path74);
19344
19568
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
19345
- const tableMatch = REST_TABLE_PATH_RE.exec(path73);
19569
+ const tableMatch = REST_TABLE_PATH_RE.exec(path74);
19346
19570
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
19347
19571
  return null;
19348
19572
  }
@@ -19937,9 +20161,9 @@ function parseFirebaseTargetName(targetName) {
19937
20161
  const secondSep = rest.indexOf(FIELD_SEP);
19938
20162
  if (secondSep === -1) return null;
19939
20163
  const method = rest.slice(0, secondSep);
19940
- const path73 = rest.slice(secondSep + 1);
19941
- if (!resourceName || !method || !path73) return null;
19942
- return { resourceName, method, path: path73 };
20164
+ const path74 = rest.slice(secondSep + 1);
20165
+ if (!resourceName || !method || !path74) return null;
20166
+ return { resourceName, method, path: path74 };
19943
20167
  }
19944
20168
  function resourceNameFor(type, labels) {
19945
20169
  if (!labels) return null;
@@ -19977,14 +20201,14 @@ function mapLogEntryToSignal(entry) {
19977
20201
  if (!req) return null;
19978
20202
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
19979
20203
  const method = req.requestMethod.toUpperCase();
19980
- const path73 = pathFromRequestUrl(req.requestUrl);
19981
- if (path73 === null) return null;
20204
+ const path74 = pathFromRequestUrl(req.requestUrl);
20205
+ if (path74 === null) return null;
19982
20206
  const timestamp = entry.timestamp;
19983
20207
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
19984
20208
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
19985
20209
  return {
19986
20210
  targetKind: resourceType,
19987
- targetName: packFirebaseTargetName({ resourceName, method, path: path73 }),
20211
+ targetName: packFirebaseTargetName({ resourceName, method, path: path74 }),
19988
20212
  callCount: 1,
19989
20213
  errorCount: isError ? 1 : 0,
19990
20214
  lastObservedIso: timestamp
@@ -20007,9 +20231,9 @@ import { NodeType as NodeType35 } from "@neat.is/types";
20007
20231
  function normalizeName(name) {
20008
20232
  return name.toLowerCase().replace(/[^a-z0-9]/g, "");
20009
20233
  }
20010
- function inferServiceName(graph, resourceName, method, path73) {
20234
+ function inferServiceName(graph, resourceName, method, path74) {
20011
20235
  const wantedMethod = method.toUpperCase();
20012
- const wantedPath = normalizePathTemplate(path73);
20236
+ const wantedPath = normalizePathTemplate(path74);
20013
20237
  const services = [];
20014
20238
  const owners = /* @__PURE__ */ new Set();
20015
20239
  graph.forEachNode((_id, attrs) => {
@@ -20217,7 +20441,7 @@ function mapEventToSignal(event) {
20217
20441
  if (Number.isNaN(observedAt.getTime())) return null;
20218
20442
  const statusCode = metadata?.statusCode;
20219
20443
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
20220
- const path73 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
20444
+ const path74 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
20221
20445
  return {
20222
20446
  targetKind: CLOUDFLARE_TARGET_KIND,
20223
20447
  targetName: scriptName,
@@ -20225,7 +20449,7 @@ function mapEventToSignal(event) {
20225
20449
  errorCount: isError ? 1 : 0,
20226
20450
  lastObservedIso: observedAt.toISOString(),
20227
20451
  method,
20228
- ...path73 ? { path: path73 } : {},
20452
+ ...path74 ? { path: path74 } : {},
20229
20453
  ...typeof statusCode === "number" ? { statusCode } : {},
20230
20454
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
20231
20455
  };
@@ -20271,8 +20495,8 @@ function findTaggedWorkerFileNode(graph, workerName) {
20271
20495
  });
20272
20496
  return found;
20273
20497
  }
20274
- function findMatchingRouteNode(graph, serviceName, method, path73) {
20275
- const normalizedPath = normalizePathTemplate(path73);
20498
+ function findMatchingRouteNode(graph, serviceName, method, path74) {
20499
+ const normalizedPath = normalizePathTemplate(path74);
20276
20500
  let found = null;
20277
20501
  graph.forEachNode((id, attrs) => {
20278
20502
  if (found) return;
@@ -20289,10 +20513,10 @@ function createCloudflareResolveTarget(config, graph) {
20289
20513
  return (signal) => {
20290
20514
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
20291
20515
  const scriptName = signal.targetName;
20292
- const { method, path: path73 } = signal;
20516
+ const { method, path: path74 } = signal;
20293
20517
  const resolveRouteGrain = (serviceName, wholeFileId) => {
20294
- if (!method || !path73) return wholeFileId;
20295
- return findMatchingRouteNode(graph, serviceName, method, path73) ?? wholeFileId;
20518
+ if (!method || !path74) return wholeFileId;
20519
+ return findMatchingRouteNode(graph, serviceName, method, path74) ?? wholeFileId;
20296
20520
  };
20297
20521
  const mapping = config.workers?.[scriptName];
20298
20522
  if (mapping) {
@@ -20622,9 +20846,9 @@ function parseCloudRunTargetName(targetName) {
20622
20846
  const secondSep = rest.indexOf(FIELD_SEP2);
20623
20847
  if (secondSep === -1) return null;
20624
20848
  const method = rest.slice(0, secondSep);
20625
- const path73 = rest.slice(secondSep + 1);
20626
- if (!serviceName || !method || !path73) return null;
20627
- return { serviceName, method, path: path73 };
20849
+ const path74 = rest.slice(secondSep + 1);
20850
+ if (!serviceName || !method || !path74) return null;
20851
+ return { serviceName, method, path: path74 };
20628
20852
  }
20629
20853
 
20630
20854
  // src/connectors/cloud-run/map.ts
@@ -20653,14 +20877,14 @@ function mapLogEntryToSignal2(entry) {
20653
20877
  if (!req) return null;
20654
20878
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
20655
20879
  const method = req.requestMethod.toUpperCase();
20656
- const path73 = pathFromRequestUrl2(req.requestUrl);
20657
- if (path73 === null) return null;
20880
+ const path74 = pathFromRequestUrl2(req.requestUrl);
20881
+ if (path74 === null) return null;
20658
20882
  const timestamp = entry.timestamp;
20659
20883
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
20660
20884
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
20661
20885
  return {
20662
20886
  targetKind: CLOUD_RUN_TARGET_KIND,
20663
- targetName: packCloudRunTargetName({ serviceName, method, path: path73 }),
20887
+ targetName: packCloudRunTargetName({ serviceName, method, path: path74 }),
20664
20888
  callCount: 1,
20665
20889
  errorCount: isError ? 1 : 0,
20666
20890
  lastObservedIso: timestamp
@@ -20698,14 +20922,14 @@ function createCloudRunResolveTarget(graph, config) {
20698
20922
  if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
20699
20923
  const identity = parseCloudRunTargetName(signal.targetName);
20700
20924
  if (!identity) return null;
20701
- const { serviceName: gcpServiceName, method, path: path73 } = identity;
20702
- const mappedService = config.serviceMap?.[gcpServiceName] ?? (config.inferServices ? inferServiceName(graph, gcpServiceName, method, path73) ?? void 0 : void 0);
20925
+ const { serviceName: gcpServiceName, method, path: path74 } = identity;
20926
+ const mappedService = config.serviceMap?.[gcpServiceName] ?? (config.inferServices ? inferServiceName(graph, gcpServiceName, method, path74) ?? void 0 : void 0);
20703
20927
  if (mappedService) {
20704
20928
  const routeNodeId = findMatchingRouteNode2(
20705
20929
  graph,
20706
20930
  mappedService,
20707
20931
  method,
20708
- normalizePathTemplate(path73)
20932
+ normalizePathTemplate(path74)
20709
20933
  );
20710
20934
  if (routeNodeId) {
20711
20935
  return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: EdgeType32.CALLS };
@@ -20833,9 +21057,9 @@ function parseGcpLbTargetName(targetName) {
20833
21057
  const secondSep = rest.indexOf(FIELD_SEP3);
20834
21058
  if (secondSep === -1) return null;
20835
21059
  const method = rest.slice(0, secondSep);
20836
- const path73 = rest.slice(secondSep + 1);
20837
- if (!backendServiceName || !method || !path73) return null;
20838
- return { backendServiceName, method, path: path73 };
21060
+ const path74 = rest.slice(secondSep + 1);
21061
+ if (!backendServiceName || !method || !path74) return null;
21062
+ return { backendServiceName, method, path: path74 };
20839
21063
  }
20840
21064
 
20841
21065
  // src/connectors/gcp-lb/map.ts
@@ -20864,14 +21088,14 @@ function mapLogEntryToSignal3(entry) {
20864
21088
  if (!req) return null;
20865
21089
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
20866
21090
  const method = req.requestMethod.toUpperCase();
20867
- const path73 = pathFromRequestUrl3(req.requestUrl);
20868
- if (path73 === null) return null;
21091
+ const path74 = pathFromRequestUrl3(req.requestUrl);
21092
+ if (path74 === null) return null;
20869
21093
  const timestamp = entry.timestamp;
20870
21094
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
20871
21095
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD5;
20872
21096
  return {
20873
21097
  targetKind: GCP_LB_TARGET_KIND,
20874
- targetName: packGcpLbTargetName({ backendServiceName, method, path: path73 }),
21098
+ targetName: packGcpLbTargetName({ backendServiceName, method, path: path74 }),
20875
21099
  callCount: 1,
20876
21100
  errorCount: isError ? 1 : 0,
20877
21101
  lastObservedIso: timestamp
@@ -20909,14 +21133,14 @@ function createGcpLbResolveTarget(graph, config) {
20909
21133
  if (signal.targetKind !== GCP_LB_TARGET_KIND) return null;
20910
21134
  const identity = parseGcpLbTargetName(signal.targetName);
20911
21135
  if (!identity) return null;
20912
- const { backendServiceName, method, path: path73 } = identity;
20913
- const mappedService = config.backendServiceMap?.[backendServiceName] ?? (config.inferServices ? inferServiceName(graph, backendServiceName, method, path73) ?? void 0 : void 0);
21136
+ const { backendServiceName, method, path: path74 } = identity;
21137
+ const mappedService = config.backendServiceMap?.[backendServiceName] ?? (config.inferServices ? inferServiceName(graph, backendServiceName, method, path74) ?? void 0 : void 0);
20914
21138
  if (mappedService) {
20915
21139
  const routeNodeId = findMatchingRouteNode3(
20916
21140
  graph,
20917
21141
  mappedService,
20918
21142
  method,
20919
- normalizePathTemplate(path73)
21143
+ normalizePathTemplate(path74)
20920
21144
  );
20921
21145
  if (routeNodeId) {
20922
21146
  return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: EdgeType33.CALLS };
@@ -22300,16 +22524,18 @@ function serializeGraph(graph) {
22300
22524
  });
22301
22525
  return { nodes, edges };
22302
22526
  }
22303
- function projectFromReq(req, singleProject) {
22527
+ function projectFromReq(req, singleProject, registry) {
22304
22528
  const params = req.params;
22305
22529
  const named = params.project;
22306
22530
  if (singleProject) {
22307
22531
  return named === void 0 || named === DEFAULT_PROJECT ? singleProject : named;
22308
22532
  }
22309
- return named ?? DEFAULT_PROJECT;
22533
+ if (named !== void 0) return named;
22534
+ const hosted = registry?.list() ?? [];
22535
+ return hosted.length === 1 ? hosted[0] : DEFAULT_PROJECT;
22310
22536
  }
22311
22537
  function resolveProject(registry, req, reply, bootstrap, singleProject) {
22312
- const name = projectFromReq(req, singleProject);
22538
+ const name = projectFromReq(req, singleProject, registry);
22313
22539
  const ctx = registry.get(name);
22314
22540
  if (!ctx) {
22315
22541
  const phase = bootstrap?.status(name);
@@ -22321,10 +22547,13 @@ function resolveProject(registry, req, reply, bootstrap, singleProject) {
22321
22547
  void reply.code(503).send({ ready: false, project: name, status: "broken" });
22322
22548
  return null;
22323
22549
  }
22550
+ const unnamed = req.params.project === void 0;
22551
+ const hosted = registry.list();
22552
+ const hint = unnamed && name === DEFAULT_PROJECT && hosted.length > 1 ? `This daemon hosts ${hosted.length} projects and none is named \`default\`: ${hosted.join(", ")}. Name one as /projects/<name>/\u2026 , or reach that project's own daemon.` : "This daemon does not host that project. GET /projects lists what it serves (see hostedHere).";
22324
22553
  void reply.code(404).send({
22325
22554
  error: "project not found",
22326
22555
  project: name,
22327
- hint: "This daemon does not host that project. GET /projects lists what it serves (see hostedHere)."
22556
+ hint
22328
22557
  });
22329
22558
  return null;
22330
22559
  }
@@ -23221,6 +23450,7 @@ export {
23221
23450
  PolicyViolationsLog,
23222
23451
  isStrictExtractionEnabled,
23223
23452
  formatExtractionBanner,
23453
+ formatSkippedBanner,
23224
23454
  formatPrecisionFloorBanner,
23225
23455
  addImports,
23226
23456
  addRoutes,
@@ -23269,9 +23499,11 @@ export {
23269
23499
  normalizeProjectPath,
23270
23500
  writeAtomically,
23271
23501
  readRegistry,
23502
+ RegistryError,
23272
23503
  ProjectNameCollisionError,
23273
23504
  addProject,
23274
23505
  getProject,
23506
+ findProjectByPath,
23275
23507
  listProjects,
23276
23508
  setStatus,
23277
23509
  touchLastSeen,
@@ -23298,4 +23530,4 @@ export {
23298
23530
  deprovisionConnector,
23299
23531
  buildApi
23300
23532
  };
23301
- //# sourceMappingURL=chunk-E3YFFIHE.js.map
23533
+ //# sourceMappingURL=chunk-LNIVRE4L.js.map