@neat.is/core 0.10.5 → 0.10.6-dev.20261007

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.
@@ -343,13 +343,35 @@ function deprecatedApis() {
343
343
  }
344
344
 
345
345
  // src/ingest.ts
346
- import { promises as fs8, existsSync, readFileSync } from "fs";
347
- import path9 from "path";
348
- import * as sourceMapJs from "source-map-js";
346
+ import { promises as fs9, existsSync, readFileSync } from "fs";
347
+ import path10 from "path";
349
348
 
350
- // src/policy.ts
349
+ // src/durable-incident.ts
351
350
  import { promises as fs2 } from "fs";
352
351
  import path2 from "path";
352
+ async function appendRuntimeIncident(errorsPath, project, event, options = {}) {
353
+ const token = options.token ?? process.env.NEAT_INCIDENT_DURABLE_TOKEN;
354
+ if (token !== void 0) {
355
+ if (Buffer.byteLength(token, "utf8") < 32) throw new Error("durable incident sink unavailable");
356
+ const response = await (options.fetchImpl ?? fetch)("http://127.0.0.1:8081/incident-append", {
357
+ method: "POST",
358
+ headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
359
+ body: JSON.stringify({ project, event }),
360
+ signal: AbortSignal.timeout(3e4)
361
+ });
362
+ if (response.status !== 204) throw new Error("durable incident sink unavailable");
363
+ return;
364
+ }
365
+ await fs2.mkdir(path2.dirname(errorsPath), { recursive: true });
366
+ await fs2.appendFile(errorsPath, JSON.stringify(event) + "\n", "utf8");
367
+ }
368
+
369
+ // src/ingest.ts
370
+ import * as sourceMapJs from "source-map-js";
371
+
372
+ // src/policy.ts
373
+ import { promises as fs3 } from "fs";
374
+ import path3 from "path";
353
375
  import {
354
376
  EdgeType,
355
377
  NodeType,
@@ -871,7 +893,7 @@ function matchPolicyToNode(graph, policy, nodeId, node) {
871
893
  async function loadPolicyFile(policyPath) {
872
894
  let raw;
873
895
  try {
874
- raw = await fs2.readFile(policyPath, "utf8");
896
+ raw = await fs3.readFile(policyPath, "utf8");
875
897
  } catch (err) {
876
898
  if (err.code === "ENOENT") return [];
877
899
  throw err;
@@ -892,8 +914,8 @@ var PolicyViolationsLog = class {
892
914
  if (!this.seen) await this.hydrate();
893
915
  if (this.seen.has(v.id)) return false;
894
916
  this.seen.add(v.id);
895
- await fs2.mkdir(path2.dirname(this.path), { recursive: true });
896
- await fs2.appendFile(this.path, JSON.stringify(v) + "\n", "utf8");
917
+ await fs3.mkdir(path3.dirname(this.path), { recursive: true });
918
+ await fs3.appendFile(this.path, JSON.stringify(v) + "\n", "utf8");
897
919
  emitNeatEvent({
898
920
  type: "policy-violation",
899
921
  project: this.project,
@@ -903,7 +925,7 @@ var PolicyViolationsLog = class {
903
925
  }
904
926
  async readAll() {
905
927
  try {
906
- const raw = await fs2.readFile(this.path, "utf8");
928
+ const raw = await fs3.readFile(this.path, "utf8");
907
929
  return raw.split("\n").filter(Boolean).map((line) => JSON.parse(line));
908
930
  } catch (err) {
909
931
  if (err.code === "ENOENT") return [];
@@ -943,7 +965,7 @@ import {
943
965
  } from "@neat.is/types";
944
966
 
945
967
  // src/extract/routes.ts
946
- import path8 from "path";
968
+ import path9 from "path";
947
969
  import Parser2 from "tree-sitter";
948
970
  import JavaScript2 from "tree-sitter-javascript";
949
971
  import Python2 from "tree-sitter-python";
@@ -961,8 +983,8 @@ import {
961
983
  } from "@neat.is/types";
962
984
 
963
985
  // src/extract/shared.ts
964
- import { promises as fs3 } from "fs";
965
- import path3 from "path";
986
+ import { promises as fs4 } from "fs";
987
+ import path4 from "path";
966
988
  import { parse as parseYaml } from "yaml";
967
989
  import { extractedEdgeId } from "@neat.is/types";
968
990
  var SERVICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".py", ".go", ".rb", ".php", ".cs", ".java", ".kt", ".rs", ".cpp", ".cc", ".cxx", ".c++", ".hpp", ".hh", ".hxx", ".h++"]);
@@ -1006,7 +1028,7 @@ async function isPythonVenvDir(dir) {
1006
1028
  const cached = PYVENV_MARKER_CACHE.get(dir);
1007
1029
  if (cached !== void 0) return cached;
1008
1030
  try {
1009
- const stat = await fs3.stat(path3.join(dir, "pyvenv.cfg"));
1031
+ const stat = await fs4.stat(path4.join(dir, "pyvenv.cfg"));
1010
1032
  const ok = stat.isFile();
1011
1033
  PYVENV_MARKER_CACHE.set(dir, ok);
1012
1034
  return ok;
@@ -1016,7 +1038,7 @@ async function isPythonVenvDir(dir) {
1016
1038
  }
1017
1039
  }
1018
1040
  function isConfigFile(name) {
1019
- const ext = path3.extname(name);
1041
+ const ext = path4.extname(name);
1020
1042
  if (CONFIG_FILE_EXTENSIONS.has(ext)) return { match: true, fileType: ext.slice(1) };
1021
1043
  if (JSON_CONFIG_FILENAMES.has(name)) return { match: true, fileType: "json" };
1022
1044
  if (name === ".env" || name.startsWith(".env.")) {
@@ -1133,16 +1155,16 @@ function cleanVersion(raw) {
1133
1155
  return raw.replace(/^[\^~><=v\s]+/, "").trim() || void 0;
1134
1156
  }
1135
1157
  async function readJson(filePath) {
1136
- const raw = await fs3.readFile(filePath, "utf8");
1158
+ const raw = await fs4.readFile(filePath, "utf8");
1137
1159
  return JSON.parse(raw);
1138
1160
  }
1139
1161
  async function readYaml(filePath) {
1140
- const raw = await fs3.readFile(filePath, "utf8");
1162
+ const raw = await fs4.readFile(filePath, "utf8");
1141
1163
  return parseYaml(raw);
1142
1164
  }
1143
1165
  async function exists(p) {
1144
1166
  try {
1145
- await fs3.access(p);
1167
+ await fs4.access(p);
1146
1168
  return true;
1147
1169
  } catch {
1148
1170
  return false;
@@ -1150,8 +1172,8 @@ async function exists(p) {
1150
1172
  }
1151
1173
 
1152
1174
  // src/extract/errors.ts
1153
- import { promises as fs4 } from "fs";
1154
- import path4 from "path";
1175
+ import { promises as fs5 } from "fs";
1176
+ import path5 from "path";
1155
1177
  var sink = [];
1156
1178
  function recordExtractionError(producer, file, err) {
1157
1179
  const e = err instanceof Error ? err : new Error(String(err));
@@ -1170,14 +1192,14 @@ function drainExtractionErrors() {
1170
1192
  }
1171
1193
  async function writeExtractionErrors(errors, errorsPath) {
1172
1194
  if (errors.length === 0) return;
1173
- await fs4.mkdir(path4.dirname(errorsPath), { recursive: true });
1195
+ await fs5.mkdir(path5.dirname(errorsPath), { recursive: true });
1174
1196
  const lines = errors.map((e) => JSON.stringify(e)).join("\n") + "\n";
1175
- await fs4.appendFile(errorsPath, lines, "utf8");
1197
+ await fs5.appendFile(errorsPath, lines, "utf8");
1176
1198
  }
1177
1199
  function extractionHealthPathFor(errorsPath) {
1178
- const dir = path4.dirname(errorsPath);
1179
- const suffix = path4.basename(errorsPath).replace(/^errors/, "").replace(/\.ndjson$/, "");
1180
- return path4.join(dir, `extraction-health${suffix}.json`);
1200
+ const dir = path5.dirname(errorsPath);
1201
+ const suffix = path5.basename(errorsPath).replace(/^errors/, "").replace(/\.ndjson$/, "");
1202
+ return path5.join(dir, `extraction-health${suffix}.json`);
1181
1203
  }
1182
1204
  var HEALTH_FILE_SAMPLE = 100;
1183
1205
  async function writeExtractionHealth(errors, healthPath, now = (/* @__PURE__ */ new Date()).toISOString()) {
@@ -1189,13 +1211,13 @@ async function writeExtractionHealth(errors, healthPath, now = (/* @__PURE__ */
1189
1211
  files: errors.slice(0, HEALTH_FILE_SAMPLE).map((e) => e.file),
1190
1212
  updatedAt: now
1191
1213
  };
1192
- await fs4.mkdir(path4.dirname(healthPath), { recursive: true });
1193
- await fs4.writeFile(healthPath, JSON.stringify(coverage), "utf8");
1214
+ await fs5.mkdir(path5.dirname(healthPath), { recursive: true });
1215
+ await fs5.writeFile(healthPath, JSON.stringify(coverage), "utf8");
1194
1216
  }
1195
1217
  async function readExtractionHealth(healthPath) {
1196
1218
  let raw;
1197
1219
  try {
1198
- raw = await fs4.readFile(healthPath, "utf8");
1220
+ raw = await fs5.readFile(healthPath, "utf8");
1199
1221
  } catch {
1200
1222
  return void 0;
1201
1223
  }
@@ -1244,9 +1266,9 @@ function isRejectedLogEnabled() {
1244
1266
  }
1245
1267
  async function writeRejectedExtracted(drops, rejectedPath) {
1246
1268
  if (drops.length === 0) return;
1247
- await fs4.mkdir(path4.dirname(rejectedPath), { recursive: true });
1269
+ await fs5.mkdir(path5.dirname(rejectedPath), { recursive: true });
1248
1270
  const lines = drops.map((d) => JSON.stringify({ ...d, ts: (/* @__PURE__ */ new Date()).toISOString() })).join("\n") + "\n";
1249
- await fs4.appendFile(rejectedPath, lines, "utf8");
1271
+ await fs5.appendFile(rejectedPath, lines, "utf8");
1250
1272
  }
1251
1273
  function formatPrecisionFloorBanner(count) {
1252
1274
  if (count === 1) return `[neat] 1 extracted edge dropped below precision floor`;
@@ -1254,8 +1276,8 @@ function formatPrecisionFloorBanner(count) {
1254
1276
  }
1255
1277
 
1256
1278
  // src/extract/calls/shared.ts
1257
- import { promises as fs6 } from "fs";
1258
- import path6 from "path";
1279
+ import { promises as fs7 } from "fs";
1280
+ import path7 from "path";
1259
1281
  import {
1260
1282
  EdgeType as EdgeType2,
1261
1283
  NodeType as NodeType2,
@@ -1266,8 +1288,8 @@ import {
1266
1288
  } from "@neat.is/types";
1267
1289
 
1268
1290
  // src/extract/gitignore.ts
1269
- import { promises as fs5 } from "fs";
1270
- import path5 from "path";
1291
+ import { promises as fs6 } from "fs";
1292
+ import path6 from "path";
1271
1293
  import ignore from "ignore";
1272
1294
  var LAYER_CACHE = /* @__PURE__ */ new Map();
1273
1295
  async function layerFor(dir) {
@@ -1275,7 +1297,7 @@ async function layerFor(dir) {
1275
1297
  if (cached !== void 0) return cached;
1276
1298
  let layer = null;
1277
1299
  try {
1278
- const raw = await fs5.readFile(path5.join(dir, ".gitignore"), "utf8");
1300
+ const raw = await fs6.readFile(path6.join(dir, ".gitignore"), "utf8");
1279
1301
  layer = { dir, ig: ignore().add(raw) };
1280
1302
  } catch {
1281
1303
  layer = null;
@@ -1285,7 +1307,7 @@ async function layerFor(dir) {
1285
1307
  }
1286
1308
  async function isRepoRoot(dir) {
1287
1309
  try {
1288
- await fs5.stat(path5.join(dir, ".git"));
1310
+ await fs6.stat(path6.join(dir, ".git"));
1289
1311
  return true;
1290
1312
  } catch {
1291
1313
  return false;
@@ -1293,11 +1315,11 @@ async function isRepoRoot(dir) {
1293
1315
  }
1294
1316
  async function loadIgnoreChain(startDir) {
1295
1317
  const dirs = [];
1296
- let current = path5.resolve(startDir);
1318
+ let current = path6.resolve(startDir);
1297
1319
  for (; ; ) {
1298
1320
  dirs.push(current);
1299
1321
  if (await isRepoRoot(current)) break;
1300
- const parent = path5.dirname(current);
1322
+ const parent = path6.dirname(current);
1301
1323
  if (parent === current) break;
1302
1324
  current = parent;
1303
1325
  }
@@ -1310,7 +1332,7 @@ async function loadIgnoreChain(startDir) {
1310
1332
  return layers;
1311
1333
  }
1312
1334
  async function extendIgnoreChain(chain, dir) {
1313
- const layer = await layerFor(path5.resolve(dir));
1335
+ const layer = await layerFor(path6.resolve(dir));
1314
1336
  return layer ? [...chain, layer] : chain;
1315
1337
  }
1316
1338
  function isIgnored(chain, fullPath, isDir) {
@@ -1318,14 +1340,14 @@ function isIgnored(chain, fullPath, isDir) {
1318
1340
  }
1319
1341
  function decide(chain, fullPath, isDir) {
1320
1342
  if (chain.length === 0) return { ignored: false, subtree: chain };
1321
- const abs = path5.resolve(fullPath);
1343
+ const abs = path6.resolve(fullPath);
1322
1344
  let ignored = false;
1323
1345
  let unignoredAt = -1;
1324
1346
  for (let i = 0; i < chain.length; i++) {
1325
1347
  const layer = chain[i];
1326
- const rel = path5.relative(layer.dir, abs);
1327
- if (!rel || rel.startsWith("..") || path5.isAbsolute(rel)) continue;
1328
- const candidate = rel.split(path5.sep).join("/") + (isDir ? "/" : "");
1348
+ const rel = path6.relative(layer.dir, abs);
1349
+ if (!rel || rel.startsWith("..") || path6.isAbsolute(rel)) continue;
1350
+ const candidate = rel.split(path6.sep).join("/") + (isDir ? "/" : "");
1329
1351
  const result = layer.ig.test(candidate);
1330
1352
  if (result.ignored) ignored = true;
1331
1353
  else if (result.unignored) {
@@ -1344,7 +1366,7 @@ function buildServiceHostIndex(services) {
1344
1366
  const knownHosts = /* @__PURE__ */ new Set();
1345
1367
  const hostToNodeId = /* @__PURE__ */ new Map();
1346
1368
  for (const service of services) {
1347
- const base = path6.basename(service.dir);
1369
+ const base = path7.basename(service.dir);
1348
1370
  knownHosts.add(base);
1349
1371
  knownHosts.add(service.pkg.name);
1350
1372
  hostToNodeId.set(base, service.node.id);
@@ -1353,29 +1375,29 @@ function buildServiceHostIndex(services) {
1353
1375
  return { knownHosts, hostToNodeId };
1354
1376
  }
1355
1377
  async function walkSourceFiles(dir, excludeDirs = []) {
1356
- const excluded = new Set(excludeDirs.map((d) => path6.resolve(d)));
1378
+ const excluded = new Set(excludeDirs.map((d) => path7.resolve(d)));
1357
1379
  const out = [];
1358
1380
  const rootChain = await loadIgnoreChain(dir);
1359
1381
  async function walk11(current, inherited) {
1360
- const entries = await fs6.readdir(current, { withFileTypes: true }).catch(() => []);
1382
+ const entries = await fs7.readdir(current, { withFileTypes: true }).catch(() => []);
1361
1383
  const chain = entries.some((e) => e.isFile() && e.name === ".gitignore") ? await extendIgnoreChain(inherited, current) : inherited;
1362
1384
  for (const entry of entries) {
1363
- const full = path6.join(current, entry.name);
1385
+ const full = path7.join(current, entry.name);
1364
1386
  if (entry.isDirectory()) {
1365
1387
  if (IGNORED_DIRS.has(entry.name)) continue;
1366
- if (excluded.has(path6.resolve(full))) continue;
1388
+ if (excluded.has(path7.resolve(full))) continue;
1367
1389
  const verdict = decide(chain, full, true);
1368
1390
  if (verdict.ignored) continue;
1369
1391
  if (await isPythonVenvDir(full)) continue;
1370
1392
  await walk11(full, verdict.subtree);
1371
- } else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(path6.extname(entry.name)) && // Skip NEAT's own generated `otel-init.*` bootstrap — extracting it
1393
+ } else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(path7.extname(entry.name)) && // Skip NEAT's own generated `otel-init.*` bootstrap — extracting it
1372
1394
  // would attribute our instrumentation imports to the user's service.
1373
1395
  !isNeatAuthoredSourceFile(entry.name) && !isIgnored(chain, full, false)) {
1374
1396
  out.push(full);
1375
1397
  }
1376
1398
  }
1377
1399
  }
1378
- await walk11(dir, rootChain.filter((l) => path6.resolve(l.dir) !== path6.resolve(dir)));
1400
+ await walk11(dir, rootChain.filter((l) => path7.resolve(l.dir) !== path7.resolve(dir)));
1379
1401
  return out;
1380
1402
  }
1381
1403
  var MINIFIED_LINE_CHARS = 5e3;
@@ -1403,7 +1425,7 @@ async function loadSourceFiles(dir, excludeDirs = []) {
1403
1425
  const out = [];
1404
1426
  for (const p of paths) {
1405
1427
  try {
1406
- const content = await fs6.readFile(p, "utf8");
1428
+ const content = await fs7.readFile(p, "utf8");
1407
1429
  const verdict = isMinifiedSource(p, content);
1408
1430
  if (verdict.minified) {
1409
1431
  noteSkippedFile({ path: p, reason: "minified", detail: verdict.detail });
@@ -1431,7 +1453,7 @@ function toPosix(p) {
1431
1453
  return p.split("\\").join("/");
1432
1454
  }
1433
1455
  function languageForPath(relPath) {
1434
- switch (path6.extname(relPath).toLowerCase()) {
1456
+ switch (path7.extname(relPath).toLowerCase()) {
1435
1457
  case ".py":
1436
1458
  return "python";
1437
1459
  case ".go":
@@ -1514,8 +1536,8 @@ function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
1514
1536
  }
1515
1537
 
1516
1538
  // src/extract/imports.ts
1517
- import path7 from "path";
1518
- import { promises as fs7 } from "fs";
1539
+ import path8 from "path";
1540
+ import { promises as fs8 } from "fs";
1519
1541
  import Parser from "tree-sitter";
1520
1542
  import JavaScript from "tree-sitter-javascript";
1521
1543
  import Python from "tree-sitter-python";
@@ -1666,15 +1688,15 @@ function collectPyImports(node, out) {
1666
1688
  }
1667
1689
  async function fileExists(p) {
1668
1690
  try {
1669
- await fs7.access(p);
1691
+ await fs8.access(p);
1670
1692
  return true;
1671
1693
  } catch {
1672
1694
  return false;
1673
1695
  }
1674
1696
  }
1675
1697
  function isWithinServiceDir(candidate, serviceDir) {
1676
- const rel = path7.relative(serviceDir, candidate);
1677
- return rel !== "" && !rel.startsWith("..") && !path7.isAbsolute(rel);
1698
+ const rel = path8.relative(serviceDir, candidate);
1699
+ return rel !== "" && !rel.startsWith("..") && !path8.isAbsolute(rel);
1678
1700
  }
1679
1701
  var JS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
1680
1702
  var JS_INDEX_FILES = JS_EXTENSIONS.map((ext) => `index${ext}`);
@@ -1682,22 +1704,22 @@ async function firstExistingCandidate(base, serviceDir) {
1682
1704
  for (const ext of JS_EXTENSIONS) {
1683
1705
  const candidate = base + ext;
1684
1706
  if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
1685
- return toPosix(path7.relative(serviceDir, candidate));
1707
+ return toPosix(path8.relative(serviceDir, candidate));
1686
1708
  }
1687
1709
  }
1688
1710
  for (const indexFile of JS_INDEX_FILES) {
1689
- const candidate = path7.join(base, indexFile);
1711
+ const candidate = path8.join(base, indexFile);
1690
1712
  if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
1691
- return toPosix(path7.relative(serviceDir, candidate));
1713
+ return toPosix(path8.relative(serviceDir, candidate));
1692
1714
  }
1693
1715
  }
1694
1716
  return null;
1695
1717
  }
1696
1718
  async function loadTsPathConfig(serviceDir) {
1697
- const tsconfigPath = path7.join(serviceDir, "tsconfig.json");
1719
+ const tsconfigPath = path8.join(serviceDir, "tsconfig.json");
1698
1720
  let raw;
1699
1721
  try {
1700
- raw = await fs7.readFile(tsconfigPath, "utf8");
1722
+ raw = await fs8.readFile(tsconfigPath, "utf8");
1701
1723
  } catch {
1702
1724
  return null;
1703
1725
  }
@@ -1706,7 +1728,7 @@ async function loadTsPathConfig(serviceDir) {
1706
1728
  const paths = parsed.compilerOptions?.paths;
1707
1729
  if (!paths || Object.keys(paths).length === 0) return null;
1708
1730
  const baseUrl = parsed.compilerOptions?.baseUrl;
1709
- return { paths, baseDir: baseUrl ? path7.resolve(serviceDir, baseUrl) : serviceDir };
1731
+ return { paths, baseDir: baseUrl ? path8.resolve(serviceDir, baseUrl) : serviceDir };
1710
1732
  } catch (err) {
1711
1733
  recordExtractionError("import alias resolution", tsconfigPath, err);
1712
1734
  return null;
@@ -1724,11 +1746,11 @@ async function resolveTsAlias(specifier, config, serviceDir) {
1724
1746
  if (suffix === null) continue;
1725
1747
  for (const target of targets) {
1726
1748
  const targetBase = target.endsWith("/*") ? target.slice(0, -2) : target.replace(/\*$/, "");
1727
- const resolvedBase = path7.resolve(config.baseDir, targetBase, suffix);
1749
+ const resolvedBase = path8.resolve(config.baseDir, targetBase, suffix);
1728
1750
  const hit = await firstExistingCandidate(resolvedBase, serviceDir);
1729
1751
  if (hit) return hit;
1730
1752
  if (isWithinServiceDir(resolvedBase, serviceDir) && await fileExists(resolvedBase)) {
1731
- return toPosix(path7.relative(serviceDir, resolvedBase));
1753
+ return toPosix(path8.relative(serviceDir, resolvedBase));
1732
1754
  }
1733
1755
  }
1734
1756
  }
@@ -1737,18 +1759,18 @@ async function resolveTsAlias(specifier, config, serviceDir) {
1737
1759
  async function resolveJsImport(specifier, importerDir, serviceDir, tsPaths) {
1738
1760
  if (!specifier) return null;
1739
1761
  if (specifier.startsWith("./") || specifier.startsWith("../")) {
1740
- const base = path7.resolve(importerDir, specifier);
1741
- const ext = path7.extname(specifier);
1762
+ const base = path8.resolve(importerDir, specifier);
1763
+ const ext = path8.extname(specifier);
1742
1764
  if (ext) {
1743
1765
  if (ext === ".js" || ext === ".jsx") {
1744
1766
  const tsExt = ext === ".jsx" ? ".tsx" : ".ts";
1745
1767
  const tsSibling = base.slice(0, -ext.length) + tsExt;
1746
1768
  if (isWithinServiceDir(tsSibling, serviceDir) && await fileExists(tsSibling)) {
1747
- return toPosix(path7.relative(serviceDir, tsSibling));
1769
+ return toPosix(path8.relative(serviceDir, tsSibling));
1748
1770
  }
1749
1771
  }
1750
1772
  if (isWithinServiceDir(base, serviceDir) && await fileExists(base)) {
1751
- return toPosix(path7.relative(serviceDir, base));
1773
+ return toPosix(path8.relative(serviceDir, base));
1752
1774
  }
1753
1775
  if (!JS_EXTENSIONS.includes(ext)) {
1754
1776
  return firstExistingCandidate(base, serviceDir);
@@ -1763,30 +1785,30 @@ async function resolveJsImport(specifier, importerDir, serviceDir, tsPaths) {
1763
1785
  async function resolvePyImport(imp, importerPath, serviceDir) {
1764
1786
  let baseDir;
1765
1787
  if (imp.level > 0) {
1766
- baseDir = path7.dirname(importerPath);
1767
- for (let i = 1; i < imp.level; i++) baseDir = path7.dirname(baseDir);
1788
+ baseDir = path8.dirname(importerPath);
1789
+ for (let i = 1; i < imp.level; i++) baseDir = path8.dirname(baseDir);
1768
1790
  } else {
1769
1791
  baseDir = serviceDir;
1770
1792
  }
1771
- const moduleBase = imp.modulePath ? path7.join(baseDir, imp.modulePath.split(".").join("/")) : baseDir;
1793
+ const moduleBase = imp.modulePath ? path8.join(baseDir, imp.modulePath.split(".").join("/")) : baseDir;
1772
1794
  const resolved = /* @__PURE__ */ new Set();
1773
1795
  let needModuleFile = imp.names.length === 0;
1774
1796
  for (const name of imp.names) {
1775
- const submoduleFile = path7.join(moduleBase, `${name}.py`);
1776
- const subpackageInit = path7.join(moduleBase, name, "__init__.py");
1797
+ const submoduleFile = path8.join(moduleBase, `${name}.py`);
1798
+ const subpackageInit = path8.join(moduleBase, name, "__init__.py");
1777
1799
  if (isWithinServiceDir(submoduleFile, serviceDir) && await fileExists(submoduleFile)) {
1778
- resolved.add(toPosix(path7.relative(serviceDir, submoduleFile)));
1800
+ resolved.add(toPosix(path8.relative(serviceDir, submoduleFile)));
1779
1801
  } else if (isWithinServiceDir(subpackageInit, serviceDir) && await fileExists(subpackageInit)) {
1780
- resolved.add(toPosix(path7.relative(serviceDir, subpackageInit)));
1802
+ resolved.add(toPosix(path8.relative(serviceDir, subpackageInit)));
1781
1803
  } else {
1782
1804
  needModuleFile = true;
1783
1805
  }
1784
1806
  }
1785
1807
  if (needModuleFile) {
1786
- const moduleFileCandidates = imp.modulePath ? [`${moduleBase}.py`, path7.join(moduleBase, "__init__.py")] : [path7.join(moduleBase, "__init__.py")];
1808
+ const moduleFileCandidates = imp.modulePath ? [`${moduleBase}.py`, path8.join(moduleBase, "__init__.py")] : [path8.join(moduleBase, "__init__.py")];
1787
1809
  for (const candidate of moduleFileCandidates) {
1788
1810
  if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
1789
- resolved.add(toPosix(path7.relative(serviceDir, candidate)));
1811
+ resolved.add(toPosix(path8.relative(serviceDir, candidate)));
1790
1812
  break;
1791
1813
  }
1792
1814
  }
@@ -1796,11 +1818,11 @@ async function resolvePyImport(imp, importerPath, serviceDir) {
1796
1818
  async function resolveGoImport(specifier, modulePath, serviceDir) {
1797
1819
  if (specifier !== modulePath && !specifier.startsWith(`${modulePath}/`)) return null;
1798
1820
  const suffix = specifier === modulePath ? "" : specifier.slice(modulePath.length + 1);
1799
- const dir = path7.join(serviceDir, suffix);
1800
- const entries = await fs7.readdir(dir, { withFileTypes: true }).catch(() => []);
1801
- const candidates = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".go") && !entry.name.endsWith("_test.go")).map((entry) => path7.join(dir, entry.name));
1821
+ const dir = path8.join(serviceDir, suffix);
1822
+ const entries = await fs8.readdir(dir, { withFileTypes: true }).catch(() => []);
1823
+ const candidates = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".go") && !entry.name.endsWith("_test.go")).map((entry) => path8.join(dir, entry.name));
1802
1824
  if (candidates.length !== 1) return null;
1803
- return toPosix(path7.relative(serviceDir, candidates[0]));
1825
+ return toPosix(path8.relative(serviceDir, candidates[0]));
1804
1826
  }
1805
1827
  function emitImportEdge(graph, serviceName, importerFileId, importerRelPath, importeeRelPath, line, snippet2) {
1806
1828
  const importeeFileId = fileId2(serviceName, importeeRelPath);
@@ -1830,12 +1852,12 @@ async function addImports(graph, services) {
1830
1852
  const files = await loadSourceFiles(service.dir, service.excludeDirs);
1831
1853
  for (const file of files) {
1832
1854
  if (isTestPath(file.path)) continue;
1833
- const relFile = toPosix(path7.relative(service.dir, file.path));
1855
+ const relFile = toPosix(path8.relative(service.dir, file.path));
1834
1856
  const importerFileId = fileId2(service.pkg.name, relFile);
1835
- const isPython = path7.extname(file.path) === ".py";
1836
- const isGo = path7.extname(file.path) === ".go";
1837
- if (path7.extname(file.path) === ".rb") continue;
1838
- if (path7.extname(file.path) === ".php") continue;
1857
+ const isPython = path8.extname(file.path) === ".py";
1858
+ const isGo = path8.extname(file.path) === ".go";
1859
+ if (path8.extname(file.path) === ".rb") continue;
1860
+ if (path8.extname(file.path) === ".php") continue;
1839
1861
  if (isGo) {
1840
1862
  let goImports = [];
1841
1863
  try {
@@ -1845,7 +1867,7 @@ async function addImports(graph, services) {
1845
1867
  recordExtractionError("import extraction", file.path, err);
1846
1868
  continue;
1847
1869
  }
1848
- const goMod = await fs7.readFile(path7.join(service.dir, "go.mod"), "utf8").catch(() => "");
1870
+ const goMod = await fs8.readFile(path8.join(service.dir, "go.mod"), "utf8").catch(() => "");
1849
1871
  const modulePath = goMod.match(/^\s*module\s+(\S+)\s*$/m)?.[1];
1850
1872
  if (!modulePath) continue;
1851
1873
  for (const imp of goImports) {
@@ -1889,7 +1911,7 @@ async function addImports(graph, services) {
1889
1911
  continue;
1890
1912
  }
1891
1913
  for (const imp of jsImports) {
1892
- const resolved = await resolveJsImport(imp.specifier, path7.dirname(file.path), service.dir, tsPaths);
1914
+ const resolved = await resolveJsImport(imp.specifier, path8.dirname(file.path), service.dir, tsPaths);
1893
1915
  if (!resolved) continue;
1894
1916
  edgesAdded += emitImportEdge(
1895
1917
  graph,
@@ -2014,8 +2036,8 @@ function chiRoutesFromSource(source, parser) {
2014
2036
  chiWalk(tree.rootNode, "", out);
2015
2037
  return out;
2016
2038
  }
2017
- function stripChiRegex(path74) {
2018
- return path74.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
2039
+ function stripChiRegex(path77) {
2040
+ return path77.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
2019
2041
  }
2020
2042
  function chiWalk(node, prefix, out) {
2021
2043
  for (let i = 0; i < node.namedChildCount; i++) {
@@ -2355,7 +2377,7 @@ function isNextPagesApiFile(relFile) {
2355
2377
  if (pagesIdx === -1 || segs[pagesIdx + 1] !== "api") return false;
2356
2378
  const base = segs[segs.length - 1] ?? "";
2357
2379
  if (/^_(app|document|middleware)\./.test(base)) return false;
2358
- return JS_ROUTE_EXTENSIONS.has(path8.extname(base));
2380
+ return JS_ROUTE_EXTENSIONS.has(path9.extname(base));
2359
2381
  }
2360
2382
  function nextSegment(seg) {
2361
2383
  if (seg.startsWith("(") && seg.endsWith(")")) return null;
@@ -2687,9 +2709,9 @@ function rubyRocketRoute(args) {
2687
2709
  if (!pair || pair.type !== "pair") continue;
2688
2710
  const k = pair.childForFieldName("key");
2689
2711
  if (k?.type !== "string") continue;
2690
- const path74 = rubyLiteral(k);
2691
- if (path74 === null) continue;
2692
- return { path: path74, target: rubyLiteral(pair.childForFieldName("value")) };
2712
+ const path77 = rubyLiteral(k);
2713
+ if (path77 === null) continue;
2714
+ return { path: path77, target: rubyLiteral(pair.childForFieldName("value")) };
2693
2715
  }
2694
2716
  return null;
2695
2717
  }
@@ -2779,23 +2801,23 @@ function railsInferHandler(rawPath) {
2779
2801
  if (segs.length === 1) return `${segs[0]}#index`;
2780
2802
  return `${segs.slice(0, -1).join("/")}#${segs[segs.length - 1]}`;
2781
2803
  }
2782
- function railsResource(call, ctx, out, plural) {
2804
+ function railsResource(call, ctx, out, plural2) {
2783
2805
  if (ctx.depth >= 2) return;
2784
2806
  const args = rubyArgs(call);
2785
2807
  const name = rubyLiteral(rubyPositional(args)[0]);
2786
2808
  if (!name) return;
2787
2809
  const base = `${ctx.path}/${name}`;
2788
- const controller = ctx.module + (plural ? name : railsPluralize(name));
2810
+ const controller = ctx.module + (plural2 ? name : railsPluralize(name));
2789
2811
  const allow = railsActionFilter(args);
2790
2812
  const line = call.startPosition.row + 1;
2791
- for (const row of plural ? RAILS_PLURAL_ROWS : RAILS_SINGULAR_ROWS) {
2813
+ for (const row of plural2 ? RAILS_PLURAL_ROWS : RAILS_SINGULAR_ROWS) {
2792
2814
  if (!allow(row.action)) continue;
2793
2815
  for (const m of row.methods) railsEmit(out, m, base + row.suffix, `${controller}#${row.action}`, line);
2794
2816
  }
2795
2817
  const block = call.childForFieldName("block");
2796
2818
  if (!block) return;
2797
- const memberBase = plural ? `${base}/:id` : base;
2798
- const nestedPath = plural ? `${base}/:${railsSingularize(name)}_id` : base;
2819
+ const memberBase = plural2 ? `${base}/:id` : base;
2820
+ const nestedPath = plural2 ? `${base}/:${railsSingularize(name)}_id` : base;
2799
2821
  for (const child of rubyBlockCalls(block)) {
2800
2822
  const cm = child.childForFieldName("method")?.text;
2801
2823
  if (cm === "member") railsBlockVerbs(child, memberBase, controller, out);
@@ -3537,12 +3559,12 @@ async function expressMountPrefixes(files, serviceDir, tsPaths) {
3537
3559
  const jsParser = makeJsParser2();
3538
3560
  const fileInfo = /* @__PURE__ */ new Map();
3539
3561
  for (const f of files) {
3540
- if (!JS_ROUTE_EXTENSIONS.has(path8.extname(f.path))) continue;
3562
+ if (!JS_ROUTE_EXTENSIONS.has(path9.extname(f.path))) continue;
3541
3563
  if (isTestPath(f.path)) continue;
3542
- const rel = toPosix(path8.relative(serviceDir, f.path));
3564
+ const rel = toPosix(path9.relative(serviceDir, f.path));
3543
3565
  try {
3544
3566
  const tree = parseSource2(jsParser, f.content);
3545
- fileInfo.set(rel, analyzeExpressFile(tree.rootNode, path8.dirname(f.path)));
3567
+ fileInfo.set(rel, analyzeExpressFile(tree.rootNode, path9.dirname(f.path)));
3546
3568
  } catch {
3547
3569
  }
3548
3570
  }
@@ -3641,14 +3663,14 @@ async function addRoutes(graph, services) {
3641
3663
  const mountPrefixes = hasExpress ? await expressMountPrefixes(files, service.dir, await loadTsPathConfig(service.dir)) : /* @__PURE__ */ new Map();
3642
3664
  for (const file of files) {
3643
3665
  if (isTestPath(file.path)) continue;
3644
- const ext = path8.extname(file.path);
3666
+ const ext = path9.extname(file.path);
3645
3667
  const isPy = ext === ".py";
3646
3668
  const isGo = ext === ".go";
3647
3669
  const isRb = ext === ".rb";
3648
3670
  const isPhp = ext === ".php";
3649
3671
  const isRs = ext === ".rs";
3650
3672
  if (!JS_ROUTE_EXTENSIONS.has(ext) && !isPy && !isGo && !isRb && !isPhp && !isRs) continue;
3651
- const relFile = toPosix(path8.relative(service.dir, file.path));
3673
+ const relFile = toPosix(path9.relative(service.dir, file.path));
3652
3674
  let routes;
3653
3675
  try {
3654
3676
  if (isPhp) {
@@ -4110,20 +4132,20 @@ var CODE_LINENO_ATTR = "code.lineno";
4110
4132
  var CODE_FUNCTION_NAME_ATTR = "code.function.name";
4111
4133
  var CODE_FUNCTION_ATTR = "code.function";
4112
4134
  function codeFilepathOf(attrs) {
4113
- const stable = attrs[CODE_FILE_PATH_ATTR];
4114
- if (typeof stable === "string" && stable.length > 0) return stable;
4135
+ const stable2 = attrs[CODE_FILE_PATH_ATTR];
4136
+ if (typeof stable2 === "string" && stable2.length > 0) return stable2;
4115
4137
  const prior = attrs[CODE_FILEPATH_ATTR];
4116
4138
  return typeof prior === "string" && prior.length > 0 ? prior : void 0;
4117
4139
  }
4118
4140
  function codeLinenoOf(attrs) {
4119
- const stable = attrs[CODE_LINE_NUMBER_ATTR];
4120
- if (typeof stable === "number" && Number.isFinite(stable)) return stable;
4141
+ const stable2 = attrs[CODE_LINE_NUMBER_ATTR];
4142
+ if (typeof stable2 === "number" && Number.isFinite(stable2)) return stable2;
4121
4143
  const prior = attrs[CODE_LINENO_ATTR];
4122
4144
  return typeof prior === "number" && Number.isFinite(prior) ? prior : void 0;
4123
4145
  }
4124
4146
  function codeFunctionOf(attrs) {
4125
- const stable = attrs[CODE_FUNCTION_NAME_ATTR];
4126
- if (typeof stable === "string" && stable.length > 0) return stable;
4147
+ const stable2 = attrs[CODE_FUNCTION_NAME_ATTR];
4148
+ if (typeof stable2 === "string" && stable2.length > 0) return stable2;
4127
4149
  const prior = attrs[CODE_FUNCTION_ATTR];
4128
4150
  return typeof prior === "string" && prior.length > 0 ? prior : void 0;
4129
4151
  }
@@ -4153,7 +4175,7 @@ function languageForExt(relPath) {
4153
4175
  function relPathForRuntimeFile(filepath, serviceNode, scanPath) {
4154
4176
  let p = toPosix2(filepath).replace(/^file:\/\//, "");
4155
4177
  if (scanPath && scanPath.length > 0) {
4156
- const absRoot = toPosix2(path9.resolve(scanPath, serviceNode?.repoPath ?? ""));
4178
+ const absRoot = toPosix2(path10.resolve(scanPath, serviceNode?.repoPath ?? ""));
4157
4179
  const anchor = absRoot.endsWith("/") ? absRoot : `${absRoot}/`;
4158
4180
  if (p.startsWith(anchor)) return p.slice(anchor.length);
4159
4181
  }
@@ -4184,7 +4206,7 @@ function resolveDistToSrc(absFilepath, line) {
4184
4206
  if (existsSync(mapPath)) {
4185
4207
  const raw = JSON.parse(readFileSync(mapPath, "utf8"));
4186
4208
  const consumer = new sourceMapJs.SourceMapConsumer(raw);
4187
- entry = { consumer, dir: path9.dirname(mapPath) };
4209
+ entry = { consumer, dir: path10.dirname(mapPath) };
4188
4210
  }
4189
4211
  } catch {
4190
4212
  entry = null;
@@ -4204,7 +4226,7 @@ function resolveDistToSrc(absFilepath, line) {
4204
4226
  }
4205
4227
  if (!pos || !pos.source) return null;
4206
4228
  const root = entry.consumer.sourceRoot ?? "";
4207
- const resolved = path9.resolve(entry.dir, root, pos.source);
4229
+ const resolved = path10.resolve(entry.dir, root, pos.source);
4208
4230
  return { filepath: resolved, ...pos.line ? { line: pos.line } : {} };
4209
4231
  } catch {
4210
4232
  return null;
@@ -4748,8 +4770,7 @@ function emitIncidentEvent(project, ev) {
4748
4770
  });
4749
4771
  }
4750
4772
  async function appendErrorEvent(ctx, ev) {
4751
- await fs8.mkdir(path9.dirname(ctx.errorsPath), { recursive: true });
4752
- await fs8.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
4773
+ await appendRuntimeIncident(ctx.errorsPath, ctx.project ?? DEFAULT_PROJECT, ev);
4753
4774
  emitIncidentEvent(ctx.project ?? DEFAULT_PROJECT, ev);
4754
4775
  }
4755
4776
  async function appendConnectorIncident(errorsPath, input, project) {
@@ -4764,8 +4785,7 @@ async function appendConnectorIncident(errorsPath, input, project) {
4764
4785
  ...input.attributes && Object.keys(input.attributes).length > 0 ? { attributes: input.attributes } : {},
4765
4786
  affectedNode: input.affectedNode
4766
4787
  };
4767
- await fs8.mkdir(path9.dirname(errorsPath), { recursive: true });
4768
- await fs8.appendFile(errorsPath, JSON.stringify(ev) + "\n", "utf8");
4788
+ await appendRuntimeIncident(errorsPath, project ?? DEFAULT_PROJECT, ev);
4769
4789
  if (project) emitIncidentEvent(project, ev);
4770
4790
  }
4771
4791
  function landIncidentCallSite(span, callSite, trusted, graph) {
@@ -4887,8 +4907,7 @@ function makeErrorSpanWriter(errorsPath, graph, scanPath, project = DEFAULT_PROJ
4887
4907
  return async (span) => {
4888
4908
  const ev = buildErrorEventForReceiver(span, graph, scanPath);
4889
4909
  if (!ev) return;
4890
- await fs8.mkdir(path9.dirname(errorsPath), { recursive: true });
4891
- await fs8.appendFile(errorsPath, JSON.stringify(ev) + "\n", "utf8");
4910
+ await appendRuntimeIncident(errorsPath, project, ev);
4892
4911
  emitIncidentEvent(project, ev);
4893
4912
  };
4894
4913
  }
@@ -5391,13 +5410,13 @@ async function markStaleEdges(graph, options = {}) {
5391
5410
  return { count: events.length, events };
5392
5411
  }
5393
5412
  async function appendStaleEvents(staleEventsPath, events) {
5394
- await fs8.mkdir(path9.dirname(staleEventsPath), { recursive: true });
5413
+ await fs9.mkdir(path10.dirname(staleEventsPath), { recursive: true });
5395
5414
  const lines = events.map((e) => JSON.stringify(e)).join("\n") + "\n";
5396
- await fs8.appendFile(staleEventsPath, lines, "utf8");
5415
+ await fs9.appendFile(staleEventsPath, lines, "utf8");
5397
5416
  }
5398
5417
  async function readStaleEvents(staleEventsPath) {
5399
5418
  try {
5400
- const raw = await fs8.readFile(staleEventsPath, "utf8");
5419
+ const raw = await fs9.readFile(staleEventsPath, "utf8");
5401
5420
  return raw.split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
5402
5421
  } catch (err) {
5403
5422
  if (err.code === "ENOENT") return [];
@@ -5433,7 +5452,7 @@ function startStalenessLoop(graph, options = {}) {
5433
5452
  var INCIDENT_READ_MAX_BYTES = 32 * 1024 * 1024;
5434
5453
  var INCIDENT_READ_MAX_EVENTS = 5e3;
5435
5454
  async function readErrorFileTail(errorsPath, maxBytes) {
5436
- const handle = await fs8.open(errorsPath, "r");
5455
+ const handle = await fs9.open(errorsPath, "r");
5437
5456
  try {
5438
5457
  const { size } = await handle.stat();
5439
5458
  if (size <= maxBytes) {
@@ -5469,6 +5488,23 @@ async function readErrorEvents(errorsPath, opts) {
5469
5488
  const cap = opts?.limit !== void 0 && opts.limit > 0 ? Math.min(opts.limit, INCIDENT_READ_MAX_EVENTS) : INCIDENT_READ_MAX_EVENTS;
5470
5489
  return deduped.length > cap ? deduped.slice(deduped.length - cap) : deduped;
5471
5490
  }
5491
+ async function readErrorEventById(errorsPath, id) {
5492
+ let raw;
5493
+ try {
5494
+ raw = await readErrorFileTail(errorsPath, INCIDENT_READ_MAX_BYTES);
5495
+ } catch (err) {
5496
+ if (err.code === "ENOENT") return void 0;
5497
+ throw err;
5498
+ }
5499
+ const lines = raw.split("\n");
5500
+ for (let i = lines.length - 1; i >= 0; i--) {
5501
+ const line = lines[i];
5502
+ if (!line) continue;
5503
+ const event = JSON.parse(line);
5504
+ if (event.id === id) return redactPersistedAttributes(event);
5505
+ }
5506
+ return void 0;
5507
+ }
5472
5508
  function isSynthesizedHttpIncident(ev) {
5473
5509
  if (ev.exceptionType || ev.exceptionStacktrace) return false;
5474
5510
  if (ev.errorType) return false;
@@ -5690,19 +5726,19 @@ function confidenceFromMix(edges, now = Date.now()) {
5690
5726
  function longestIncomingWalk(graph, start, maxDepth) {
5691
5727
  let best = { path: [start], edges: [] };
5692
5728
  const visited = /* @__PURE__ */ new Set([start]);
5693
- function step(node, path74, edges) {
5694
- if (path74.length > best.path.length) {
5695
- best = { path: [...path74], edges: [...edges] };
5729
+ function step(node, path77, edges) {
5730
+ if (path77.length > best.path.length) {
5731
+ best = { path: [...path77], edges: [...edges] };
5696
5732
  }
5697
- if (path74.length - 1 >= maxDepth) return;
5733
+ if (path77.length - 1 >= maxDepth) return;
5698
5734
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
5699
5735
  for (const [srcId, edge] of incoming) {
5700
5736
  if (visited.has(srcId)) continue;
5701
5737
  visited.add(srcId);
5702
- path74.push(srcId);
5738
+ path77.push(srcId);
5703
5739
  edges.push(edge);
5704
- step(srcId, path74, edges);
5705
- path74.pop();
5740
+ step(srcId, path77, edges);
5741
+ path77.pop();
5706
5742
  edges.pop();
5707
5743
  visited.delete(srcId);
5708
5744
  }
@@ -5917,20 +5953,20 @@ function dominantFailingCall(graph, serviceId17, visited) {
5917
5953
  return best;
5918
5954
  }
5919
5955
  function followFailingCallChain(graph, originServiceId, maxDepth) {
5920
- const path74 = [originServiceId];
5956
+ const path77 = [originServiceId];
5921
5957
  const edges = [];
5922
5958
  const visited = /* @__PURE__ */ new Set([originServiceId]);
5923
5959
  let current = originServiceId;
5924
5960
  for (let depth = 0; depth < maxDepth; depth++) {
5925
5961
  const hop = dominantFailingCall(graph, current, visited);
5926
5962
  if (!hop) break;
5927
- path74.push(hop.nextService);
5963
+ path77.push(hop.nextService);
5928
5964
  edges.push(hop.edge);
5929
5965
  visited.add(hop.nextService);
5930
5966
  current = hop.nextService;
5931
5967
  }
5932
5968
  if (edges.length === 0) return null;
5933
- return { path: path74, edges, culprit: current };
5969
+ return { path: path77, edges, culprit: current };
5934
5970
  }
5935
5971
  function isStaleCallEdge(e) {
5936
5972
  return e.type === EdgeType6.CALLS && e.provenance === Provenance6.STALE;
@@ -5966,26 +6002,26 @@ function dominantStaleCall(graph, serviceId17, visited) {
5966
6002
  return best;
5967
6003
  }
5968
6004
  function followStaleCallChain(graph, originServiceId, maxDepth) {
5969
- const path74 = [originServiceId];
6005
+ const path77 = [originServiceId];
5970
6006
  const edges = [];
5971
6007
  const visited = /* @__PURE__ */ new Set([originServiceId]);
5972
6008
  let current = originServiceId;
5973
6009
  for (let depth = 0; depth < maxDepth; depth++) {
5974
6010
  const hop = dominantStaleCall(graph, current, visited);
5975
6011
  if (!hop) break;
5976
- path74.push(hop.nextService);
6012
+ path77.push(hop.nextService);
5977
6013
  edges.push(hop.edge);
5978
6014
  visited.add(hop.nextService);
5979
6015
  current = hop.nextService;
5980
6016
  }
5981
6017
  if (edges.length === 0) return null;
5982
- return { path: path74, edges, culprit: current };
6018
+ return { path: path77, edges, culprit: current };
5983
6019
  }
5984
6020
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
5985
6021
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
5986
6022
  if (!chain) return null;
5987
6023
  const culprit = chain.culprit;
5988
- const path74 = [...chain.path];
6024
+ const path77 = [...chain.path];
5989
6025
  const edgeProvenances = chain.edges.map((e) => e.provenance);
5990
6026
  const baseConfidence = confidenceFromMix(chain.edges);
5991
6027
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -5993,14 +6029,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
5993
6029
  if (loc) {
5994
6030
  let rootCauseNode = culprit;
5995
6031
  if (loc.fileNode) {
5996
- path74.push(loc.fileNode);
6032
+ path77.push(loc.fileNode);
5997
6033
  edgeProvenances.push(Provenance6.OBSERVED);
5998
6034
  rootCauseNode = loc.fileNode;
5999
6035
  }
6000
6036
  return RootCauseResultSchema.parse({
6001
6037
  rootCauseNode,
6002
6038
  rootCauseReason: loc.rootCauseReason,
6003
- traversalPath: path74,
6039
+ traversalPath: path77,
6004
6040
  edgeProvenances,
6005
6041
  confidence,
6006
6042
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -6012,7 +6048,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
6012
6048
  return RootCauseResultSchema.parse({
6013
6049
  rootCauseNode: culprit,
6014
6050
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
6015
- traversalPath: path74,
6051
+ traversalPath: path77,
6016
6052
  edgeProvenances,
6017
6053
  confidence,
6018
6054
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -6665,10 +6701,10 @@ function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
6665
6701
  traversalPath = staleChain.path;
6666
6702
  edgeProvenances = staleChain.edges.map((e) => e.provenance);
6667
6703
  } else if (top.node !== seedNode) {
6668
- const path74 = findPath(graph, errorNodeId, top.node, "up", ROOT_CAUSE_MAX_DEPTH);
6669
- if (path74) {
6670
- traversalPath = path74.nodes;
6671
- edgeProvenances = path74.edges.map((e) => e.provenance);
6704
+ const path77 = findPath(graph, errorNodeId, top.node, "up", ROOT_CAUSE_MAX_DEPTH);
6705
+ if (path77) {
6706
+ traversalPath = path77.nodes;
6707
+ edgeProvenances = path77.edges.map((e) => e.provenance);
6672
6708
  } else {
6673
6709
  traversalPath = [errorNodeId, top.node];
6674
6710
  edgeProvenances = [top.provenance ?? Provenance6.OBSERVED];
@@ -6703,15 +6739,15 @@ function fixRecommendationForTop(top, seedNode, legacy) {
6703
6739
  }
6704
6740
 
6705
6741
  // src/extract/services.ts
6706
- import { promises as fs19 } from "fs";
6707
- import path21 from "path";
6742
+ import { promises as fs20 } from "fs";
6743
+ import path22 from "path";
6708
6744
  import ignore2 from "ignore";
6709
6745
  import { minimatch as minimatch2 } from "minimatch";
6710
6746
  import { NodeType as NodeType15, serviceId as serviceId11 } from "@neat.is/types";
6711
6747
 
6712
6748
  // src/extract/python.ts
6713
- import { promises as fs9 } from "fs";
6714
- import path10 from "path";
6749
+ import { promises as fs10 } from "fs";
6750
+ import path11 from "path";
6715
6751
  import { parse as parseToml } from "smol-toml";
6716
6752
  var REQUIREMENT_LINE = /^\s*([A-Za-z0-9_.-]+)(?:\[[^\]]*\])?\s*(?:(==)\s*([A-Za-z0-9_.+-]+))?/;
6717
6753
  function parseRequirementsTxt(content) {
@@ -6744,25 +6780,25 @@ function depsFromPyProject(pyproject) {
6744
6780
  return out;
6745
6781
  }
6746
6782
  async function discoverPythonService(serviceDir) {
6747
- const pyprojectPath = path10.join(serviceDir, "pyproject.toml");
6748
- const requirementsPath = path10.join(serviceDir, "requirements.txt");
6749
- const setupPath = path10.join(serviceDir, "setup.py");
6783
+ const pyprojectPath = path11.join(serviceDir, "pyproject.toml");
6784
+ const requirementsPath = path11.join(serviceDir, "requirements.txt");
6785
+ const setupPath = path11.join(serviceDir, "setup.py");
6750
6786
  const hasPyproject = await exists(pyprojectPath);
6751
6787
  const hasRequirements = await exists(requirementsPath);
6752
6788
  const hasSetup = await exists(setupPath);
6753
6789
  if (!hasPyproject && !hasRequirements && !hasSetup) return null;
6754
- let name = path10.basename(serviceDir);
6790
+ let name = path11.basename(serviceDir);
6755
6791
  let version;
6756
6792
  const dependencies = {};
6757
6793
  if (hasPyproject) {
6758
- const raw = await fs9.readFile(pyprojectPath, "utf8");
6794
+ const raw = await fs10.readFile(pyprojectPath, "utf8");
6759
6795
  const pyproject = parseToml(raw);
6760
6796
  name = pyproject.project?.name ?? pyproject.tool?.poetry?.name ?? name;
6761
6797
  version = pyproject.project?.version ?? pyproject.tool?.poetry?.version ?? void 0;
6762
6798
  Object.assign(dependencies, depsFromPyProject(pyproject));
6763
6799
  }
6764
6800
  if (hasRequirements) {
6765
- const raw = await fs9.readFile(requirementsPath, "utf8");
6801
+ const raw = await fs10.readFile(requirementsPath, "utf8");
6766
6802
  Object.assign(dependencies, parseRequirementsTxt(raw));
6767
6803
  }
6768
6804
  return { name, version, dependencies };
@@ -6776,8 +6812,8 @@ function pythonToPackage(service) {
6776
6812
  }
6777
6813
 
6778
6814
  // src/extract/go.ts
6779
- import { promises as fs10 } from "fs";
6780
- import path11 from "path";
6815
+ import { promises as fs11 } from "fs";
6816
+ import path12 from "path";
6781
6817
  import { NodeType as NodeType6, serviceId as serviceId2 } from "@neat.is/types";
6782
6818
  function parseGoMod(source) {
6783
6819
  const module = source.match(/^\s*module\s+(\S+)\s*$/m)?.[1];
@@ -6804,7 +6840,7 @@ function goFramework(deps) {
6804
6840
  async function discoverGoService(scanPath, dir) {
6805
6841
  let raw;
6806
6842
  try {
6807
- raw = await fs10.readFile(path11.join(dir, "go.mod"), "utf8");
6843
+ raw = await fs11.readFile(path12.join(dir, "go.mod"), "utf8");
6808
6844
  } catch {
6809
6845
  return null;
6810
6846
  }
@@ -6819,15 +6855,15 @@ async function discoverGoService(scanPath, dir) {
6819
6855
  name,
6820
6856
  language: "go",
6821
6857
  dependencies: mod.dependencies,
6822
- repoPath: path11.relative(scanPath, dir),
6858
+ repoPath: path12.relative(scanPath, dir),
6823
6859
  ...framework ? { framework } : {}
6824
6860
  };
6825
6861
  return { pkg, dir, node };
6826
6862
  }
6827
6863
 
6828
6864
  // src/extract/ruby.ts
6829
- import { promises as fs11 } from "fs";
6830
- import path12 from "path";
6865
+ import { promises as fs12 } from "fs";
6866
+ import path13 from "path";
6831
6867
  import { NodeType as NodeType7, serviceId as serviceId3 } from "@neat.is/types";
6832
6868
  function parseGemfile(source) {
6833
6869
  const dependencies = {};
@@ -6844,12 +6880,12 @@ function parseGemfile(source) {
6844
6880
  async function discoverRubyService(scanPath, dir) {
6845
6881
  let raw;
6846
6882
  try {
6847
- raw = await fs11.readFile(path12.join(dir, "Gemfile"), "utf8");
6883
+ raw = await fs12.readFile(path13.join(dir, "Gemfile"), "utf8");
6848
6884
  } catch {
6849
6885
  return null;
6850
6886
  }
6851
6887
  const gemfile = parseGemfile(raw);
6852
- const name = path12.basename(dir);
6888
+ const name = path13.basename(dir);
6853
6889
  const pkg = { name, dependencies: gemfile.dependencies };
6854
6890
  const node = {
6855
6891
  id: serviceId3(name),
@@ -6857,14 +6893,14 @@ async function discoverRubyService(scanPath, dir) {
6857
6893
  name,
6858
6894
  language: "ruby",
6859
6895
  dependencies: gemfile.dependencies,
6860
- repoPath: path12.relative(scanPath, dir),
6896
+ repoPath: path13.relative(scanPath, dir),
6861
6897
  ...gemfile.dependencies["rails"] !== void 0 ? { framework: "rails" } : {}
6862
6898
  };
6863
6899
  return { pkg, dir, node };
6864
6900
  }
6865
6901
 
6866
6902
  // src/extract/php.ts
6867
- import path13 from "path";
6903
+ import path14 from "path";
6868
6904
  import { NodeType as NodeType8, serviceId as serviceId4 } from "@neat.is/types";
6869
6905
  function parseComposerJson(composer) {
6870
6906
  return {
@@ -6877,12 +6913,12 @@ function parseComposerJson(composer) {
6877
6913
  async function discoverPhpService(scanPath, dir) {
6878
6914
  let composer;
6879
6915
  try {
6880
- composer = await readJson(path13.join(dir, "composer.json"));
6916
+ composer = await readJson(path14.join(dir, "composer.json"));
6881
6917
  } catch {
6882
6918
  return null;
6883
6919
  }
6884
6920
  const { dependencies } = parseComposerJson(composer);
6885
- const name = path13.basename(dir);
6921
+ const name = path14.basename(dir);
6886
6922
  const pkg = { name, dependencies };
6887
6923
  const node = {
6888
6924
  id: serviceId4(name),
@@ -6890,30 +6926,30 @@ async function discoverPhpService(scanPath, dir) {
6890
6926
  name,
6891
6927
  language: "php",
6892
6928
  dependencies,
6893
- repoPath: path13.relative(scanPath, dir),
6929
+ repoPath: path14.relative(scanPath, dir),
6894
6930
  ...dependencies["laravel/framework"] !== void 0 ? { framework: "laravel" } : {}
6895
6931
  };
6896
6932
  return { pkg, dir, node };
6897
6933
  }
6898
6934
 
6899
6935
  // src/extract/csharp.ts
6900
- import { promises as fs12 } from "fs";
6901
- import path14 from "path";
6936
+ import { promises as fs13 } from "fs";
6937
+ import path15 from "path";
6902
6938
  import { NodeType as NodeType9, serviceId as serviceId5 } from "@neat.is/types";
6903
6939
  var CSHARP_PROJECT_EXTS = /* @__PURE__ */ new Set([".csproj", ".sln"]);
6904
6940
  async function projectFiles(dir) {
6905
- const entries = await fs12.readdir(dir, { withFileTypes: true }).catch(() => []);
6941
+ const entries = await fs13.readdir(dir, { withFileTypes: true }).catch(() => []);
6906
6942
  const out = [];
6907
6943
  for (const entry of entries) {
6908
6944
  if (!entry.isFile()) continue;
6909
- if (CSHARP_PROJECT_EXTS.has(path14.extname(entry.name).toLowerCase())) out.push(entry.name);
6945
+ if (CSHARP_PROJECT_EXTS.has(path15.extname(entry.name).toLowerCase())) out.push(entry.name);
6910
6946
  }
6911
6947
  return out.sort();
6912
6948
  }
6913
6949
  var NESTED_PROJECT_DIR = "src";
6914
6950
  async function resolveProjectDir(dir) {
6915
6951
  if ((await projectFiles(dir)).length > 0) return dir;
6916
- const nested = path14.join(dir, NESTED_PROJECT_DIR);
6952
+ const nested = path15.join(dir, NESTED_PROJECT_DIR);
6917
6953
  if ((await projectFiles(nested)).length > 0) return nested;
6918
6954
  return null;
6919
6955
  }
@@ -6938,10 +6974,10 @@ async function discoverCsharpService(scanPath, dir) {
6938
6974
  if (projectDir === null) return null;
6939
6975
  const projects = await projectFiles(projectDir);
6940
6976
  const csproj = projects.find((p) => p.toLowerCase().endsWith(".csproj"));
6941
- const name = csproj ? path14.basename(csproj, path14.extname(csproj)) : path14.basename(dir);
6977
+ const name = csproj ? path15.basename(csproj, path15.extname(csproj)) : path15.basename(dir);
6942
6978
  let dependencies = {};
6943
6979
  if (csproj) {
6944
- const raw = await fs12.readFile(path14.join(projectDir, csproj), "utf8").catch(() => "");
6980
+ const raw = await fs13.readFile(path15.join(projectDir, csproj), "utf8").catch(() => "");
6945
6981
  dependencies = parseCsproj(raw).dependencies;
6946
6982
  }
6947
6983
  const pkg = { name, dependencies };
@@ -6953,19 +6989,19 @@ async function discoverCsharpService(scanPath, dir) {
6953
6989
  dependencies,
6954
6990
  // Anchor on the directory that holds the project and its `.cs` files, so file
6955
6991
  // attribution reaches the source whether it sits at the root or under `src/`.
6956
- repoPath: path14.relative(scanPath, projectDir)
6992
+ repoPath: path15.relative(scanPath, projectDir)
6957
6993
  };
6958
6994
  return { pkg, dir: projectDir, node };
6959
6995
  }
6960
6996
 
6961
6997
  // src/extract/java.ts
6962
- import { promises as fs13 } from "fs";
6963
- import path15 from "path";
6998
+ import { promises as fs14 } from "fs";
6999
+ import path16 from "path";
6964
7000
  import { NodeType as NodeType10, serviceId as serviceId6 } from "@neat.is/types";
6965
7001
  var JAVA_MANIFESTS = ["pom.xml", "build.gradle", "build.gradle.kts"];
6966
7002
  async function hasJavaManifest(dir) {
6967
7003
  for (const manifest of JAVA_MANIFESTS) {
6968
- if (await exists(path15.join(dir, manifest))) return true;
7004
+ if (await exists(path16.join(dir, manifest))) return true;
6969
7005
  }
6970
7006
  return false;
6971
7007
  }
@@ -7000,19 +7036,19 @@ function parseGradle(source) {
7000
7036
  return { dependencies };
7001
7037
  }
7002
7038
  async function discoverJavaService(scanPath, dir) {
7003
- const pomPath = path15.join(dir, "pom.xml");
7004
- const gradlePath = path15.join(dir, "build.gradle");
7005
- const gradleKtsPath = path15.join(dir, "build.gradle.kts");
7039
+ const pomPath = path16.join(dir, "pom.xml");
7040
+ const gradlePath = path16.join(dir, "build.gradle");
7041
+ const gradleKtsPath = path16.join(dir, "build.gradle.kts");
7006
7042
  let manifest = null;
7007
7043
  if (await exists(pomPath)) {
7008
- manifest = parsePomXml(await fs13.readFile(pomPath, "utf8").catch(() => ""));
7044
+ manifest = parsePomXml(await fs14.readFile(pomPath, "utf8").catch(() => ""));
7009
7045
  } else if (await exists(gradlePath)) {
7010
- manifest = parseGradle(await fs13.readFile(gradlePath, "utf8").catch(() => ""));
7046
+ manifest = parseGradle(await fs14.readFile(gradlePath, "utf8").catch(() => ""));
7011
7047
  } else if (await exists(gradleKtsPath)) {
7012
- manifest = parseGradle(await fs13.readFile(gradleKtsPath, "utf8").catch(() => ""));
7048
+ manifest = parseGradle(await fs14.readFile(gradleKtsPath, "utf8").catch(() => ""));
7013
7049
  }
7014
7050
  if (!manifest) return null;
7015
- const name = manifest.name?.trim() || path15.basename(dir);
7051
+ const name = manifest.name?.trim() || path16.basename(dir);
7016
7052
  const dependencies = manifest.dependencies;
7017
7053
  const pkg = { name, dependencies };
7018
7054
  const node = {
@@ -7021,14 +7057,14 @@ async function discoverJavaService(scanPath, dir) {
7021
7057
  name,
7022
7058
  language: "java",
7023
7059
  dependencies,
7024
- repoPath: path15.relative(scanPath, dir)
7060
+ repoPath: path16.relative(scanPath, dir)
7025
7061
  };
7026
7062
  return { pkg, dir, node };
7027
7063
  }
7028
7064
 
7029
7065
  // src/extract/kotlin.ts
7030
- import { promises as fs14 } from "fs";
7031
- import path16 from "path";
7066
+ import { promises as fs15 } from "fs";
7067
+ import path17 from "path";
7032
7068
  import { NodeType as NodeType11, serviceId as serviceId7 } from "@neat.is/types";
7033
7069
  var KOTLIN_SOURCE_EXT = ".kt";
7034
7070
  var JAVA_SOURCE_EXT = ".java";
@@ -7038,15 +7074,15 @@ async function dominantJvmSourceLanguage(dir) {
7038
7074
  let java = 0;
7039
7075
  async function recurse(current, depth) {
7040
7076
  if (depth > SOURCE_PROBE_MAX_DEPTH) return;
7041
- const entries = await fs14.readdir(current, { withFileTypes: true }).catch(() => []);
7077
+ const entries = await fs15.readdir(current, { withFileTypes: true }).catch(() => []);
7042
7078
  for (const entry of entries) {
7043
7079
  if (entry.isDirectory()) {
7044
7080
  if (IGNORED_DIRS.has(entry.name)) continue;
7045
- await recurse(path16.join(current, entry.name), depth + 1);
7081
+ await recurse(path17.join(current, entry.name), depth + 1);
7046
7082
  continue;
7047
7083
  }
7048
7084
  if (!entry.isFile()) continue;
7049
- const ext = path16.extname(entry.name).toLowerCase();
7085
+ const ext = path17.extname(entry.name).toLowerCase();
7050
7086
  if (ext === KOTLIN_SOURCE_EXT) kt++;
7051
7087
  else if (ext === JAVA_SOURCE_EXT) java++;
7052
7088
  }
@@ -7057,17 +7093,17 @@ async function dominantJvmSourceLanguage(dir) {
7057
7093
  return void 0;
7058
7094
  }
7059
7095
  async function readJvmManifest(dir) {
7060
- const pomPath = path16.join(dir, JAVA_MANIFESTS[0]);
7061
- const gradlePath = path16.join(dir, JAVA_MANIFESTS[1]);
7062
- const gradleKtsPath = path16.join(dir, JAVA_MANIFESTS[2]);
7096
+ const pomPath = path17.join(dir, JAVA_MANIFESTS[0]);
7097
+ const gradlePath = path17.join(dir, JAVA_MANIFESTS[1]);
7098
+ const gradleKtsPath = path17.join(dir, JAVA_MANIFESTS[2]);
7063
7099
  if (await exists(pomPath)) {
7064
- return parsePomXml(await fs14.readFile(pomPath, "utf8").catch(() => ""));
7100
+ return parsePomXml(await fs15.readFile(pomPath, "utf8").catch(() => ""));
7065
7101
  }
7066
7102
  if (await exists(gradlePath)) {
7067
- return parseGradle(await fs14.readFile(gradlePath, "utf8").catch(() => ""));
7103
+ return parseGradle(await fs15.readFile(gradlePath, "utf8").catch(() => ""));
7068
7104
  }
7069
7105
  if (await exists(gradleKtsPath)) {
7070
- return parseGradle(await fs14.readFile(gradleKtsPath, "utf8").catch(() => ""));
7106
+ return parseGradle(await fs15.readFile(gradleKtsPath, "utf8").catch(() => ""));
7071
7107
  }
7072
7108
  return null;
7073
7109
  }
@@ -7076,7 +7112,7 @@ async function discoverKotlinService(scanPath, dir) {
7076
7112
  if (await dominantJvmSourceLanguage(dir) !== "kotlin") return null;
7077
7113
  const manifest = await readJvmManifest(dir);
7078
7114
  if (!manifest) return null;
7079
- const name = manifest.name?.trim() || path16.basename(dir);
7115
+ const name = manifest.name?.trim() || path17.basename(dir);
7080
7116
  const dependencies = manifest.dependencies;
7081
7117
  const pkg = { name, dependencies };
7082
7118
  const node = {
@@ -7085,19 +7121,19 @@ async function discoverKotlinService(scanPath, dir) {
7085
7121
  name,
7086
7122
  language: "kotlin",
7087
7123
  dependencies,
7088
- repoPath: path16.relative(scanPath, dir)
7124
+ repoPath: path17.relative(scanPath, dir)
7089
7125
  };
7090
7126
  return { pkg, dir, node };
7091
7127
  }
7092
7128
 
7093
7129
  // src/extract/rust.ts
7094
- import { promises as fs15 } from "fs";
7095
- import path17 from "path";
7130
+ import { promises as fs16 } from "fs";
7131
+ import path18 from "path";
7096
7132
  import { parse as parseToml2 } from "smol-toml";
7097
7133
  import { NodeType as NodeType12, serviceId as serviceId8 } from "@neat.is/types";
7098
7134
  var CARGO_MANIFEST = "Cargo.toml";
7099
7135
  async function hasRustManifest(dir) {
7100
- return exists(path17.join(dir, CARGO_MANIFEST));
7136
+ return exists(path18.join(dir, CARGO_MANIFEST));
7101
7137
  }
7102
7138
  function parseCargoToml(source) {
7103
7139
  const dependencies = {};
@@ -7122,10 +7158,10 @@ function parseCargoToml(source) {
7122
7158
  return { name, dependencies };
7123
7159
  }
7124
7160
  async function discoverRustService(scanPath, dir) {
7125
- const cargoPath = path17.join(dir, CARGO_MANIFEST);
7161
+ const cargoPath = path18.join(dir, CARGO_MANIFEST);
7126
7162
  if (!await exists(cargoPath)) return null;
7127
- const manifest = parseCargoToml(await fs15.readFile(cargoPath, "utf8").catch(() => ""));
7128
- const name = manifest.name?.trim() || path17.basename(dir);
7163
+ const manifest = parseCargoToml(await fs16.readFile(cargoPath, "utf8").catch(() => ""));
7164
+ const name = manifest.name?.trim() || path18.basename(dir);
7129
7165
  const dependencies = manifest.dependencies;
7130
7166
  const pkg = { name, dependencies };
7131
7167
  const node = {
@@ -7134,18 +7170,18 @@ async function discoverRustService(scanPath, dir) {
7134
7170
  name,
7135
7171
  language: "rust",
7136
7172
  dependencies,
7137
- repoPath: path17.relative(scanPath, dir)
7173
+ repoPath: path18.relative(scanPath, dir)
7138
7174
  };
7139
7175
  return { pkg, dir, node };
7140
7176
  }
7141
7177
 
7142
7178
  // src/extract/cpp.ts
7143
- import { promises as fs16 } from "fs";
7144
- import path18 from "path";
7179
+ import { promises as fs17 } from "fs";
7180
+ import path19 from "path";
7145
7181
  import { NodeType as NodeType13, serviceId as serviceId9 } from "@neat.is/types";
7146
7182
  var CMAKE_MANIFEST = "CMakeLists.txt";
7147
7183
  async function hasCppManifest(dir) {
7148
- return exists(path18.join(dir, CMAKE_MANIFEST));
7184
+ return exists(path19.join(dir, CMAKE_MANIFEST));
7149
7185
  }
7150
7186
  function parseCMakeProjectName(source) {
7151
7187
  const withoutComments = source.replace(/#.*$/gm, "");
@@ -7153,10 +7189,10 @@ function parseCMakeProjectName(source) {
7153
7189
  return m?.[1];
7154
7190
  }
7155
7191
  async function discoverCppService(scanPath, dir) {
7156
- const cmakePath = path18.join(dir, CMAKE_MANIFEST);
7192
+ const cmakePath = path19.join(dir, CMAKE_MANIFEST);
7157
7193
  if (!await exists(cmakePath)) return null;
7158
- const source = await fs16.readFile(cmakePath, "utf8").catch(() => "");
7159
- const name = parseCMakeProjectName(source)?.trim() || path18.basename(dir);
7194
+ const source = await fs17.readFile(cmakePath, "utf8").catch(() => "");
7195
+ const name = parseCMakeProjectName(source)?.trim() || path19.basename(dir);
7160
7196
  const dependencies = {};
7161
7197
  const pkg = { name, dependencies };
7162
7198
  const node = {
@@ -7165,14 +7201,14 @@ async function discoverCppService(scanPath, dir) {
7165
7201
  name,
7166
7202
  language: "cpp",
7167
7203
  dependencies,
7168
- repoPath: path18.relative(scanPath, dir)
7204
+ repoPath: path19.relative(scanPath, dir)
7169
7205
  };
7170
7206
  return { pkg, dir, node };
7171
7207
  }
7172
7208
 
7173
7209
  // src/extract/dockerfile-service.ts
7174
- import { promises as fs17 } from "fs";
7175
- import path19 from "path";
7210
+ import { promises as fs18 } from "fs";
7211
+ import path20 from "path";
7176
7212
  import { NodeType as NodeType14, serviceId as serviceId10 } from "@neat.is/types";
7177
7213
  function languageForSourceExt(ext) {
7178
7214
  switch (ext) {
@@ -7214,12 +7250,12 @@ function languageForSourceExt(ext) {
7214
7250
  }
7215
7251
  var LANGUAGE_PRECEDENCE = ["typescript", "javascript", "python", "go", "ruby", "php", "csharp", "java", "kotlin", "rust", "cpp"];
7216
7252
  async function inferServiceLanguage(dir) {
7217
- const entries = await fs17.readdir(dir, { withFileTypes: true }).catch(() => []);
7253
+ const entries = await fs18.readdir(dir, { withFileTypes: true }).catch(() => []);
7218
7254
  const counts = /* @__PURE__ */ new Map();
7219
7255
  for (const entry of entries) {
7220
7256
  if (!entry.isFile()) continue;
7221
7257
  if (isNeatAuthoredSourceFile(entry.name)) continue;
7222
- const ext = path19.extname(entry.name).toLowerCase();
7258
+ const ext = path20.extname(entry.name).toLowerCase();
7223
7259
  if (!SERVICE_FILE_EXTENSIONS.has(ext)) continue;
7224
7260
  const lang = languageForSourceExt(ext);
7225
7261
  if (!lang) continue;
@@ -7238,7 +7274,7 @@ async function inferServiceLanguage(dir) {
7238
7274
  return best;
7239
7275
  }
7240
7276
  async function hasLanguageManifest(dir) {
7241
- 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
7277
+ return await exists(path20.join(dir, "package.json")) || await exists(path20.join(dir, "pyproject.toml")) || await exists(path20.join(dir, "requirements.txt")) || await exists(path20.join(dir, "setup.py")) || await exists(path20.join(dir, "go.mod")) || await exists(path20.join(dir, "Gemfile")) || await exists(path20.join(dir, "composer.json")) || // C#'s marker is a glob (`*.csproj` / `*.sln`), so it reads the directory
7242
7278
  // rather than probing a fixed filename (ADR-196).
7243
7279
  await hasCsharpProject(dir) || // Java (and its JVM sibling Kotlin, ADR-199) key on the same Maven / Gradle
7244
7280
  // build manifest (ADR-197), so this one check stands the Dockerfile fallback
@@ -7248,15 +7284,15 @@ async function hasLanguageManifest(dir) {
7248
7284
  await hasCppManifest(dir);
7249
7285
  }
7250
7286
  async function hasDockerfileDeclaredService(dir) {
7251
- if (!await exists(path19.join(dir, "Dockerfile"))) return false;
7287
+ if (!await exists(path20.join(dir, "Dockerfile"))) return false;
7252
7288
  return await inferServiceLanguage(dir) !== null;
7253
7289
  }
7254
7290
  async function discoverDockerfileService(scanPath, dir) {
7255
- if (!await exists(path19.join(dir, "Dockerfile"))) return null;
7291
+ if (!await exists(path20.join(dir, "Dockerfile"))) return null;
7256
7292
  if (await hasLanguageManifest(dir)) return null;
7257
7293
  const language = await inferServiceLanguage(dir);
7258
7294
  if (!language) return null;
7259
- const name = path19.basename(dir);
7295
+ const name = path20.basename(dir);
7260
7296
  const pkg = { name, dependencies: {} };
7261
7297
  const node = {
7262
7298
  id: serviceId10(name),
@@ -7264,23 +7300,23 @@ async function discoverDockerfileService(scanPath, dir) {
7264
7300
  name,
7265
7301
  language,
7266
7302
  dependencies: {},
7267
- repoPath: path19.relative(scanPath, dir)
7303
+ repoPath: path20.relative(scanPath, dir)
7268
7304
  };
7269
7305
  return { pkg, dir, node };
7270
7306
  }
7271
7307
 
7272
7308
  // src/extract/owners.ts
7273
- import { promises as fs18 } from "fs";
7274
- import path20 from "path";
7309
+ import { promises as fs19 } from "fs";
7310
+ import path21 from "path";
7275
7311
  import { minimatch } from "minimatch";
7276
7312
  async function loadCodeowners(scanPath) {
7277
7313
  const candidates = [
7278
- path20.join(scanPath, "CODEOWNERS"),
7279
- path20.join(scanPath, ".github", "CODEOWNERS")
7314
+ path21.join(scanPath, "CODEOWNERS"),
7315
+ path21.join(scanPath, ".github", "CODEOWNERS")
7280
7316
  ];
7281
7317
  for (const file of candidates) {
7282
7318
  if (await exists(file)) {
7283
- const raw = await fs18.readFile(file, "utf8");
7319
+ const raw = await fs19.readFile(file, "utf8");
7284
7320
  return parseCodeowners(raw);
7285
7321
  }
7286
7322
  }
@@ -7298,7 +7334,7 @@ function parseCodeowners(raw) {
7298
7334
  return { rules };
7299
7335
  }
7300
7336
  function matchOwner(file, repoPath) {
7301
- const normalized = repoPath.split(path20.sep).join("/");
7337
+ const normalized = repoPath.split(path21.sep).join("/");
7302
7338
  for (const rule of file.rules) {
7303
7339
  if (matchesPattern(rule.pattern, normalized)) return rule.owners;
7304
7340
  }
@@ -7314,7 +7350,7 @@ function matchesPattern(rawPattern, repoPath) {
7314
7350
  return false;
7315
7351
  }
7316
7352
  async function readPackageJsonAuthor(serviceDir) {
7317
- const pkgPath = path20.join(serviceDir, "package.json");
7353
+ const pkgPath = path21.join(serviceDir, "package.json");
7318
7354
  if (!await exists(pkgPath)) return null;
7319
7355
  try {
7320
7356
  const pkg = await readJson(pkgPath);
@@ -7351,36 +7387,36 @@ function workspaceGlobs(pkg) {
7351
7387
  return null;
7352
7388
  }
7353
7389
  async function hasPythonManifest(dir) {
7354
- return await exists(path21.join(dir, "pyproject.toml")) || await exists(path21.join(dir, "requirements.txt")) || await exists(path21.join(dir, "setup.py"));
7390
+ return await exists(path22.join(dir, "pyproject.toml")) || await exists(path22.join(dir, "requirements.txt")) || await exists(path22.join(dir, "setup.py"));
7355
7391
  }
7356
7392
  async function hasGoManifest(dir) {
7357
- return exists(path21.join(dir, "go.mod"));
7393
+ return exists(path22.join(dir, "go.mod"));
7358
7394
  }
7359
7395
  async function hasRubyManifest(dir) {
7360
- return exists(path21.join(dir, "Gemfile"));
7396
+ return exists(path22.join(dir, "Gemfile"));
7361
7397
  }
7362
7398
  async function hasPhpManifest(dir) {
7363
- return exists(path21.join(dir, "composer.json"));
7399
+ return exists(path22.join(dir, "composer.json"));
7364
7400
  }
7365
7401
  async function hasCsharpManifest(dir) {
7366
7402
  return hasCsharpProject(dir);
7367
7403
  }
7368
7404
  async function loadGitignore(scanPath) {
7369
- const gitignorePath = path21.join(scanPath, ".gitignore");
7405
+ const gitignorePath = path22.join(scanPath, ".gitignore");
7370
7406
  if (!await exists(gitignorePath)) return null;
7371
- const raw = await fs19.readFile(gitignorePath, "utf8");
7407
+ const raw = await fs20.readFile(gitignorePath, "utf8");
7372
7408
  return ignore2().add(raw);
7373
7409
  }
7374
7410
  async function walkDirs(start, scanPath, options, visit) {
7375
7411
  async function recurse(current, depth) {
7376
7412
  if (depth > options.maxDepth) return;
7377
- const entries = await fs19.readdir(current, { withFileTypes: true }).catch(() => []);
7413
+ const entries = await fs20.readdir(current, { withFileTypes: true }).catch(() => []);
7378
7414
  for (const entry of entries) {
7379
7415
  if (!entry.isDirectory()) continue;
7380
7416
  if (IGNORED_DIRS.has(entry.name)) continue;
7381
- const child = path21.join(current, entry.name);
7417
+ const child = path22.join(current, entry.name);
7382
7418
  if (options.ig) {
7383
- const rel = path21.relative(scanPath, child).split(path21.sep).join("/");
7419
+ const rel = path22.relative(scanPath, child).split(path22.sep).join("/");
7384
7420
  if (rel && options.ig.ignores(rel + "/")) continue;
7385
7421
  }
7386
7422
  if (await isPythonVenvDir(child)) continue;
@@ -7396,8 +7432,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
7396
7432
  for (const raw of globs) {
7397
7433
  const pattern = raw.replace(/^\.\//, "");
7398
7434
  if (!pattern.includes("*")) {
7399
- const candidate = path21.join(scanPath, pattern);
7400
- if (await exists(path21.join(candidate, "package.json"))) found.add(candidate);
7435
+ const candidate = path22.join(scanPath, pattern);
7436
+ if (await exists(path22.join(candidate, "package.json"))) found.add(candidate);
7401
7437
  continue;
7402
7438
  }
7403
7439
  const segments = pattern.split("/");
@@ -7406,13 +7442,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
7406
7442
  if (seg.includes("*")) break;
7407
7443
  staticSegments.push(seg);
7408
7444
  }
7409
- const start = path21.join(scanPath, ...staticSegments);
7445
+ const start = path22.join(scanPath, ...staticSegments);
7410
7446
  if (!await exists(start)) continue;
7411
7447
  const hasDoubleStar = pattern.includes("**");
7412
7448
  const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
7413
7449
  await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
7414
- const rel = path21.relative(scanPath, dir).split(path21.sep).join("/");
7415
- if (minimatch2(rel, pattern) && await exists(path21.join(dir, "package.json"))) {
7450
+ const rel = path22.relative(scanPath, dir).split(path22.sep).join("/");
7451
+ if (minimatch2(rel, pattern) && await exists(path22.join(dir, "package.json"))) {
7416
7452
  found.add(dir);
7417
7453
  }
7418
7454
  });
@@ -7435,18 +7471,18 @@ function detectJsFramework(pkg) {
7435
7471
  async function detectJsServiceLanguage(dir, pkg) {
7436
7472
  const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
7437
7473
  if (deps["typescript"] !== void 0) return "typescript";
7438
- const entries = await fs19.readdir(dir).catch(() => []);
7474
+ const entries = await fs20.readdir(dir).catch(() => []);
7439
7475
  if (entries.some((name) => /^tsconfig(\..+)?\.json$/.test(name))) return "typescript";
7440
7476
  return "javascript";
7441
7477
  }
7442
7478
  async function discoverNodeService(scanPath, dir) {
7443
- const pkgPath = path21.join(dir, "package.json");
7479
+ const pkgPath = path22.join(dir, "package.json");
7444
7480
  if (!await exists(pkgPath)) return null;
7445
7481
  let pkg;
7446
7482
  try {
7447
7483
  pkg = await readJson(pkgPath);
7448
7484
  } catch (err) {
7449
- recordExtractionError("services", path21.relative(scanPath, pkgPath), err);
7485
+ recordExtractionError("services", path22.relative(scanPath, pkgPath), err);
7450
7486
  return null;
7451
7487
  }
7452
7488
  if (!pkg.name) return null;
@@ -7459,7 +7495,7 @@ async function discoverNodeService(scanPath, dir) {
7459
7495
  language,
7460
7496
  version: pkg.version,
7461
7497
  dependencies: pkg.dependencies ?? {},
7462
- repoPath: path21.relative(scanPath, dir),
7498
+ repoPath: path22.relative(scanPath, dir),
7463
7499
  ...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {},
7464
7500
  ...framework ? { framework } : {}
7465
7501
  };
@@ -7476,12 +7512,12 @@ async function discoverPyService(scanPath, dir) {
7476
7512
  language: "python",
7477
7513
  version: py.version,
7478
7514
  dependencies: py.dependencies,
7479
- repoPath: path21.relative(scanPath, dir)
7515
+ repoPath: path22.relative(scanPath, dir)
7480
7516
  };
7481
7517
  return { pkg, dir, node };
7482
7518
  }
7483
7519
  async function discoverServices(scanPath) {
7484
- const rootPkgPath = path21.join(scanPath, "package.json");
7520
+ const rootPkgPath = path22.join(scanPath, "package.json");
7485
7521
  let rootPkg = null;
7486
7522
  if (await exists(rootPkgPath)) {
7487
7523
  try {
@@ -7489,7 +7525,7 @@ async function discoverServices(scanPath) {
7489
7525
  } catch (err) {
7490
7526
  recordExtractionError(
7491
7527
  "services workspaces",
7492
- path21.relative(scanPath, rootPkgPath),
7528
+ path22.relative(scanPath, rootPkgPath),
7493
7529
  err
7494
7530
  );
7495
7531
  }
@@ -7510,7 +7546,7 @@ async function discoverServices(scanPath) {
7510
7546
  scanPath,
7511
7547
  { maxDepth: parseScanDepth(), ig },
7512
7548
  async (dir) => {
7513
- if (await exists(path21.join(dir, "package.json"))) {
7549
+ if (await exists(path22.join(dir, "package.json"))) {
7514
7550
  candidateDirs.push(dir);
7515
7551
  } 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)) {
7516
7552
  candidateDirs.push(dir);
@@ -7538,8 +7574,8 @@ async function discoverServices(scanPath) {
7538
7574
  seenDirs.add(service.dir);
7539
7575
  const existingDir = seen.get(service.node.name);
7540
7576
  if (existingDir !== void 0) {
7541
- const a = path21.relative(scanPath, existingDir) || ".";
7542
- const b = path21.relative(scanPath, dir) || ".";
7577
+ const a = path22.relative(scanPath, existingDir) || ".";
7578
+ const b = path22.relative(scanPath, dir) || ".";
7543
7579
  console.warn(
7544
7580
  `[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
7545
7581
  );
@@ -7553,14 +7589,14 @@ async function discoverServices(scanPath) {
7553
7589
  const owner = await computeServiceOwner(codeowners, service.node.repoPath, service.dir);
7554
7590
  if (owner !== void 0) service.node.owner = owner;
7555
7591
  }
7556
- const resolvedDirs = out.map((s) => path21.resolve(s.dir));
7592
+ const resolvedDirs = out.map((s) => path22.resolve(s.dir));
7557
7593
  for (let i = 0; i < out.length; i++) {
7558
7594
  const self = resolvedDirs[i];
7559
7595
  const nested = [];
7560
7596
  for (let j = 0; j < out.length; j++) {
7561
7597
  if (i === j) continue;
7562
7598
  const other = resolvedDirs[j];
7563
- if (other.startsWith(self + path21.sep)) nested.push(other);
7599
+ if (other.startsWith(self + path22.sep)) nested.push(other);
7564
7600
  }
7565
7601
  out[i].excludeDirs = nested;
7566
7602
  }
@@ -7586,8 +7622,8 @@ function addServiceNodes(graph, services) {
7586
7622
  }
7587
7623
 
7588
7624
  // src/extract/aliases.ts
7589
- import path22 from "path";
7590
- import { promises as fs20 } from "fs";
7625
+ import path23 from "path";
7626
+ import { promises as fs21 } from "fs";
7591
7627
  import { parseAllDocuments } from "yaml";
7592
7628
  import { NodeType as NodeType16 } from "@neat.is/types";
7593
7629
  var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
@@ -7614,14 +7650,14 @@ function indexServicesByName(services) {
7614
7650
  const map = /* @__PURE__ */ new Map();
7615
7651
  for (const s of services) {
7616
7652
  map.set(s.node.name, s.node.id);
7617
- map.set(path22.basename(s.dir), s.node.id);
7653
+ map.set(path23.basename(s.dir), s.node.id);
7618
7654
  }
7619
7655
  return map;
7620
7656
  }
7621
7657
  async function collectComposeAliases(graph, scanPath, serviceIndex) {
7622
7658
  let composePath = null;
7623
7659
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
7624
- const abs = path22.join(scanPath, name);
7660
+ const abs = path23.join(scanPath, name);
7625
7661
  if (await exists(abs)) {
7626
7662
  composePath = abs;
7627
7663
  break;
@@ -7634,7 +7670,7 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
7634
7670
  } catch (err) {
7635
7671
  recordExtractionError(
7636
7672
  "aliases compose",
7637
- path22.relative(scanPath, composePath),
7673
+ path23.relative(scanPath, composePath),
7638
7674
  err
7639
7675
  );
7640
7676
  return;
@@ -7677,11 +7713,11 @@ function parseDockerfileLabels(content) {
7677
7713
  }
7678
7714
  async function collectDockerfileAliases(graph, services) {
7679
7715
  for (const service of services) {
7680
- const dockerfilePath = path22.join(service.dir, "Dockerfile");
7716
+ const dockerfilePath = path23.join(service.dir, "Dockerfile");
7681
7717
  if (!await exists(dockerfilePath)) continue;
7682
7718
  let content;
7683
7719
  try {
7684
- content = await fs20.readFile(dockerfilePath, "utf8");
7720
+ content = await fs21.readFile(dockerfilePath, "utf8");
7685
7721
  } catch (err) {
7686
7722
  recordExtractionError("aliases dockerfile", dockerfilePath, err);
7687
7723
  continue;
@@ -7693,15 +7729,15 @@ async function collectDockerfileAliases(graph, services) {
7693
7729
  async function walkYamlFiles(start, depth = 0, max = 5) {
7694
7730
  if (depth > max) return [];
7695
7731
  const out = [];
7696
- const entries = await fs20.readdir(start, { withFileTypes: true }).catch(() => []);
7732
+ const entries = await fs21.readdir(start, { withFileTypes: true }).catch(() => []);
7697
7733
  for (const entry of entries) {
7698
7734
  if (entry.isDirectory()) {
7699
7735
  if (IGNORED_DIRS.has(entry.name)) continue;
7700
- const child = path22.join(start, entry.name);
7736
+ const child = path23.join(start, entry.name);
7701
7737
  if (await isPythonVenvDir(child)) continue;
7702
7738
  out.push(...await walkYamlFiles(child, depth + 1, max));
7703
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path22.extname(entry.name))) {
7704
- out.push(path22.join(start, entry.name));
7739
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path23.extname(entry.name))) {
7740
+ out.push(path23.join(start, entry.name));
7705
7741
  }
7706
7742
  }
7707
7743
  return out;
@@ -7728,7 +7764,7 @@ function k8sServiceTarget(doc, byName) {
7728
7764
  async function collectK8sAliases(graph, scanPath, serviceIndex) {
7729
7765
  const files = await walkYamlFiles(scanPath);
7730
7766
  for (const file of files) {
7731
- const content = await fs20.readFile(file, "utf8");
7767
+ const content = await fs21.readFile(file, "utf8");
7732
7768
  let docs;
7733
7769
  try {
7734
7770
  docs = parseAllDocuments(content).map((d) => d.toJSON());
@@ -7752,14 +7788,14 @@ async function addServiceAliases(graph, scanPath, services) {
7752
7788
  }
7753
7789
 
7754
7790
  // src/extract/files.ts
7755
- import path23 from "path";
7791
+ import path24 from "path";
7756
7792
  async function addFiles(graph, services) {
7757
7793
  let nodesAdded = 0;
7758
7794
  let edgesAdded = 0;
7759
7795
  for (const service of services) {
7760
7796
  const filePaths = await walkSourceFiles(service.dir, service.excludeDirs);
7761
7797
  for (const filePath of filePaths) {
7762
- const relPath = toPosix(path23.relative(service.dir, filePath));
7798
+ const relPath = toPosix(path24.relative(service.dir, filePath));
7763
7799
  const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
7764
7800
  graph,
7765
7801
  service.pkg.name,
@@ -7774,7 +7810,7 @@ async function addFiles(graph, services) {
7774
7810
  }
7775
7811
 
7776
7812
  // src/extract/symbols.ts
7777
- import path24 from "path";
7813
+ import path25 from "path";
7778
7814
  import Parser3 from "tree-sitter";
7779
7815
  import JavaScript3 from "tree-sitter-javascript";
7780
7816
  import TypeScript from "tree-sitter-typescript";
@@ -8533,10 +8569,10 @@ async function addSymbols(graph, services) {
8533
8569
  for (const service of services) {
8534
8570
  const files = await loadSourceFiles(service.dir, service.excludeDirs);
8535
8571
  for (const file of files) {
8536
- const ext = path24.extname(file.path);
8572
+ const ext = path25.extname(file.path);
8537
8573
  const parser = parserForExt5(ext);
8538
8574
  if (!parser) continue;
8539
- const relPath = toPosix(path24.relative(service.dir, file.path));
8575
+ const relPath = toPosix(path25.relative(service.dir, file.path));
8540
8576
  let defs;
8541
8577
  try {
8542
8578
  const tree = parseSource3(parser, file.content);
@@ -8595,7 +8631,7 @@ async function addSymbols(graph, services) {
8595
8631
  }
8596
8632
 
8597
8633
  // src/extract/symbol-edges.ts
8598
- import path25 from "path";
8634
+ import path26 from "path";
8599
8635
  import Parser4 from "tree-sitter";
8600
8636
  import {
8601
8637
  EdgeType as EdgeType8,
@@ -8709,10 +8745,10 @@ async function addSymbolEdges(graph, services) {
8709
8745
  const tsPaths = await loadTsPathConfig(service.dir);
8710
8746
  const files = await loadSourceFiles(service.dir, service.excludeDirs);
8711
8747
  for (const file of files) {
8712
- const parser = parserForExt5(path25.extname(file.path));
8748
+ const parser = parserForExt5(path26.extname(file.path));
8713
8749
  if (!parser) continue;
8714
- const relPath = toPosix(path25.relative(service.dir, file.path));
8715
- const fileDir = path25.dirname(file.path);
8750
+ const relPath = toPosix(path26.relative(service.dir, file.path));
8751
+ const fileDir = path26.dirname(file.path);
8716
8752
  let root;
8717
8753
  try {
8718
8754
  root = parseSource3(parser, file.content).rootNode;
@@ -8846,7 +8882,7 @@ async function addSymbolEdges(graph, services) {
8846
8882
  }
8847
8883
 
8848
8884
  // src/extract/actions.ts
8849
- import path26 from "path";
8885
+ import path27 from "path";
8850
8886
  import Parser5 from "tree-sitter";
8851
8887
  import {
8852
8888
  EdgeType as EdgeType9,
@@ -9000,9 +9036,9 @@ async function addServerActions(graph, services) {
9000
9036
  const files = await loadSourceFiles(service.dir, service.excludeDirs);
9001
9037
  for (const file of files) {
9002
9038
  if (isTestPath(file.path)) continue;
9003
- const parser = parserForExt5(path26.extname(file.path));
9039
+ const parser = parserForExt5(path27.extname(file.path));
9004
9040
  if (!parser) continue;
9005
- const relPath = toPosix(path26.relative(service.dir, file.path));
9041
+ const relPath = toPosix(path27.relative(service.dir, file.path));
9006
9042
  let root;
9007
9043
  try {
9008
9044
  root = parseSource3(parser, file.content).rootNode;
@@ -9063,10 +9099,10 @@ async function addServerActions(graph, services) {
9063
9099
  }
9064
9100
  for (const file of files) {
9065
9101
  if (isTestPath(file.path)) continue;
9066
- const parser = parserForExt5(path26.extname(file.path));
9102
+ const parser = parserForExt5(path27.extname(file.path));
9067
9103
  if (!parser) continue;
9068
- const relPath = toPosix(path26.relative(service.dir, file.path));
9069
- const fileDir = path26.dirname(file.path);
9104
+ const relPath = toPosix(path27.relative(service.dir, file.path));
9105
+ const fileDir = path27.dirname(file.path);
9070
9106
  let root;
9071
9107
  try {
9072
9108
  root = parseSource3(parser, file.content).rootNode;
@@ -9118,7 +9154,7 @@ async function addServerActions(graph, services) {
9118
9154
  }
9119
9155
 
9120
9156
  // src/extract/databases/index.ts
9121
- import path35 from "path";
9157
+ import path36 from "path";
9122
9158
  import {
9123
9159
  EdgeType as EdgeType10,
9124
9160
  NodeType as NodeType20,
@@ -9129,9 +9165,9 @@ import {
9129
9165
  } from "@neat.is/types";
9130
9166
 
9131
9167
  // src/extract/databases/db-config-yaml.ts
9132
- import path27 from "path";
9168
+ import path28 from "path";
9133
9169
  async function parse(serviceDir) {
9134
- const yamlPath = path27.join(serviceDir, "db-config.yaml");
9170
+ const yamlPath = path28.join(serviceDir, "db-config.yaml");
9135
9171
  if (!await exists(yamlPath)) return [];
9136
9172
  const raw = await readYaml(yamlPath);
9137
9173
  return [
@@ -9148,12 +9184,12 @@ async function parse(serviceDir) {
9148
9184
  var dbConfigYamlParser = { name: "db-config.yaml", parse };
9149
9185
 
9150
9186
  // src/extract/databases/dotenv.ts
9151
- import { promises as fs22 } from "fs";
9152
- import path29 from "path";
9187
+ import { promises as fs23 } from "fs";
9188
+ import path30 from "path";
9153
9189
 
9154
9190
  // src/extract/databases/shared.ts
9155
- import { promises as fs21 } from "fs";
9156
- import path28 from "path";
9191
+ import { promises as fs22 } from "fs";
9192
+ import path29 from "path";
9157
9193
  function schemeToEngine(scheme) {
9158
9194
  const s = scheme.toLowerCase().split("+")[0];
9159
9195
  switch (s) {
@@ -9192,18 +9228,18 @@ function parseConnectionString(url) {
9192
9228
  }
9193
9229
  async function readIfExists(filePath) {
9194
9230
  try {
9195
- return await fs21.readFile(filePath, "utf8");
9231
+ return await fs22.readFile(filePath, "utf8");
9196
9232
  } catch {
9197
9233
  return null;
9198
9234
  }
9199
9235
  }
9200
9236
  async function resolveEnvVar(serviceDir, name) {
9201
- const entries = await fs21.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
9237
+ const entries = await fs22.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
9202
9238
  const envNames = entries.filter((e) => e.isFile() && (e.name === ".env" || e.name.startsWith(".env."))).map((e) => e.name);
9203
9239
  const rank = (n) => n === ".env.local" ? 0 : n === ".env" ? 1 : 2;
9204
9240
  envNames.sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
9205
9241
  for (const fileName of envNames) {
9206
- const content = await readIfExists(path28.join(serviceDir, fileName));
9242
+ const content = await readIfExists(path29.join(serviceDir, fileName));
9207
9243
  if (!content) continue;
9208
9244
  for (const line of content.split("\n")) {
9209
9245
  const trimmed = line.trim();
@@ -9222,7 +9258,7 @@ async function resolveEnvVar(serviceDir, name) {
9222
9258
  }
9223
9259
  async function findFirst(serviceDir, candidates) {
9224
9260
  for (const rel of candidates) {
9225
- const abs = path28.join(serviceDir, rel);
9261
+ const abs = path29.join(serviceDir, rel);
9226
9262
  const content = await readIfExists(abs);
9227
9263
  if (content !== null) return abs;
9228
9264
  }
@@ -9280,15 +9316,15 @@ function parseDotenvLine(line) {
9280
9316
  return { key, value };
9281
9317
  }
9282
9318
  async function parse2(serviceDir) {
9283
- const entries = await fs22.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
9319
+ const entries = await fs23.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
9284
9320
  const configs = [];
9285
9321
  const seen = /* @__PURE__ */ new Set();
9286
9322
  for (const entry of entries) {
9287
9323
  if (!entry.isFile()) continue;
9288
9324
  const match = isConfigFile(entry.name);
9289
9325
  if (!match.match || match.fileType !== "env") continue;
9290
- const filePath = path29.join(serviceDir, entry.name);
9291
- const content = await fs22.readFile(filePath, "utf8");
9326
+ const filePath = path30.join(serviceDir, entry.name);
9327
+ const content = await fs23.readFile(filePath, "utf8");
9292
9328
  for (const line of content.split("\n")) {
9293
9329
  const parsed = parseDotenvLine(line);
9294
9330
  if (!parsed) continue;
@@ -9306,9 +9342,9 @@ async function parse2(serviceDir) {
9306
9342
  var dotenvParser = { name: ".env", parse: parse2 };
9307
9343
 
9308
9344
  // src/extract/databases/prisma.ts
9309
- import path30 from "path";
9345
+ import path31 from "path";
9310
9346
  async function parse3(serviceDir) {
9311
- const schemaPath = path30.join(serviceDir, "prisma", "schema.prisma");
9347
+ const schemaPath = path31.join(serviceDir, "prisma", "schema.prisma");
9312
9348
  const content = await readIfExists(schemaPath);
9313
9349
  if (!content) return [];
9314
9350
  const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
@@ -9463,10 +9499,10 @@ async function parse5(serviceDir) {
9463
9499
  var knexParser = { name: "knex", parse: parse5 };
9464
9500
 
9465
9501
  // src/extract/databases/ormconfig.ts
9466
- import path31 from "path";
9502
+ import path32 from "path";
9467
9503
  async function parse6(serviceDir) {
9468
9504
  for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
9469
- const abs = path31.join(serviceDir, candidate);
9505
+ const abs = path32.join(serviceDir, candidate);
9470
9506
  if (!await exists(abs)) continue;
9471
9507
  const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
9472
9508
  const entries = Array.isArray(raw) ? raw : [raw];
@@ -9524,9 +9560,9 @@ async function parse7(serviceDir) {
9524
9560
  var typeormParser = { name: "typeorm", parse: parse7 };
9525
9561
 
9526
9562
  // src/extract/databases/sequelize.ts
9527
- import path32 from "path";
9563
+ import path33 from "path";
9528
9564
  async function parse8(serviceDir) {
9529
- const configPath = path32.join(serviceDir, "config", "config.json");
9565
+ const configPath = path33.join(serviceDir, "config", "config.json");
9530
9566
  if (!await exists(configPath)) return [];
9531
9567
  const raw = await readJson(configPath);
9532
9568
  const out = [];
@@ -9552,8 +9588,8 @@ async function parse8(serviceDir) {
9552
9588
  var sequelizeParser = { name: "sequelize", parse: parse8 };
9553
9589
 
9554
9590
  // src/extract/databases/csharp.ts
9555
- import { promises as fs23 } from "fs";
9556
- import path33 from "path";
9591
+ import { promises as fs24 } from "fs";
9592
+ import path34 from "path";
9557
9593
  var CS_EXT = ".cs";
9558
9594
  var NPGSQL_GATE = /\bUseNpgsql\b|\bNpgsql\b/;
9559
9595
  var REDIS_GATE = /\bConnectionMultiplexer\b|\bStackExchange\.Redis\b|\bConfigurationOptions\.Parse\b|\bAddStackExchangeRedisCache\b/;
@@ -9600,12 +9636,12 @@ function parseRedisEndpoint(raw) {
9600
9636
  return { host, port, database: "", engine: "redis", engineVersion: "unknown" };
9601
9637
  }
9602
9638
  async function resolveEnvUpTree(startDir, name) {
9603
- let dir = path33.resolve(startDir);
9639
+ let dir = path34.resolve(startDir);
9604
9640
  for (let depth = 0; depth < 12; depth++) {
9605
9641
  const value = await resolveEnvVar(dir, name);
9606
9642
  if (value !== null) return value;
9607
- const atRepoRoot = await fs23.access(path33.join(dir, ".git")).then(() => true).catch(() => false);
9608
- const parent = path33.dirname(dir);
9643
+ const atRepoRoot = await fs24.access(path34.join(dir, ".git")).then(() => true).catch(() => false);
9644
+ const parent = path34.dirname(dir);
9609
9645
  if (atRepoRoot || parent === dir) break;
9610
9646
  dir = parent;
9611
9647
  }
@@ -9660,12 +9696,12 @@ async function resolveConfigs(literals, keys, serviceDir, looksLike, parse11) {
9660
9696
  }
9661
9697
  async function parse9(serviceDir) {
9662
9698
  const files = (await walkSourceFiles(serviceDir).catch(() => [])).filter(
9663
- (f) => path33.extname(f) === CS_EXT
9699
+ (f) => path34.extname(f) === CS_EXT
9664
9700
  );
9665
9701
  if (files.length === 0) return [];
9666
9702
  const sources = [];
9667
9703
  for (const file of files) {
9668
- const content = await fs23.readFile(file, "utf8").catch(() => null);
9704
+ const content = await fs24.readFile(file, "utf8").catch(() => null);
9669
9705
  if (content !== null) sources.push({ file, content });
9670
9706
  }
9671
9707
  let pgGateFile = null;
@@ -9705,7 +9741,7 @@ async function parse9(serviceDir) {
9705
9741
  var csharpParser = { name: "csharp", parse: parse9 };
9706
9742
 
9707
9743
  // src/extract/databases/docker-compose.ts
9708
- import path34 from "path";
9744
+ import path35 from "path";
9709
9745
  function portFromService(svc) {
9710
9746
  for (const raw of svc.ports ?? []) {
9711
9747
  const str = String(raw);
@@ -9732,7 +9768,7 @@ function databaseFromEnv(svc) {
9732
9768
  }
9733
9769
  async function parse10(serviceDir) {
9734
9770
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
9735
- const abs = path34.join(serviceDir, name);
9771
+ const abs = path35.join(serviceDir, name);
9736
9772
  if (!await exists(abs)) continue;
9737
9773
  const raw = await readYaml(abs);
9738
9774
  if (!raw?.services) return [];
@@ -9905,7 +9941,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
9905
9941
  discoveredVia: mergedDiscoveredVia
9906
9942
  });
9907
9943
  }
9908
- const relConfigFile = toPosix(path35.relative(service.dir, config.sourceFile));
9944
+ const relConfigFile = toPosix(path36.relative(service.dir, config.sourceFile));
9909
9945
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
9910
9946
  graph,
9911
9947
  service.pkg.name,
@@ -9914,7 +9950,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
9914
9950
  );
9915
9951
  nodesAdded += fn;
9916
9952
  edgesAdded += fe;
9917
- const evidenceFile = toPosix(path35.relative(scanPath, config.sourceFile));
9953
+ const evidenceFile = toPosix(path36.relative(scanPath, config.sourceFile));
9918
9954
  const edge = {
9919
9955
  id: extractedEdgeId(fileNodeId, dbNode.id, EdgeType10.CONNECTS_TO),
9920
9956
  source: fileNodeId,
@@ -9935,15 +9971,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
9935
9971
  if (allConfigs.length === 1) {
9936
9972
  const primary = allConfigs[0];
9937
9973
  service.node.dbConnectionTarget = primary.port ? `${primary.host}:${primary.port}` : primary.host;
9938
- const relPath = path35.relative(scanPath, primary.sourceFile);
9974
+ const relPath = path36.relative(scanPath, primary.sourceFile);
9939
9975
  const cfgId = configId(relPath);
9940
9976
  if (!graph.hasNode(cfgId)) {
9941
9977
  const cfgNode = {
9942
9978
  id: cfgId,
9943
9979
  type: NodeType20.ConfigNode,
9944
- name: path35.basename(primary.sourceFile),
9980
+ name: path36.basename(primary.sourceFile),
9945
9981
  path: relPath,
9946
- fileType: isConfigFile(path35.basename(primary.sourceFile)).fileType || "config"
9982
+ fileType: isConfigFile(path36.basename(primary.sourceFile)).fileType || "config"
9947
9983
  };
9948
9984
  graph.addNode(cfgId, cfgNode);
9949
9985
  nodesAdded++;
@@ -9983,8 +10019,8 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
9983
10019
  }
9984
10020
 
9985
10021
  // src/extract/configs.ts
9986
- import { promises as fs24 } from "fs";
9987
- import path36 from "path";
10022
+ import { promises as fs25 } from "fs";
10023
+ import path37 from "path";
9988
10024
  import {
9989
10025
  EdgeType as EdgeType11,
9990
10026
  NodeType as NodeType21,
@@ -9993,15 +10029,15 @@ import {
9993
10029
  confidenceForExtracted as confidenceForExtracted8
9994
10030
  } from "@neat.is/types";
9995
10031
  async function walkConfigFiles(dir, excludeDirs = []) {
9996
- const excluded = new Set(excludeDirs.map((d) => path36.resolve(d)));
10032
+ const excluded = new Set(excludeDirs.map((d) => path37.resolve(d)));
9997
10033
  const out = [];
9998
10034
  async function walk11(current) {
9999
- const entries = await fs24.readdir(current, { withFileTypes: true });
10035
+ const entries = await fs25.readdir(current, { withFileTypes: true });
10000
10036
  for (const entry of entries) {
10001
- const full = path36.join(current, entry.name);
10037
+ const full = path37.join(current, entry.name);
10002
10038
  if (entry.isDirectory()) {
10003
10039
  if (IGNORED_DIRS.has(entry.name)) continue;
10004
- if (excluded.has(path36.resolve(full))) continue;
10040
+ if (excluded.has(path37.resolve(full))) continue;
10005
10041
  if (await isPythonVenvDir(full)) continue;
10006
10042
  await walk11(full);
10007
10043
  } else if (entry.isFile() && isConfigFile(entry.name).match) {
@@ -10018,19 +10054,19 @@ async function addConfigNodes(graph, services, scanPath) {
10018
10054
  for (const service of services) {
10019
10055
  const configFiles = await walkConfigFiles(service.dir, service.excludeDirs);
10020
10056
  for (const file of configFiles) {
10021
- const relPath = path36.relative(scanPath, file);
10057
+ const relPath = path37.relative(scanPath, file);
10022
10058
  const node = {
10023
10059
  id: configId2(relPath),
10024
10060
  type: NodeType21.ConfigNode,
10025
- name: path36.basename(file),
10061
+ name: path37.basename(file),
10026
10062
  path: relPath,
10027
- fileType: isConfigFile(path36.basename(file)).fileType
10063
+ fileType: isConfigFile(path37.basename(file)).fileType
10028
10064
  };
10029
10065
  if (!graph.hasNode(node.id)) {
10030
10066
  graph.addNode(node.id, node);
10031
10067
  nodesAdded++;
10032
10068
  }
10033
- const relToService = toPosix(path36.relative(service.dir, file));
10069
+ const relToService = toPosix(path37.relative(service.dir, file));
10034
10070
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
10035
10071
  graph,
10036
10072
  service.pkg.name,
@@ -10046,7 +10082,7 @@ async function addConfigNodes(graph, services, scanPath) {
10046
10082
  type: EdgeType11.CONFIGURED_BY,
10047
10083
  provenance: Provenance11.EXTRACTED,
10048
10084
  confidence: confidenceForExtracted8("structural"),
10049
- evidence: { file: relPath.split(path36.sep).join("/") }
10085
+ evidence: { file: relPath.split(path37.sep).join("/") }
10050
10086
  };
10051
10087
  if (!graph.hasEdge(edge.id)) {
10052
10088
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -10058,8 +10094,8 @@ async function addConfigNodes(graph, services, scanPath) {
10058
10094
  }
10059
10095
 
10060
10096
  // src/extract/proto.ts
10061
- import { promises as fs25 } from "fs";
10062
- import path37 from "path";
10097
+ import { promises as fs26 } from "fs";
10098
+ import path38 from "path";
10063
10099
  import {
10064
10100
  EdgeType as EdgeType12,
10065
10101
  NodeType as NodeType22,
@@ -10093,10 +10129,10 @@ function grpcMethodsFromProto(content, fqPackage) {
10093
10129
  }
10094
10130
  const body = content.slice(bodyStart, i - 1);
10095
10131
  const rpcRe = /\brpc\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/g;
10096
- let rm;
10097
- while ((rm = rpcRe.exec(body)) !== null) {
10098
- const rpcMethod = rm[1];
10099
- const line = lineAt(content, bodyStart + rm.index);
10132
+ let rm2;
10133
+ while ((rm2 = rpcRe.exec(body)) !== null) {
10134
+ const rpcMethod = rm2[1];
10135
+ const line = lineAt(content, bodyStart + rm2.index);
10100
10136
  out.push({ rpcService, rpcMethod, line });
10101
10137
  }
10102
10138
  serviceRe.lastIndex = i;
@@ -10104,18 +10140,18 @@ function grpcMethodsFromProto(content, fqPackage) {
10104
10140
  return out;
10105
10141
  }
10106
10142
  async function walkProtoFiles(dir, excludeDirs = []) {
10107
- const excluded = new Set(excludeDirs.map((d) => path37.resolve(d)));
10143
+ const excluded = new Set(excludeDirs.map((d) => path38.resolve(d)));
10108
10144
  const out = [];
10109
10145
  async function walk11(current) {
10110
- const entries = await fs25.readdir(current, { withFileTypes: true }).catch(() => []);
10146
+ const entries = await fs26.readdir(current, { withFileTypes: true }).catch(() => []);
10111
10147
  for (const entry of entries) {
10112
- const full = path37.join(current, entry.name);
10148
+ const full = path38.join(current, entry.name);
10113
10149
  if (entry.isDirectory()) {
10114
10150
  if (IGNORED_DIRS.has(entry.name)) continue;
10115
- if (excluded.has(path37.resolve(full))) continue;
10151
+ if (excluded.has(path38.resolve(full))) continue;
10116
10152
  if (await isPythonVenvDir(full)) continue;
10117
10153
  await walk11(full);
10118
- } else if (entry.isFile() && path37.extname(entry.name) === PROTO_EXTENSION) {
10154
+ } else if (entry.isFile() && path38.extname(entry.name) === PROTO_EXTENSION) {
10119
10155
  out.push(full);
10120
10156
  }
10121
10157
  }
@@ -10130,10 +10166,10 @@ async function addGrpcMethods(graph, services) {
10130
10166
  const protoPaths = await walkProtoFiles(service.dir, service.excludeDirs);
10131
10167
  for (const protoPath of protoPaths) {
10132
10168
  if (isTestPath(protoPath)) continue;
10133
- const relFile = toPosix(path37.relative(service.dir, protoPath));
10169
+ const relFile = toPosix(path38.relative(service.dir, protoPath));
10134
10170
  let content;
10135
10171
  try {
10136
- content = await fs25.readFile(protoPath, "utf8");
10172
+ content = await fs26.readFile(protoPath, "utf8");
10137
10173
  } catch (err) {
10138
10174
  recordExtractionError("proto extraction", protoPath, err);
10139
10175
  continue;
@@ -10196,7 +10232,7 @@ import {
10196
10232
  } from "@neat.is/types";
10197
10233
 
10198
10234
  // src/extract/calls/http.ts
10199
- import path38 from "path";
10235
+ import path39 from "path";
10200
10236
  import Parser6 from "tree-sitter";
10201
10237
  import JavaScript4 from "tree-sitter-javascript";
10202
10238
  import TypeScript2 from "tree-sitter-typescript";
@@ -10284,7 +10320,7 @@ async function addHttpCallEdges(graph, services) {
10284
10320
  const seen = /* @__PURE__ */ new Set();
10285
10321
  for (const file of files) {
10286
10322
  if (isTestPath(file.path)) continue;
10287
- const parser = parserForExt(path38.extname(file.path), parserCache);
10323
+ const parser = parserForExt(path39.extname(file.path), parserCache);
10288
10324
  let sites;
10289
10325
  try {
10290
10326
  sites = callsFromSource(file.content, parser, knownHosts);
@@ -10293,7 +10329,7 @@ async function addHttpCallEdges(graph, services) {
10293
10329
  continue;
10294
10330
  }
10295
10331
  if (sites.length === 0) continue;
10296
- const relFile = toPosix(path38.relative(service.dir, file.path));
10332
+ const relFile = toPosix(path39.relative(service.dir, file.path));
10297
10333
  for (const site of sites) {
10298
10334
  const targetId = hostToNodeId.get(site.host);
10299
10335
  if (!targetId || targetId === service.node.id) continue;
@@ -10346,7 +10382,7 @@ async function addHttpCallEdges(graph, services) {
10346
10382
  }
10347
10383
 
10348
10384
  // src/extract/calls/route-match.ts
10349
- import path39 from "path";
10385
+ import path40 from "path";
10350
10386
  import Parser7 from "tree-sitter";
10351
10387
  import JavaScript5 from "tree-sitter-javascript";
10352
10388
  import {
@@ -10631,7 +10667,7 @@ async function addRouteCallEdges(graph, services) {
10631
10667
  const seen = /* @__PURE__ */ new Set();
10632
10668
  for (const file of files) {
10633
10669
  if (isTestPath(file.path)) continue;
10634
- if (!JS_CLIENT_EXTENSIONS.has(path39.extname(file.path))) continue;
10670
+ if (!JS_CLIENT_EXTENSIONS.has(path40.extname(file.path))) continue;
10635
10671
  let sites;
10636
10672
  try {
10637
10673
  sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
@@ -10640,7 +10676,7 @@ async function addRouteCallEdges(graph, services) {
10640
10676
  continue;
10641
10677
  }
10642
10678
  if (sites.length === 0) continue;
10643
- const relFile = toPosix(path39.relative(service.dir, file.path));
10679
+ const relFile = toPosix(path40.relative(service.dir, file.path));
10644
10680
  for (const site of sites) {
10645
10681
  if (site.host === null) {
10646
10682
  const dedupKey2 = `${relFile}|unresolved-host`;
@@ -10732,7 +10768,7 @@ async function addRouteCallEdges(graph, services) {
10732
10768
  }
10733
10769
 
10734
10770
  // src/extract/calls/kafka.ts
10735
- import path40 from "path";
10771
+ import path41 from "path";
10736
10772
  import { infraId as infraId2 } from "@neat.is/types";
10737
10773
  var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
10738
10774
  var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
@@ -10820,13 +10856,13 @@ function kafkaEndpointsFromFile(file, serviceDir) {
10820
10856
  // call sites — verified-call-site tier (ADR-066).
10821
10857
  confidenceKind: "verified-call-site",
10822
10858
  evidence: {
10823
- file: path40.relative(serviceDir, file.path),
10859
+ file: path41.relative(serviceDir, file.path),
10824
10860
  line,
10825
10861
  snippet: snippet(file.content, line)
10826
10862
  }
10827
10863
  });
10828
10864
  };
10829
- if (path40.extname(file.path) === ".go") {
10865
+ if (path41.extname(file.path) === ".go") {
10830
10866
  goSaramaEndpoints(file.content, make);
10831
10867
  } else {
10832
10868
  for (const { topic } of findAll(PRODUCER_TOPIC_RE, file.content)) make(topic, "PUBLISHES_TO");
@@ -10836,7 +10872,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
10836
10872
  }
10837
10873
 
10838
10874
  // src/extract/calls/redis.ts
10839
- import path41 from "path";
10875
+ import path42 from "path";
10840
10876
  import { infraId as infraId3 } from "@neat.is/types";
10841
10877
  var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
10842
10878
  function redisEndpointsFromFile(file, serviceDir) {
@@ -10859,7 +10895,7 @@ function redisEndpointsFromFile(file, serviceDir) {
10859
10895
  // support tier (ADR-066).
10860
10896
  confidenceKind: "url-with-structural-support",
10861
10897
  evidence: {
10862
- file: path41.relative(serviceDir, file.path),
10898
+ file: path42.relative(serviceDir, file.path),
10863
10899
  line,
10864
10900
  snippet: snippet(file.content, line)
10865
10901
  }
@@ -10869,7 +10905,7 @@ function redisEndpointsFromFile(file, serviceDir) {
10869
10905
  }
10870
10906
 
10871
10907
  // src/extract/calls/aws.ts
10872
- import path42 from "path";
10908
+ import path43 from "path";
10873
10909
  import { infraId as infraId4 } from "@neat.is/types";
10874
10910
  var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
10875
10911
  var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
@@ -10903,7 +10939,7 @@ function awsEndpointsFromFile(file, serviceDir) {
10903
10939
  // (ADR-066).
10904
10940
  confidenceKind: "verified-call-site",
10905
10941
  evidence: {
10906
- file: path42.relative(serviceDir, file.path),
10942
+ file: path43.relative(serviceDir, file.path),
10907
10943
  line,
10908
10944
  snippet: snippet(file.content, line)
10909
10945
  }
@@ -10927,7 +10963,7 @@ function awsEndpointsFromFile(file, serviceDir) {
10927
10963
  }
10928
10964
 
10929
10965
  // src/extract/calls/grpc.ts
10930
- import path43 from "path";
10966
+ import path44 from "path";
10931
10967
  import { infraId as infraId5 } from "@neat.is/types";
10932
10968
  var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
10933
10969
  var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
@@ -10986,7 +11022,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
10986
11022
  // tier (ADR-066).
10987
11023
  confidenceKind: "verified-call-site",
10988
11024
  evidence: {
10989
- file: path43.relative(serviceDir, file.path),
11025
+ file: path44.relative(serviceDir, file.path),
10990
11026
  line,
10991
11027
  snippet: snippet(file.content, line)
10992
11028
  }
@@ -10996,7 +11032,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
10996
11032
  }
10997
11033
 
10998
11034
  // src/extract/calls/supabase.ts
10999
- import path44 from "path";
11035
+ import path45 from "path";
11000
11036
  import { infraId as infraId6 } from "@neat.is/types";
11001
11037
  var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
11002
11038
  var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
@@ -11055,7 +11091,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
11055
11091
  // tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
11056
11092
  confidenceKind: "verified-call-site",
11057
11093
  evidence: {
11058
- file: path44.relative(serviceDir, file.path),
11094
+ file: path45.relative(serviceDir, file.path),
11059
11095
  line,
11060
11096
  snippet: snippet(file.content, line)
11061
11097
  }
@@ -11082,7 +11118,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
11082
11118
  edgeType: "CALLS",
11083
11119
  confidenceKind: "verified-call-site",
11084
11120
  evidence: {
11085
- file: path44.relative(serviceDir, file.path),
11121
+ file: path45.relative(serviceDir, file.path),
11086
11122
  line,
11087
11123
  snippet: snippet(file.content, line)
11088
11124
  }
@@ -11093,7 +11129,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
11093
11129
  }
11094
11130
 
11095
11131
  // src/extract/calls/firestore.ts
11096
- import path45 from "path";
11132
+ import path46 from "path";
11097
11133
  import Parser8 from "tree-sitter";
11098
11134
  import JavaScript6 from "tree-sitter-javascript";
11099
11135
  import { infraId as infraId7 } from "@neat.is/types";
@@ -11266,7 +11302,7 @@ function firestoreEndpointsFromFile(file, serviceDir, imported) {
11266
11302
  if (!hasClient && !hasAdmin && importedNames.size === 0) return [];
11267
11303
  const ownSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
11268
11304
  const fileSdk = !hasClient && !hasAdmin ? imported?.sdk ?? null : ownSdk;
11269
- const tree = parseSource3(parserForExt2(path45.extname(file.path)), file.content);
11305
+ const tree = parseSource3(parserForExt2(path46.extname(file.path)), file.content);
11270
11306
  const clientVars = firestoreClientVars(tree.rootNode);
11271
11307
  for (const n of importedNames) clientVars.add(n);
11272
11308
  const collLine = /* @__PURE__ */ new Map();
@@ -11371,7 +11407,7 @@ function firestoreEndpointsFromFile(file, serviceDir, imported) {
11371
11407
  ...columnSet.size > 0 ? { columns: [...columnSet] } : {},
11372
11408
  ...sdkWrites ? { sdkWrites } : {},
11373
11409
  evidence: {
11374
- file: path45.relative(serviceDir, file.path),
11410
+ file: path46.relative(serviceDir, file.path),
11375
11411
  line,
11376
11412
  snippet: snippet(file.content, line)
11377
11413
  }
@@ -11431,7 +11467,7 @@ function sdkOf(content) {
11431
11467
  return hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
11432
11468
  }
11433
11469
  function parseOrNull(file) {
11434
- const ext = path45.extname(file.path);
11470
+ const ext = path46.extname(file.path);
11435
11471
  if (!(ext in GRAMMAR_BY_EXT)) return null;
11436
11472
  try {
11437
11473
  return parseSource3(parserForExt2(ext), file.content).rootNode;
@@ -11447,7 +11483,7 @@ async function firestoreCrossFileEndpoints(files, serviceDir) {
11447
11483
  if (!root) continue;
11448
11484
  const names = exportedFirestoreClients(root);
11449
11485
  if (names.size === 0) continue;
11450
- registry.set(toPosix(path45.relative(serviceDir, f.path)), { names, sdk: sdkOf(f.content) });
11486
+ registry.set(toPosix(path46.relative(serviceDir, f.path)), { names, sdk: sdkOf(f.content) });
11451
11487
  }
11452
11488
  if (registry.size === 0) return [];
11453
11489
  const out = [];
@@ -11460,7 +11496,7 @@ async function firestoreCrossFileEndpoints(files, serviceDir) {
11460
11496
  const names = /* @__PURE__ */ new Set();
11461
11497
  let sdk = null;
11462
11498
  for (const b of bindings) {
11463
- const rel = await resolveJsImport(b.specifier, path45.dirname(f.path), serviceDir, null);
11499
+ const rel = await resolveJsImport(b.specifier, path46.dirname(f.path), serviceDir, null);
11464
11500
  if (!rel) continue;
11465
11501
  const entry = registry.get(rel);
11466
11502
  if (!entry || !entry.names.has(b.imported)) continue;
@@ -11474,7 +11510,7 @@ async function firestoreCrossFileEndpoints(files, serviceDir) {
11474
11510
  }
11475
11511
 
11476
11512
  // src/extract/calls/mongoose.ts
11477
- import path46 from "path";
11513
+ import path47 from "path";
11478
11514
  import { infraId as infraId8 } from "@neat.is/types";
11479
11515
  var MONGOOSE_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongoose['"`]/;
11480
11516
  var MONGODB_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongodb['"`]/;
@@ -11625,7 +11661,7 @@ function endpoint(r, file, serviceDir, matchText) {
11625
11661
  kind: r.kind,
11626
11662
  edgeType: "CALLS",
11627
11663
  confidenceKind: "verified-call-site",
11628
- evidence: { file: path46.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
11664
+ evidence: { file: path47.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
11629
11665
  };
11630
11666
  }
11631
11667
  function mongooseEndpointsFromFile(file, serviceDir) {
@@ -11715,7 +11751,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
11715
11751
  const registry = /* @__PURE__ */ new Map();
11716
11752
  for (const f of mongooseFiles) {
11717
11753
  const fx = fileExportsOf(f.content, pluralizeOn);
11718
- if (fx) registry.set(toPosix(path46.relative(serviceDir, f.path)), fx);
11754
+ if (fx) registry.set(toPosix(path47.relative(serviceDir, f.path)), fx);
11719
11755
  }
11720
11756
  if (registry.size === 0) return [];
11721
11757
  const out = [];
@@ -11726,7 +11762,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
11726
11762
  const directColl = /* @__PURE__ */ new Map();
11727
11763
  const nsExports = /* @__PURE__ */ new Map();
11728
11764
  for (const b of bindings) {
11729
- const resolvedRel = await resolveJsImport(b.specifier, path46.dirname(f.path), serviceDir, null);
11765
+ const resolvedRel = await resolveJsImport(b.specifier, path47.dirname(f.path), serviceDir, null);
11730
11766
  if (!resolvedRel) continue;
11731
11767
  const fx = registry.get(resolvedRel);
11732
11768
  if (!fx) continue;
@@ -11769,7 +11805,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
11769
11805
  }
11770
11806
 
11771
11807
  // src/extract/calls/sqlalchemy.ts
11772
- import path47 from "path";
11808
+ import path48 from "path";
11773
11809
  import Parser9 from "tree-sitter";
11774
11810
  import Python5 from "tree-sitter-python";
11775
11811
  import { infraId as infraId9 } from "@neat.is/types";
@@ -11908,7 +11944,7 @@ function sqlalchemyForeignKeys(file, serviceDir) {
11908
11944
  childTable,
11909
11945
  parentTable,
11910
11946
  evidence: {
11911
- file: path47.relative(serviceDir, file.path),
11947
+ file: path48.relative(serviceDir, file.path),
11912
11948
  line,
11913
11949
  snippet: snippet(file.content, line)
11914
11950
  }
@@ -11933,7 +11969,7 @@ function sqlalchemyEndpointsFromFile(file, serviceDir) {
11933
11969
  confidenceKind: "verified-call-site",
11934
11970
  ...columns && columns.length > 0 ? { columns } : {},
11935
11971
  evidence: {
11936
- file: path47.relative(serviceDir, file.path),
11972
+ file: path48.relative(serviceDir, file.path),
11937
11973
  line,
11938
11974
  snippet: snippet(file.content, line)
11939
11975
  }
@@ -12040,7 +12076,7 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
12040
12076
  edgeType: "CALLS",
12041
12077
  confidenceKind: "verified-call-site",
12042
12078
  evidence: {
12043
- file: path47.relative(serviceDir, file.path),
12079
+ file: path48.relative(serviceDir, file.path),
12044
12080
  line,
12045
12081
  snippet: snippet(file.content, line)
12046
12082
  }
@@ -12051,7 +12087,7 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
12051
12087
  }
12052
12088
 
12053
12089
  // src/extract/calls/socket.ts
12054
- import path48 from "path";
12090
+ import path49 from "path";
12055
12091
  import Parser10 from "tree-sitter";
12056
12092
  import Python6 from "tree-sitter-python";
12057
12093
  import { infraId as infraId10 } from "@neat.is/types";
@@ -12127,7 +12163,7 @@ function socketEndpointsFromFile(file, serviceDir) {
12127
12163
  // A verified `socket.socket(...)` + `.connect((host, port))` call site.
12128
12164
  confidenceKind: "verified-call-site",
12129
12165
  evidence: {
12130
- file: path48.relative(serviceDir, file.path),
12166
+ file: path49.relative(serviceDir, file.path),
12131
12167
  line,
12132
12168
  snippet: snippet(file.content, line)
12133
12169
  }
@@ -12137,7 +12173,7 @@ function socketEndpointsFromFile(file, serviceDir) {
12137
12173
  }
12138
12174
 
12139
12175
  // src/extract/calls/django-orm.ts
12140
- import path49 from "path";
12176
+ import path50 from "path";
12141
12177
  import Parser11 from "tree-sitter";
12142
12178
  import Python7 from "tree-sitter-python";
12143
12179
  import { infraId as infraId11 } from "@neat.is/types";
@@ -12207,7 +12243,7 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
12207
12243
  const tree = parseSource8(makePyParser5(), file.content);
12208
12244
  const out = [];
12209
12245
  const seen = /* @__PURE__ */ new Set();
12210
- const defaultAppLabel = path49.basename(path49.dirname(file.path));
12246
+ const defaultAppLabel = path50.basename(path50.dirname(file.path));
12211
12247
  walk5(tree.rootNode, (node) => {
12212
12248
  if (node.type !== "class_definition") return;
12213
12249
  if (!extendsDjangoModel(node)) return;
@@ -12225,14 +12261,14 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
12225
12261
  kind: "sql-table",
12226
12262
  edgeType: "CALLS",
12227
12263
  confidenceKind: "verified-call-site",
12228
- evidence: { file: path49.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
12264
+ evidence: { file: path50.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
12229
12265
  });
12230
12266
  });
12231
12267
  return out;
12232
12268
  }
12233
12269
 
12234
12270
  // src/extract/calls/drizzle.ts
12235
- import path50 from "path";
12271
+ import path51 from "path";
12236
12272
  import Parser12 from "tree-sitter";
12237
12273
  import JavaScript7 from "tree-sitter-javascript";
12238
12274
  import { infraId as infraId12 } from "@neat.is/types";
@@ -12310,7 +12346,7 @@ function columnsFromObject(obj) {
12310
12346
  }
12311
12347
  function drizzleEndpointsFromFile(file, serviceDir) {
12312
12348
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
12313
- const tree = parseSource3(parserForExt3(path50.extname(file.path)), file.content);
12349
+ const tree = parseSource3(parserForExt3(path51.extname(file.path)), file.content);
12314
12350
  const out = [];
12315
12351
  const seen = /* @__PURE__ */ new Set();
12316
12352
  const walk11 = (node) => {
@@ -12333,7 +12369,7 @@ function drizzleEndpointsFromFile(file, serviceDir) {
12333
12369
  confidenceKind: "structural",
12334
12370
  columns,
12335
12371
  evidence: {
12336
- file: path50.relative(serviceDir, file.path),
12372
+ file: path51.relative(serviceDir, file.path),
12337
12373
  line,
12338
12374
  snippet: snippet(file.content, line)
12339
12375
  }
@@ -12399,7 +12435,7 @@ function referencesTargetVar(call) {
12399
12435
  }
12400
12436
  function drizzleForeignKeys(file, serviceDir) {
12401
12437
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
12402
- const tree = parseSource3(parserForExt3(path50.extname(file.path)), file.content);
12438
+ const tree = parseSource3(parserForExt3(path51.extname(file.path)), file.content);
12403
12439
  const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
12404
12440
  const out = [];
12405
12441
  const seen = /* @__PURE__ */ new Set();
@@ -12418,7 +12454,7 @@ function drizzleForeignKeys(file, serviceDir) {
12418
12454
  childTable: table.tableName,
12419
12455
  parentTable,
12420
12456
  evidence: {
12421
- file: path50.relative(serviceDir, file.path),
12457
+ file: path51.relative(serviceDir, file.path),
12422
12458
  line,
12423
12459
  snippet: snippet(file.content, line)
12424
12460
  }
@@ -12434,7 +12470,7 @@ function drizzleForeignKeys(file, serviceDir) {
12434
12470
  }
12435
12471
 
12436
12472
  // src/extract/calls/prisma.ts
12437
- import path51 from "path";
12473
+ import path52 from "path";
12438
12474
  import { infraId as infraId13 } from "@neat.is/types";
12439
12475
  var SCALAR_TYPES = /* @__PURE__ */ new Set([
12440
12476
  "Int",
@@ -12490,7 +12526,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
12490
12526
  confidenceKind: "structural",
12491
12527
  columns: b.columns,
12492
12528
  evidence: {
12493
- file: path51.relative(serviceDir, file.path),
12529
+ file: path52.relative(serviceDir, file.path),
12494
12530
  line: b.startLine,
12495
12531
  snippet: snippet(content, b.startLine)
12496
12532
  }
@@ -12551,7 +12587,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
12551
12587
  }
12552
12588
  async function prismaColumnEndpoints(serviceDir) {
12553
12589
  const schemaPath = await findFirst(serviceDir, [
12554
- path51.join("prisma", "schema.prisma"),
12590
+ path52.join("prisma", "schema.prisma"),
12555
12591
  "schema.prisma"
12556
12592
  ]);
12557
12593
  if (!schemaPath) return [];
@@ -12626,7 +12662,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
12626
12662
  childTable: current.table,
12627
12663
  parentTable,
12628
12664
  evidence: {
12629
- file: path51.relative(serviceDir, file.path),
12665
+ file: path52.relative(serviceDir, file.path),
12630
12666
  line: lineNo,
12631
12667
  snippet: snippet(content, lineNo)
12632
12668
  }
@@ -12641,7 +12677,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
12641
12677
  }
12642
12678
  async function prismaForeignKeys(serviceDir) {
12643
12679
  const schemaPath = await findFirst(serviceDir, [
12644
- path51.join("prisma", "schema.prisma"),
12680
+ path52.join("prisma", "schema.prisma"),
12645
12681
  "schema.prisma"
12646
12682
  ]);
12647
12683
  if (!schemaPath) return [];
@@ -12651,7 +12687,7 @@ async function prismaForeignKeys(serviceDir) {
12651
12687
  }
12652
12688
 
12653
12689
  // src/extract/calls/activerecord.ts
12654
- import path52 from "path";
12690
+ import path53 from "path";
12655
12691
  import Parser13 from "tree-sitter";
12656
12692
  import Ruby3 from "tree-sitter-ruby";
12657
12693
  import { infraId as infraId14 } from "@neat.is/types";
@@ -12859,7 +12895,7 @@ function railsSchemaEndpointsFromFile(file, serviceDir) {
12859
12895
  confidenceKind: "structural",
12860
12896
  ...table.columns.length > 0 ? { columns: table.columns } : {},
12861
12897
  evidence: {
12862
- file: path52.relative(serviceDir, file.path),
12898
+ file: path53.relative(serviceDir, file.path),
12863
12899
  line: table.line,
12864
12900
  snippet: snippet(file.content, table.line)
12865
12901
  }
@@ -12881,7 +12917,7 @@ function railsSchemaForeignKeys(file, serviceDir) {
12881
12917
  childTable,
12882
12918
  parentTable,
12883
12919
  evidence: {
12884
- file: path52.relative(serviceDir, file.path),
12920
+ file: path53.relative(serviceDir, file.path),
12885
12921
  line,
12886
12922
  snippet: snippet(file.content, line)
12887
12923
  }
@@ -12964,7 +13000,7 @@ function railsModelEndpointsFromFile(file, serviceDir) {
12964
13000
  edgeType: "CALLS",
12965
13001
  confidenceKind: "verified-call-site",
12966
13002
  evidence: {
12967
- file: path52.relative(serviceDir, file.path),
13003
+ file: path53.relative(serviceDir, file.path),
12968
13004
  line,
12969
13005
  snippet: snippet(file.content, line)
12970
13006
  }
@@ -13001,7 +13037,7 @@ function railsModelForeignKeys(file, serviceDir) {
13001
13037
  childTable,
13002
13038
  parentTable,
13003
13039
  evidence: {
13004
- file: path52.relative(serviceDir, file.path),
13040
+ file: path53.relative(serviceDir, file.path),
13005
13041
  line,
13006
13042
  snippet: snippet(file.content, line)
13007
13043
  }
@@ -13012,7 +13048,7 @@ function railsModelForeignKeys(file, serviceDir) {
13012
13048
  }
13013
13049
 
13014
13050
  // src/extract/calls/eloquent.ts
13015
- import path53 from "path";
13051
+ import path54 from "path";
13016
13052
  import Parser14 from "tree-sitter";
13017
13053
  import Php3 from "tree-sitter-php";
13018
13054
  import { infraId as infraId15 } from "@neat.is/types";
@@ -13269,7 +13305,7 @@ function laravelMigrationEndpointsFromFile(file, serviceDir) {
13269
13305
  confidenceKind: "structural",
13270
13306
  ...columns.length > 0 ? { columns } : {},
13271
13307
  evidence: {
13272
- file: path53.relative(serviceDir, file.path),
13308
+ file: path54.relative(serviceDir, file.path),
13273
13309
  line: bp.line,
13274
13310
  snippet: snippet(file.content, bp.line)
13275
13311
  }
@@ -13291,7 +13327,7 @@ function laravelMigrationForeignKeys(file, serviceDir) {
13291
13327
  childTable,
13292
13328
  parentTable,
13293
13329
  evidence: {
13294
- file: path53.relative(serviceDir, file.path),
13330
+ file: path54.relative(serviceDir, file.path),
13295
13331
  line,
13296
13332
  snippet: snippet(file.content, line)
13297
13333
  }
@@ -13411,7 +13447,7 @@ function laravelModelEndpointsFromFile(file, serviceDir) {
13411
13447
  edgeType: "CALLS",
13412
13448
  confidenceKind: "verified-call-site",
13413
13449
  evidence: {
13414
- file: path53.relative(serviceDir, file.path),
13450
+ file: path54.relative(serviceDir, file.path),
13415
13451
  line,
13416
13452
  snippet: snippet(file.content, line)
13417
13453
  }
@@ -13447,7 +13483,7 @@ function laravelModelForeignKeys(file, serviceDir) {
13447
13483
  childTable,
13448
13484
  parentTable,
13449
13485
  evidence: {
13450
- file: path53.relative(serviceDir, file.path),
13486
+ file: path54.relative(serviceDir, file.path),
13451
13487
  line,
13452
13488
  snippet: snippet(file.content, line)
13453
13489
  }
@@ -13458,7 +13494,7 @@ function laravelModelForeignKeys(file, serviceDir) {
13458
13494
  }
13459
13495
 
13460
13496
  // src/extract/calls/go.ts
13461
- import path54 from "path";
13497
+ import path55 from "path";
13462
13498
  import Parser15 from "tree-sitter";
13463
13499
  import Go4 from "tree-sitter-go";
13464
13500
  import { infraId as infraId16 } from "@neat.is/types";
@@ -13532,7 +13568,7 @@ function firstStringLiteralArg(argsNode) {
13532
13568
  return null;
13533
13569
  }
13534
13570
  function goSqlEndpointsFromFile(file, serviceDir) {
13535
- if (path54.extname(file.path) !== ".go") return [];
13571
+ if (path55.extname(file.path) !== ".go") return [];
13536
13572
  if (!file.content.includes(DATABASE_SQL_IMPORT) && !file.content.includes(SQLX_IMPORT)) return [];
13537
13573
  const tree = parseSource11(makeGoParser3(), file.content);
13538
13574
  const importsDatabaseSql = goImportsAny(tree.rootNode, /* @__PURE__ */ new Set([DATABASE_SQL_IMPORT]));
@@ -13561,7 +13597,7 @@ function goSqlEndpointsFromFile(file, serviceDir) {
13561
13597
  confidenceKind: "verified-call-site",
13562
13598
  ...columns.length > 0 ? { columns } : {},
13563
13599
  evidence: {
13564
- file: toPosix(path54.relative(serviceDir, file.path)),
13600
+ file: toPosix(path55.relative(serviceDir, file.path)),
13565
13601
  line,
13566
13602
  snippet: snippet(file.content, line)
13567
13603
  }
@@ -13571,7 +13607,7 @@ function goSqlEndpointsFromFile(file, serviceDir) {
13571
13607
  }
13572
13608
 
13573
13609
  // src/extract/calls/gorm.ts
13574
- import path55 from "path";
13610
+ import path56 from "path";
13575
13611
  import Parser16 from "tree-sitter";
13576
13612
  import Go5 from "tree-sitter-go";
13577
13613
  import { infraId as infraId17 } from "@neat.is/types";
@@ -14004,7 +14040,7 @@ function collectColumns(struct, structs, seen, prefix, out, emitted) {
14004
14040
  seen.delete(struct.name);
14005
14041
  }
14006
14042
  function gormEndpointsFromFile(file, serviceDir) {
14007
- if (path55.extname(file.path) !== ".go") return [];
14043
+ if (path56.extname(file.path) !== ".go") return [];
14008
14044
  if (!GORM_IMPORT_RE.test(file.content)) return [];
14009
14045
  const tree = parseSource12(makeGoParser4(), file.content);
14010
14046
  const { structs, models, tableFor } = analyze(tree);
@@ -14026,7 +14062,7 @@ function gormEndpointsFromFile(file, serviceDir) {
14026
14062
  confidenceKind: "structural",
14027
14063
  ...columns.length > 0 ? { columns } : {},
14028
14064
  evidence: {
14029
- file: toPosix(path55.relative(serviceDir, file.path)),
14065
+ file: toPosix(path56.relative(serviceDir, file.path)),
14030
14066
  line: struct.line,
14031
14067
  snippet: snippet(file.content, struct.line)
14032
14068
  }
@@ -14035,7 +14071,7 @@ function gormEndpointsFromFile(file, serviceDir) {
14035
14071
  return out;
14036
14072
  }
14037
14073
  function gormForeignKeys(file, serviceDir) {
14038
- if (path55.extname(file.path) !== ".go") return [];
14074
+ if (path56.extname(file.path) !== ".go") return [];
14039
14075
  if (!GORM_IMPORT_RE.test(file.content)) return [];
14040
14076
  const tree = parseSource12(makeGoParser4(), file.content);
14041
14077
  const { structs, models, tableFor } = analyze(tree);
@@ -14050,7 +14086,7 @@ function gormForeignKeys(file, serviceDir) {
14050
14086
  childTable,
14051
14087
  parentTable,
14052
14088
  evidence: {
14053
- file: toPosix(path55.relative(serviceDir, file.path)),
14089
+ file: toPosix(path56.relative(serviceDir, file.path)),
14054
14090
  line,
14055
14091
  snippet: snippet(file.content, line)
14056
14092
  }
@@ -14086,7 +14122,7 @@ function gormForeignKeys(file, serviceDir) {
14086
14122
  }
14087
14123
 
14088
14124
  // src/extract/calls/efcore.ts
14089
- import path56 from "path";
14125
+ import path57 from "path";
14090
14126
  import Parser17 from "tree-sitter";
14091
14127
  import CSharp2 from "tree-sitter-c-sharp";
14092
14128
  import { infraId as infraId18 } from "@neat.is/types";
@@ -14165,7 +14201,7 @@ function tableFromToTable(call) {
14165
14201
  return value ? csStringLiteral(value) : null;
14166
14202
  }
14167
14203
  function efcoreEndpointsFromFile(file, serviceDir) {
14168
- if (path56.extname(file.path) !== ".cs") return [];
14204
+ if (path57.extname(file.path) !== ".cs") return [];
14169
14205
  if (!EFCORE_GATE.test(file.content)) return [];
14170
14206
  const tree = parseSource13(makeCsParser(), file.content);
14171
14207
  const out = [];
@@ -14180,7 +14216,7 @@ function efcoreEndpointsFromFile(file, serviceDir) {
14180
14216
  edgeType: "CALLS",
14181
14217
  confidenceKind: "structural",
14182
14218
  evidence: {
14183
- file: toPosix(path56.relative(serviceDir, file.path)),
14219
+ file: toPosix(path57.relative(serviceDir, file.path)),
14184
14220
  line,
14185
14221
  snippet: snippet(file.content, line)
14186
14222
  }
@@ -14437,7 +14473,7 @@ function ensureTableNode(graph, id, name) {
14437
14473
  }
14438
14474
 
14439
14475
  // src/extract/infra/docker-compose.ts
14440
- import path57 from "path";
14476
+ import path58 from "path";
14441
14477
  import { EdgeType as EdgeType17, Provenance as Provenance18, confidenceForExtracted as confidenceForExtracted15 } from "@neat.is/types";
14442
14478
 
14443
14479
  // src/extract/infra/shared.ts
@@ -14507,7 +14543,7 @@ function dependsOnList(value) {
14507
14543
  }
14508
14544
  function serviceNameToServiceNode(name, services) {
14509
14545
  for (const s of services) {
14510
- if (s.node.name === name || path57.basename(s.dir) === name) return s.node.id;
14546
+ if (s.node.name === name || path58.basename(s.dir) === name) return s.node.id;
14511
14547
  }
14512
14548
  return null;
14513
14549
  }
@@ -14516,7 +14552,7 @@ async function addComposeInfra(graph, scanPath, services) {
14516
14552
  let edgesAdded = 0;
14517
14553
  let composePath = null;
14518
14554
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
14519
- const abs = path57.join(scanPath, name);
14555
+ const abs = path58.join(scanPath, name);
14520
14556
  if (await exists(abs)) {
14521
14557
  composePath = abs;
14522
14558
  break;
@@ -14529,13 +14565,13 @@ async function addComposeInfra(graph, scanPath, services) {
14529
14565
  } catch (err) {
14530
14566
  recordExtractionError(
14531
14567
  "infra docker-compose",
14532
- path57.relative(scanPath, composePath),
14568
+ path58.relative(scanPath, composePath),
14533
14569
  err
14534
14570
  );
14535
14571
  return { nodesAdded, edgesAdded };
14536
14572
  }
14537
14573
  if (!compose?.services) return { nodesAdded, edgesAdded };
14538
- const evidenceFile = path57.relative(scanPath, composePath).split(path57.sep).join("/");
14574
+ const evidenceFile = path58.relative(scanPath, composePath).split(path58.sep).join("/");
14539
14575
  const composeNameToNodeId = /* @__PURE__ */ new Map();
14540
14576
  for (const [composeName, svc] of Object.entries(compose.services)) {
14541
14577
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -14576,8 +14612,8 @@ async function addComposeInfra(graph, scanPath, services) {
14576
14612
  }
14577
14613
 
14578
14614
  // src/extract/infra/dockerfile.ts
14579
- import path58 from "path";
14580
- import { promises as fs26 } from "fs";
14615
+ import path59 from "path";
14616
+ import { promises as fs27 } from "fs";
14581
14617
  import { EdgeType as EdgeType18, Provenance as Provenance19, confidenceForExtracted as confidenceForExtracted16 } from "@neat.is/types";
14582
14618
  function readDockerfile(content) {
14583
14619
  let image = null;
@@ -14607,15 +14643,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
14607
14643
  let nodesAdded = 0;
14608
14644
  let edgesAdded = 0;
14609
14645
  for (const service of services) {
14610
- const dockerfilePath = path58.join(service.dir, "Dockerfile");
14646
+ const dockerfilePath = path59.join(service.dir, "Dockerfile");
14611
14647
  if (!await exists(dockerfilePath)) continue;
14612
14648
  let content;
14613
14649
  try {
14614
- content = await fs26.readFile(dockerfilePath, "utf8");
14650
+ content = await fs27.readFile(dockerfilePath, "utf8");
14615
14651
  } catch (err) {
14616
14652
  recordExtractionError(
14617
14653
  "infra dockerfile",
14618
- path58.relative(scanPath, dockerfilePath),
14654
+ path59.relative(scanPath, dockerfilePath),
14619
14655
  err
14620
14656
  );
14621
14657
  continue;
@@ -14627,8 +14663,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
14627
14663
  graph.addNode(node.id, node);
14628
14664
  nodesAdded++;
14629
14665
  }
14630
- const relDockerfile = toPosix(path58.relative(service.dir, dockerfilePath));
14631
- const evidenceFile = toPosix(path58.relative(scanPath, dockerfilePath));
14666
+ const relDockerfile = toPosix(path59.relative(service.dir, dockerfilePath));
14667
+ const evidenceFile = toPosix(path59.relative(scanPath, dockerfilePath));
14632
14668
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
14633
14669
  graph,
14634
14670
  service.pkg.name,
@@ -14679,23 +14715,23 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
14679
14715
  }
14680
14716
 
14681
14717
  // src/extract/infra/terraform.ts
14682
- import { promises as fs27 } from "fs";
14683
- import path59 from "path";
14718
+ import { promises as fs28 } from "fs";
14719
+ import path60 from "path";
14684
14720
  import { EdgeType as EdgeType19, Provenance as Provenance20, confidenceForExtracted as confidenceForExtracted17 } from "@neat.is/types";
14685
14721
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
14686
14722
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
14687
14723
  async function walkTfFiles(start, depth = 0, max = 5) {
14688
14724
  if (depth > max) return [];
14689
14725
  const out = [];
14690
- const entries = await fs27.readdir(start, { withFileTypes: true }).catch(() => []);
14726
+ const entries = await fs28.readdir(start, { withFileTypes: true }).catch(() => []);
14691
14727
  for (const entry of entries) {
14692
14728
  if (entry.isDirectory()) {
14693
14729
  if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
14694
- const child = path59.join(start, entry.name);
14730
+ const child = path60.join(start, entry.name);
14695
14731
  if (await isPythonVenvDir(child)) continue;
14696
14732
  out.push(...await walkTfFiles(child, depth + 1, max));
14697
14733
  } else if (entry.isFile() && entry.name.endsWith(".tf")) {
14698
- out.push(path59.join(start, entry.name));
14734
+ out.push(path60.join(start, entry.name));
14699
14735
  }
14700
14736
  }
14701
14737
  return out;
@@ -14726,8 +14762,8 @@ async function addTerraformResources(graph, scanPath) {
14726
14762
  let edgesAdded = 0;
14727
14763
  const files = await walkTfFiles(scanPath);
14728
14764
  for (const file of files) {
14729
- const content = await fs27.readFile(file, "utf8");
14730
- const evidenceFile = toPosix(path59.relative(scanPath, file));
14765
+ const content = await fs28.readFile(file, "utf8");
14766
+ const evidenceFile = toPosix(path60.relative(scanPath, file));
14731
14767
  const resources = [];
14732
14768
  const byKey = /* @__PURE__ */ new Map();
14733
14769
  RESOURCE_RE.lastIndex = 0;
@@ -14783,8 +14819,8 @@ async function addTerraformResources(graph, scanPath) {
14783
14819
  }
14784
14820
 
14785
14821
  // src/extract/infra/k8s.ts
14786
- import { promises as fs28 } from "fs";
14787
- import path60 from "path";
14822
+ import { promises as fs29 } from "fs";
14823
+ import path61 from "path";
14788
14824
  import { parseAllDocuments as parseAllDocuments2 } from "yaml";
14789
14825
  import { EdgeType as EdgeType20, serviceId as serviceId13 } from "@neat.is/types";
14790
14826
  var K8S_KIND_TO_INFRA_KIND = {
@@ -14800,15 +14836,15 @@ var WORKLOAD_KINDS = /* @__PURE__ */ new Set(["Deployment", "StatefulSet", "Daem
14800
14836
  async function walkYamlFiles2(start, depth = 0, max = 5) {
14801
14837
  if (depth > max) return [];
14802
14838
  const out = [];
14803
- const entries = await fs28.readdir(start, { withFileTypes: true }).catch(() => []);
14839
+ const entries = await fs29.readdir(start, { withFileTypes: true }).catch(() => []);
14804
14840
  for (const entry of entries) {
14805
14841
  if (entry.isDirectory()) {
14806
14842
  if (IGNORED_DIRS.has(entry.name)) continue;
14807
- const child = path60.join(start, entry.name);
14843
+ const child = path61.join(start, entry.name);
14808
14844
  if (await isPythonVenvDir(child)) continue;
14809
14845
  out.push(...await walkYamlFiles2(child, depth + 1, max));
14810
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path60.extname(entry.name))) {
14811
- out.push(path60.join(start, entry.name));
14846
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path61.extname(entry.name))) {
14847
+ out.push(path61.join(start, entry.name));
14812
14848
  }
14813
14849
  }
14814
14850
  return out;
@@ -14818,14 +14854,14 @@ async function addK8sResources(graph, scanPath) {
14818
14854
  let edgesAdded = 0;
14819
14855
  const files = await walkYamlFiles2(scanPath);
14820
14856
  for (const file of files) {
14821
- const content = await fs28.readFile(file, "utf8");
14857
+ const content = await fs29.readFile(file, "utf8");
14822
14858
  let docs;
14823
14859
  try {
14824
14860
  docs = parseAllDocuments2(content).map((d) => d.toJSON());
14825
14861
  } catch {
14826
14862
  continue;
14827
14863
  }
14828
- const relFile = toPosix(path60.relative(scanPath, file));
14864
+ const relFile = toPosix(path61.relative(scanPath, file));
14829
14865
  for (const doc of docs) {
14830
14866
  if (!doc?.kind || !doc.metadata?.name) continue;
14831
14867
  const infraKind = K8S_KIND_TO_INFRA_KIND[doc.kind];
@@ -14873,16 +14909,16 @@ async function addK8sResources(graph, scanPath) {
14873
14909
  }
14874
14910
 
14875
14911
  // src/extract/infra/cloudflare.ts
14876
- import { promises as fs29 } from "fs";
14877
- import path61 from "path";
14912
+ import { promises as fs30 } from "fs";
14913
+ import path62 from "path";
14878
14914
  import { parse as parseToml3 } from "smol-toml";
14879
14915
  import { EdgeType as EdgeType21, Provenance as Provenance21, confidenceForExtracted as confidenceForExtracted18 } from "@neat.is/types";
14880
14916
  var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
14881
14917
  async function readWranglerConfig(dir) {
14882
14918
  for (const filename of WRANGLER_FILENAMES) {
14883
- const abs = path61.join(dir, filename);
14919
+ const abs = path62.join(dir, filename);
14884
14920
  if (!await exists(abs)) continue;
14885
- const raw = await fs29.readFile(abs, "utf8");
14921
+ const raw = await fs30.readFile(abs, "utf8");
14886
14922
  const config = filename === "wrangler.toml" ? parseToml3(raw) : JSON.parse(maskCommentsInSource(raw));
14887
14923
  return { config, relFile: filename, raw };
14888
14924
  }
@@ -14943,11 +14979,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
14943
14979
  try {
14944
14980
  read = await readWranglerConfig(service.dir);
14945
14981
  } catch (err) {
14946
- recordExtractionError("infra cloudflare", path61.relative(scanPath, service.dir), err);
14982
+ recordExtractionError("infra cloudflare", path62.relative(scanPath, service.dir), err);
14947
14983
  continue;
14948
14984
  }
14949
14985
  if (!read || !read.config.name) continue;
14950
- const evidenceFile = toPosix(path61.relative(scanPath, path61.join(service.dir, read.relFile)));
14986
+ const evidenceFile = toPosix(path62.relative(scanPath, path62.join(service.dir, read.relFile)));
14951
14987
  discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
14952
14988
  }
14953
14989
  for (const worker of discovered) {
@@ -14959,7 +14995,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
14959
14995
  }
14960
14996
  let anchorId = service.node.id;
14961
14997
  if (config.main) {
14962
- const entryRelPath = toPosix(path61.normalize(config.main));
14998
+ const entryRelPath = toPosix(path62.normalize(config.main));
14963
14999
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
14964
15000
  graph,
14965
15001
  service.pkg.name,
@@ -15105,24 +15141,24 @@ async function addCloudflareWorkers(graph, services, scanPath) {
15105
15141
  }
15106
15142
 
15107
15143
  // src/extract/infra/vercel.ts
15108
- import { promises as fs30 } from "fs";
15109
- import path62 from "path";
15144
+ import { promises as fs31 } from "fs";
15145
+ import path63 from "path";
15110
15146
  import { EdgeType as EdgeType22 } from "@neat.is/types";
15111
15147
  var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
15112
15148
  async function readVercelConfig(dir) {
15113
15149
  for (const filename of VERCEL_CONFIG_FILENAMES) {
15114
- const abs = path62.join(dir, filename);
15150
+ const abs = path63.join(dir, filename);
15115
15151
  if (!await exists(abs)) continue;
15116
- const raw = await fs30.readFile(abs, "utf8");
15152
+ const raw = await fs31.readFile(abs, "utf8");
15117
15153
  const config = JSON.parse(maskCommentsInSource(raw));
15118
15154
  return { config, relFile: filename, raw };
15119
15155
  }
15120
15156
  return null;
15121
15157
  }
15122
15158
  async function readLinkedProjectName(dir) {
15123
- const abs = path62.join(dir, ".vercel", "project.json");
15159
+ const abs = path63.join(dir, ".vercel", "project.json");
15124
15160
  if (!await exists(abs)) return void 0;
15125
- const parsed = JSON.parse(await fs30.readFile(abs, "utf8"));
15161
+ const parsed = JSON.parse(await fs31.readFile(abs, "utf8"));
15126
15162
  return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
15127
15163
  }
15128
15164
  function routeSource(route) {
@@ -15138,7 +15174,7 @@ async function addVercelServices(graph, services, scanPath) {
15138
15174
  read = await readVercelConfig(service.dir);
15139
15175
  projectName = await readLinkedProjectName(service.dir);
15140
15176
  } catch (err) {
15141
- recordExtractionError("infra vercel", path62.relative(scanPath, service.dir), err);
15177
+ recordExtractionError("infra vercel", path63.relative(scanPath, service.dir), err);
15142
15178
  continue;
15143
15179
  }
15144
15180
  if (!read && !projectName) continue;
@@ -15154,7 +15190,7 @@ async function addVercelServices(graph, services, scanPath) {
15154
15190
  const anchorId = service.node.id;
15155
15191
  if (!read) continue;
15156
15192
  const { config, relFile, raw } = read;
15157
- const evidenceFile = toPosix(path62.relative(scanPath, path62.join(service.dir, relFile)));
15193
+ const evidenceFile = toPosix(path63.relative(scanPath, path63.join(service.dir, relFile)));
15158
15194
  const add = (edgeType, kind, name) => {
15159
15195
  if (!name) return;
15160
15196
  const result = emitPlatformResourceEdge(
@@ -15182,16 +15218,16 @@ async function addVercelServices(graph, services, scanPath) {
15182
15218
  }
15183
15219
 
15184
15220
  // src/extract/infra/railway.ts
15185
- import { promises as fs31 } from "fs";
15186
- import path63 from "path";
15221
+ import { promises as fs32 } from "fs";
15222
+ import path64 from "path";
15187
15223
  import { parse as parseToml4 } from "smol-toml";
15188
15224
  import { EdgeType as EdgeType23 } from "@neat.is/types";
15189
15225
  var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
15190
15226
  async function readRailwayConfig(dir) {
15191
15227
  for (const filename of RAILWAY_FILENAMES) {
15192
- const abs = path63.join(dir, filename);
15228
+ const abs = path64.join(dir, filename);
15193
15229
  if (!await exists(abs)) continue;
15194
- const raw = await fs31.readFile(abs, "utf8");
15230
+ const raw = await fs32.readFile(abs, "utf8");
15195
15231
  const config = filename === "railway.toml" ? parseToml4(raw) : JSON.parse(maskCommentsInSource(raw));
15196
15232
  return { config, relFile: filename, raw };
15197
15233
  }
@@ -15205,7 +15241,7 @@ async function addRailwayServices(graph, services, scanPath) {
15205
15241
  try {
15206
15242
  read = await readRailwayConfig(service.dir);
15207
15243
  } catch (err) {
15208
- recordExtractionError("infra railway", path63.relative(scanPath, service.dir), err);
15244
+ recordExtractionError("infra railway", path64.relative(scanPath, service.dir), err);
15209
15245
  continue;
15210
15246
  }
15211
15247
  if (!read) continue;
@@ -15215,7 +15251,7 @@ async function addRailwayServices(graph, services, scanPath) {
15215
15251
  }
15216
15252
  const anchorId = service.node.id;
15217
15253
  const { config, relFile, raw } = read;
15218
- const evidenceFile = toPosix(path63.relative(scanPath, path63.join(service.dir, relFile)));
15254
+ const evidenceFile = toPosix(path64.relative(scanPath, path64.join(service.dir, relFile)));
15219
15255
  const add = (edgeType, kind, name) => {
15220
15256
  if (!name) return;
15221
15257
  const result = emitPlatformResourceEdge(
@@ -15239,15 +15275,15 @@ async function addRailwayServices(graph, services, scanPath) {
15239
15275
  }
15240
15276
 
15241
15277
  // src/extract/infra/supabase.ts
15242
- import { promises as fs32 } from "fs";
15243
- import path64 from "path";
15278
+ import { promises as fs33 } from "fs";
15279
+ import path65 from "path";
15244
15280
  import { parse as parseToml5 } from "smol-toml";
15245
15281
  import { EdgeType as EdgeType24 } from "@neat.is/types";
15246
15282
  async function readSupabaseConfig(dir) {
15247
- const relFile = path64.join("supabase", "config.toml");
15248
- const abs = path64.join(dir, relFile);
15283
+ const relFile = path65.join("supabase", "config.toml");
15284
+ const abs = path65.join(dir, relFile);
15249
15285
  if (!await exists(abs)) return null;
15250
- const raw = await fs32.readFile(abs, "utf8");
15286
+ const raw = await fs33.readFile(abs, "utf8");
15251
15287
  const config = parseToml5(raw);
15252
15288
  return { config, relFile, raw };
15253
15289
  }
@@ -15259,7 +15295,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
15259
15295
  try {
15260
15296
  read = await readSupabaseConfig(service.dir);
15261
15297
  } catch (err) {
15262
- recordExtractionError("infra supabase", path64.relative(scanPath, service.dir), err);
15298
+ recordExtractionError("infra supabase", path65.relative(scanPath, service.dir), err);
15263
15299
  continue;
15264
15300
  }
15265
15301
  if (!read) continue;
@@ -15274,7 +15310,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
15274
15310
  });
15275
15311
  }
15276
15312
  const anchorId = service.node.id;
15277
- const evidenceFile = toPosix(path64.relative(scanPath, path64.join(service.dir, relFile)));
15313
+ const evidenceFile = toPosix(path65.relative(scanPath, path65.join(service.dir, relFile)));
15278
15314
  const add = (edgeType, kind, name) => {
15279
15315
  if (!name) return;
15280
15316
  const result = emitPlatformResourceEdge(
@@ -15315,7 +15351,7 @@ async function addInfra(graph, scanPath, services) {
15315
15351
  }
15316
15352
 
15317
15353
  // src/extract/zod-shapes.ts
15318
- import path65 from "path";
15354
+ import path66 from "path";
15319
15355
  import Parser18 from "tree-sitter";
15320
15356
  import JavaScript8 from "tree-sitter-javascript";
15321
15357
  import {
@@ -15417,7 +15453,7 @@ function topLevelSchemas(root) {
15417
15453
  }
15418
15454
  function zodShapesFromFile(file, serviceDir) {
15419
15455
  if (!ZOD_IMPORT_RE.test(file.content)) return [];
15420
- const tree = parseSource3(parserForExt4(path65.extname(file.path)), file.content);
15456
+ const tree = parseSource3(parserForExt4(path66.extname(file.path)), file.content);
15421
15457
  const out = [];
15422
15458
  const seen = /* @__PURE__ */ new Set();
15423
15459
  for (const { name, call } of topLevelSchemas(tree.rootNode)) {
@@ -15435,7 +15471,7 @@ function zodShapesFromFile(file, serviceDir) {
15435
15471
  name,
15436
15472
  fields,
15437
15473
  evidence: {
15438
- file: path65.relative(serviceDir, file.path),
15474
+ file: path66.relative(serviceDir, file.path),
15439
15475
  line,
15440
15476
  snippet: snippet(file.content, line)
15441
15477
  }
@@ -15702,11 +15738,11 @@ async function addFirestoreRules(graph, services) {
15702
15738
  }
15703
15739
 
15704
15740
  // src/extract/index.ts
15705
- import path67 from "path";
15741
+ import path68 from "path";
15706
15742
 
15707
15743
  // src/extract/retire.ts
15708
15744
  import { existsSync as existsSync2 } from "fs";
15709
- import path66 from "path";
15745
+ import path67 from "path";
15710
15746
  import { NodeType as NodeType29, Provenance as Provenance23 } from "@neat.is/types";
15711
15747
  function dropOrphanedFileNodes(graph, source) {
15712
15748
  const orphans = [];
@@ -15753,11 +15789,11 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = [], so
15753
15789
  if (outOfScope(graph, edge, source)) return;
15754
15790
  const evidenceFile = edge.evidence?.file;
15755
15791
  if (!evidenceFile) return;
15756
- if (path66.isAbsolute(evidenceFile)) {
15792
+ if (path67.isAbsolute(evidenceFile)) {
15757
15793
  if (!existsSync2(evidenceFile)) toDrop.push(id);
15758
15794
  return;
15759
15795
  }
15760
- const found = bases.some((base) => existsSync2(path66.join(base, evidenceFile)));
15796
+ const found = bases.some((base) => existsSync2(path67.join(base, evidenceFile)));
15761
15797
  if (!found) toDrop.push(id);
15762
15798
  });
15763
15799
  for (const id of toDrop) graph.dropEdge(id);
@@ -15766,7 +15802,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = [], so
15766
15802
  }
15767
15803
 
15768
15804
  // src/extract/index.ts
15769
- async function extractFromDirectory(graph, scanPath, opts = {}) {
15805
+ var passQueue = Promise.resolve();
15806
+ function extractFromDirectory(graph, scanPath, opts = {}) {
15807
+ const pass = passQueue.then(() => runExtractionPass(graph, scanPath, opts));
15808
+ passQueue = pass.catch(() => {
15809
+ });
15810
+ return pass;
15811
+ }
15812
+ async function runExtractionPass(graph, scanPath, opts) {
15770
15813
  await ensureCompatLoaded();
15771
15814
  drainExtractionErrors();
15772
15815
  drainSkippedFiles();
@@ -15818,7 +15861,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
15818
15861
  const droppedEntries = drainDroppedExtracted();
15819
15862
  const skippedEntries = drainSkippedFiles();
15820
15863
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
15821
- const rejectedPath = path67.join(path67.dirname(opts.errorsPath), "rejected.ndjson");
15864
+ const rejectedPath = path68.join(path68.dirname(opts.errorsPath), "rejected.ndjson");
15822
15865
  try {
15823
15866
  await writeRejectedExtracted(droppedEntries, rejectedPath);
15824
15867
  } catch (err) {
@@ -15840,7 +15883,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
15840
15883
  skippedFiles: skippedEntries.length,
15841
15884
  skippedEntries
15842
15885
  };
15843
- emitNeatEvent({
15886
+ if (opts.announce !== false) emitNeatEvent({
15844
15887
  type: "extraction-complete",
15845
15888
  project: opts.project ?? DEFAULT_PROJECT,
15846
15889
  payload: {
@@ -16540,8 +16583,8 @@ function computeDivergences(graph, opts = {}) {
16540
16583
  }
16541
16584
 
16542
16585
  // src/persist.ts
16543
- import { promises as fs33 } from "fs";
16544
- import path68 from "path";
16586
+ import { promises as fs34 } from "fs";
16587
+ import path69 from "path";
16545
16588
  import { NodeType as NodeType31, Provenance as Provenance25, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
16546
16589
  var SCHEMA_VERSION = 7;
16547
16590
  function migrateV1ToV2(payload) {
@@ -16596,7 +16639,7 @@ function migrateV2ToV3(payload) {
16596
16639
  return { ...payload, schemaVersion: 3 };
16597
16640
  }
16598
16641
  async function ensureDir(filePath) {
16599
- await fs33.mkdir(path68.dirname(filePath), { recursive: true });
16642
+ await fs34.mkdir(path69.dirname(filePath), { recursive: true });
16600
16643
  }
16601
16644
  var WRITE_FLUSH_CHARS = 4 * 1024 * 1024;
16602
16645
  function jsonOrUndefined(value) {
@@ -16642,7 +16685,7 @@ async function saveGraphToDisk(graph, outPath) {
16642
16685
  graph: graph.export()
16643
16686
  };
16644
16687
  const tmp = `${outPath}.tmp`;
16645
- const handle = await fs33.open(tmp, "w");
16688
+ const handle = await fs34.open(tmp, "w");
16646
16689
  try {
16647
16690
  let buffered = "";
16648
16691
  for (const chunk of serializeGraphChunks(payload)) {
@@ -16656,7 +16699,7 @@ async function saveGraphToDisk(graph, outPath) {
16656
16699
  } finally {
16657
16700
  await handle.close();
16658
16701
  }
16659
- await fs33.rename(tmp, outPath);
16702
+ await fs34.rename(tmp, outPath);
16660
16703
  }
16661
16704
  function migrateSnapshot(input) {
16662
16705
  let payload = input;
@@ -16688,7 +16731,7 @@ function migrateSnapshot(input) {
16688
16731
  async function loadGraphFromDisk(graph, outPath) {
16689
16732
  let raw;
16690
16733
  try {
16691
- raw = await fs33.readFile(outPath, "utf8");
16734
+ raw = await fs34.readFile(outPath, "utf8");
16692
16735
  } catch (err) {
16693
16736
  if (err.code === "ENOENT") return;
16694
16737
  throw err;
@@ -16738,7 +16781,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
16738
16781
  }
16739
16782
 
16740
16783
  // src/diff.ts
16741
- import { promises as fs34 } from "fs";
16784
+ import { promises as fs35 } from "fs";
16742
16785
  async function loadSnapshotForDiff(target) {
16743
16786
  if (/^https?:\/\//i.test(target)) {
16744
16787
  const res = await fetch(target);
@@ -16747,7 +16790,7 @@ async function loadSnapshotForDiff(target) {
16747
16790
  }
16748
16791
  return await res.json();
16749
16792
  }
16750
- const raw = await fs34.readFile(target, "utf8");
16793
+ const raw = await fs35.readFile(target, "utf8");
16751
16794
  return JSON.parse(raw);
16752
16795
  }
16753
16796
  function indexEntries(entries) {
@@ -16814,23 +16857,23 @@ function canonicalJson(value) {
16814
16857
  }
16815
16858
 
16816
16859
  // src/projects.ts
16817
- import path69 from "path";
16860
+ import path70 from "path";
16818
16861
  function pathsForProject(project, baseDir) {
16819
16862
  if (project === DEFAULT_PROJECT) {
16820
16863
  return {
16821
- snapshotPath: path69.join(baseDir, "graph.json"),
16822
- errorsPath: path69.join(baseDir, "errors.ndjson"),
16823
- staleEventsPath: path69.join(baseDir, "stale-events.ndjson"),
16824
- embeddingsCachePath: path69.join(baseDir, "embeddings.json"),
16825
- policyViolationsPath: path69.join(baseDir, "policy-violations.ndjson")
16864
+ snapshotPath: path70.join(baseDir, "graph.json"),
16865
+ errorsPath: path70.join(baseDir, "errors.ndjson"),
16866
+ staleEventsPath: path70.join(baseDir, "stale-events.ndjson"),
16867
+ embeddingsCachePath: path70.join(baseDir, "embeddings.json"),
16868
+ policyViolationsPath: path70.join(baseDir, "policy-violations.ndjson")
16826
16869
  };
16827
16870
  }
16828
16871
  return {
16829
- snapshotPath: path69.join(baseDir, `${project}.json`),
16830
- errorsPath: path69.join(baseDir, `errors.${project}.ndjson`),
16831
- staleEventsPath: path69.join(baseDir, `stale-events.${project}.ndjson`),
16832
- embeddingsCachePath: path69.join(baseDir, `embeddings.${project}.json`),
16833
- policyViolationsPath: path69.join(baseDir, `policy-violations.${project}.ndjson`)
16872
+ snapshotPath: path70.join(baseDir, `${project}.json`),
16873
+ errorsPath: path70.join(baseDir, `errors.${project}.ndjson`),
16874
+ staleEventsPath: path70.join(baseDir, `stale-events.${project}.ndjson`),
16875
+ embeddingsCachePath: path70.join(baseDir, `embeddings.${project}.json`),
16876
+ policyViolationsPath: path70.join(baseDir, `policy-violations.${project}.ndjson`)
16834
16877
  };
16835
16878
  }
16836
16879
  var Projects = class {
@@ -16869,9 +16912,9 @@ function parseExtraProjects(raw) {
16869
16912
  }
16870
16913
 
16871
16914
  // src/registry.ts
16872
- import { promises as fs35 } from "fs";
16915
+ import { promises as fs36 } from "fs";
16873
16916
  import os2 from "os";
16874
- import path70 from "path";
16917
+ import path71 from "path";
16875
16918
  import {
16876
16919
  RegistryFileSchema
16877
16920
  } from "@neat.is/types";
@@ -16879,20 +16922,20 @@ var LOCK_TIMEOUT_MS = 5e3;
16879
16922
  var LOCK_RETRY_MS = 50;
16880
16923
  function neatHome() {
16881
16924
  const override = process.env.NEAT_HOME;
16882
- if (override && override.length > 0) return path70.resolve(override);
16883
- return path70.join(os2.homedir(), ".neat");
16925
+ if (override && override.length > 0) return path71.resolve(override);
16926
+ return path71.join(os2.homedir(), ".neat");
16884
16927
  }
16885
16928
  function registryPath() {
16886
- return path70.join(neatHome(), "projects.json");
16929
+ return path71.join(neatHome(), "projects.json");
16887
16930
  }
16888
16931
  function registryLockPath() {
16889
- return path70.join(neatHome(), "projects.json.lock");
16932
+ return path71.join(neatHome(), "projects.json.lock");
16890
16933
  }
16891
16934
  function daemonPidPath() {
16892
- return path70.join(neatHome(), "neatd.pid");
16935
+ return path71.join(neatHome(), "neatd.pid");
16893
16936
  }
16894
16937
  function daemonsDir() {
16895
- return path70.join(neatHome(), "daemons");
16938
+ return path71.join(neatHome(), "daemons");
16896
16939
  }
16897
16940
  function isFiniteInt(v) {
16898
16941
  return typeof v === "number" && Number.isFinite(v);
@@ -16925,7 +16968,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
16925
16968
  const dir = daemonsDir();
16926
16969
  let names;
16927
16970
  try {
16928
- names = await fs35.readdir(dir);
16971
+ names = await fs36.readdir(dir);
16929
16972
  } catch (err) {
16930
16973
  if (err.code === "ENOENT") return [];
16931
16974
  throw err;
@@ -16933,10 +16976,10 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
16933
16976
  const out = [];
16934
16977
  for (const name of names) {
16935
16978
  if (!name.endsWith(".json")) continue;
16936
- const file = path70.join(dir, name);
16979
+ const file = path71.join(dir, name);
16937
16980
  let raw;
16938
16981
  try {
16939
- raw = await fs35.readFile(file, "utf8");
16982
+ raw = await fs36.readFile(file, "utf8");
16940
16983
  } catch {
16941
16984
  continue;
16942
16985
  }
@@ -16949,7 +16992,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
16949
16992
  return out;
16950
16993
  }
16951
16994
  async function removeDaemonRecord(source) {
16952
- await fs35.unlink(source).catch(() => {
16995
+ await fs36.unlink(source).catch(() => {
16953
16996
  });
16954
16997
  }
16955
16998
  async function listMachineProjects(probe = defaultDiscoveryProbe) {
@@ -17006,7 +17049,7 @@ function isPidAliveDefault(pid) {
17006
17049
  }
17007
17050
  async function readPidFile(file) {
17008
17051
  try {
17009
- const raw = await fs35.readFile(file, "utf8");
17052
+ const raw = await fs36.readFile(file, "utf8");
17010
17053
  const pid = Number.parseInt(raw.trim(), 10);
17011
17054
  return Number.isInteger(pid) && pid > 0 ? pid : void 0;
17012
17055
  } catch {
@@ -17052,32 +17095,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
17052
17095
  }
17053
17096
  }
17054
17097
  async function normalizeProjectPath(input) {
17055
- const resolved = path70.resolve(input);
17098
+ const resolved = path71.resolve(input);
17056
17099
  try {
17057
- return await fs35.realpath(resolved);
17100
+ return await fs36.realpath(resolved);
17058
17101
  } catch {
17059
17102
  return resolved;
17060
17103
  }
17061
17104
  }
17062
17105
  async function writeAtomically(target, contents) {
17063
- await fs35.mkdir(path70.dirname(target), { recursive: true });
17106
+ await fs36.mkdir(path71.dirname(target), { recursive: true });
17064
17107
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
17065
- const fd = await fs35.open(tmp, "w");
17108
+ const fd = await fs36.open(tmp, "w");
17066
17109
  try {
17067
17110
  await fd.writeFile(contents, "utf8");
17068
17111
  await fd.sync();
17069
17112
  } finally {
17070
17113
  await fd.close();
17071
17114
  }
17072
- await fs35.rename(tmp, target);
17115
+ await fs36.rename(tmp, target);
17073
17116
  }
17074
17117
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
17075
17118
  const deadline = Date.now() + timeoutMs;
17076
- await fs35.mkdir(path70.dirname(lockPath), { recursive: true });
17119
+ await fs36.mkdir(path71.dirname(lockPath), { recursive: true });
17077
17120
  let probedHolder = false;
17078
17121
  while (true) {
17079
17122
  try {
17080
- const fd = await fs35.open(lockPath, "wx");
17123
+ const fd = await fs36.open(lockPath, "wx");
17081
17124
  try {
17082
17125
  await fd.writeFile(`${process.pid}
17083
17126
  `, "utf8");
@@ -17095,7 +17138,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
17095
17138
  throw new RegistryLockError(lockHolderMessage(holder, lockPath, timeoutMs));
17096
17139
  }
17097
17140
  if (holder.kind === "stale" && await isReclaimableLock(lockPath)) {
17098
- await fs35.unlink(lockPath).catch(() => {
17141
+ await fs36.unlink(lockPath).catch(() => {
17099
17142
  });
17100
17143
  continue;
17101
17144
  }
@@ -17113,14 +17156,14 @@ async function isReclaimableLock(lockPath) {
17113
17156
  const pid = await readLockPid(lockPath);
17114
17157
  if (pid !== void 0) return true;
17115
17158
  try {
17116
- const stat = await fs35.stat(lockPath);
17159
+ const stat = await fs36.stat(lockPath);
17117
17160
  return Date.now() - stat.mtimeMs > LOCK_UNSTAMPED_GRACE_MS;
17118
17161
  } catch {
17119
17162
  return false;
17120
17163
  }
17121
17164
  }
17122
17165
  async function releaseLock(lockPath) {
17123
- await fs35.unlink(lockPath).catch(() => {
17166
+ await fs36.unlink(lockPath).catch(() => {
17124
17167
  });
17125
17168
  }
17126
17169
  async function withLock(fn) {
@@ -17136,7 +17179,7 @@ async function readRegistry() {
17136
17179
  const file = registryPath();
17137
17180
  let raw;
17138
17181
  try {
17139
- raw = await fs35.readFile(file, "utf8");
17182
+ raw = await fs36.readFile(file, "utf8");
17140
17183
  } catch (err) {
17141
17184
  if (err.code === "ENOENT") {
17142
17185
  return { version: 1, projects: [] };
@@ -17211,7 +17254,7 @@ async function findProjectByPath(dir) {
17211
17254
  const { projects } = await readRegistry();
17212
17255
  let best;
17213
17256
  for (const entry of projects) {
17214
- const inside = here === entry.path || here.startsWith(entry.path + path70.sep);
17257
+ const inside = here === entry.path || here.startsWith(entry.path + path71.sep);
17215
17258
  if (inside && (!best || entry.path.length > best.path.length)) best = entry;
17216
17259
  }
17217
17260
  return best;
@@ -17263,7 +17306,7 @@ function pruneTtlMs() {
17263
17306
  }
17264
17307
  async function statPathStatus(p) {
17265
17308
  try {
17266
- const stat = await fs35.stat(p);
17309
+ const stat = await fs36.stat(p);
17267
17310
  return stat.isDirectory() ? "present" : "unknown";
17268
17311
  } catch (err) {
17269
17312
  return err.code === "ENOENT" ? "gone" : "unknown";
@@ -17304,18 +17347,19 @@ async function pruneRegistry(opts = {}) {
17304
17347
 
17305
17348
  // src/api.ts
17306
17349
  import Fastify from "fastify";
17350
+ import { timingSafeEqual } from "crypto";
17307
17351
  import cors from "@fastify/cors";
17308
17352
  import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } from "@neat.is/types";
17309
17353
 
17310
17354
  // src/extend/index.ts
17311
- import { promises as fs37 } from "fs";
17312
- import path72 from "path";
17355
+ import { promises as fs38 } from "fs";
17356
+ import path73 from "path";
17313
17357
  import os3 from "os";
17314
17358
  import { resolve as registryResolve, list as registryList } from "@neat.is/instrumentation-registry";
17315
17359
 
17316
17360
  // src/installers/package-manager.ts
17317
- import { promises as fs36 } from "fs";
17318
- import path71 from "path";
17361
+ import { promises as fs37 } from "fs";
17362
+ import path72 from "path";
17319
17363
  import { spawn } from "child_process";
17320
17364
  var LOCKFILE_PRIORITY = [
17321
17365
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -17330,29 +17374,29 @@ var LOCKFILE_PRIORITY = [
17330
17374
  var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
17331
17375
  async function exists2(p) {
17332
17376
  try {
17333
- await fs36.access(p);
17377
+ await fs37.access(p);
17334
17378
  return true;
17335
17379
  } catch {
17336
17380
  return false;
17337
17381
  }
17338
17382
  }
17339
17383
  async function detectPackageManager(serviceDir) {
17340
- let dir = path71.resolve(serviceDir);
17384
+ let dir = path72.resolve(serviceDir);
17341
17385
  const stops = /* @__PURE__ */ new Set();
17342
17386
  for (let i = 0; i < 64; i++) {
17343
17387
  if (stops.has(dir)) break;
17344
17388
  stops.add(dir);
17345
17389
  for (const candidate of LOCKFILE_PRIORITY) {
17346
- const lockPath = path71.join(dir, candidate.lockfile);
17390
+ const lockPath = path72.join(dir, candidate.lockfile);
17347
17391
  if (await exists2(lockPath)) {
17348
17392
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
17349
17393
  }
17350
17394
  }
17351
- const parent = path71.dirname(dir);
17395
+ const parent = path72.dirname(dir);
17352
17396
  if (parent === dir) break;
17353
17397
  dir = parent;
17354
17398
  }
17355
- return { pm: "npm", cwd: path71.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
17399
+ return { pm: "npm", cwd: path72.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
17356
17400
  }
17357
17401
  async function runPackageManagerInstall(cmd) {
17358
17402
  return new Promise((resolve) => {
@@ -17394,15 +17438,15 @@ ${err.message}`
17394
17438
  // src/extend/index.ts
17395
17439
  async function fileExists2(p) {
17396
17440
  try {
17397
- await fs37.access(p);
17441
+ await fs38.access(p);
17398
17442
  return true;
17399
17443
  } catch {
17400
17444
  return false;
17401
17445
  }
17402
17446
  }
17403
17447
  async function readPackageJson(scanPath) {
17404
- const pkgPath = path72.join(scanPath, "package.json");
17405
- const raw = await fs37.readFile(pkgPath, "utf8");
17448
+ const pkgPath = path73.join(scanPath, "package.json");
17449
+ const raw = await fs38.readFile(pkgPath, "utf8");
17406
17450
  return JSON.parse(raw);
17407
17451
  }
17408
17452
  var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
@@ -17416,15 +17460,15 @@ var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
17416
17460
  async function findHookFiles(scanPath) {
17417
17461
  const found = [];
17418
17462
  const walk11 = async (dir) => {
17419
- const entries = await fs37.readdir(dir, { withFileTypes: true }).catch(() => []);
17463
+ const entries = await fs38.readdir(dir, { withFileTypes: true }).catch(() => []);
17420
17464
  for (const entry of entries) {
17421
17465
  if (entry.isDirectory()) {
17422
17466
  if (entry.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry.name)) continue;
17423
- await walk11(path72.join(dir, entry.name));
17467
+ await walk11(path73.join(dir, entry.name));
17424
17468
  } else if (entry.isFile()) {
17425
17469
  if ((entry.name.startsWith("instrumentation") || entry.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry.name)) {
17426
- const rel = path72.relative(scanPath, path72.join(dir, entry.name));
17427
- found.push(rel.split(path72.sep).join("/"));
17470
+ const rel = path73.relative(scanPath, path73.join(dir, entry.name));
17471
+ found.push(rel.split(path73.sep).join("/"));
17428
17472
  }
17429
17473
  }
17430
17474
  }
@@ -17435,7 +17479,7 @@ async function findHookFiles(scanPath) {
17435
17479
  async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
17436
17480
  let fallback = null;
17437
17481
  for (const file of hookFiles) {
17438
- const content = await fs37.readFile(path72.join(scanPath, file), "utf8");
17482
+ const content = await fs38.readFile(path73.join(scanPath, file), "utf8");
17439
17483
  const patched = splicedContent(content, snippet2);
17440
17484
  if (patched !== null) return { file, content, patched };
17441
17485
  if (fallback === null) fallback = { file, content };
@@ -17443,12 +17487,12 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
17443
17487
  return { file: fallback.file, content: fallback.content, patched: null };
17444
17488
  }
17445
17489
  function extendLogPath() {
17446
- return process.env.NEAT_EXTEND_LOG ?? path72.join(os3.homedir(), ".neat", "extend-log.ndjson");
17490
+ return process.env.NEAT_EXTEND_LOG ?? path73.join(os3.homedir(), ".neat", "extend-log.ndjson");
17447
17491
  }
17448
17492
  async function appendExtendLog(entry) {
17449
17493
  const logPath = extendLogPath();
17450
- await fs37.mkdir(path72.dirname(logPath), { recursive: true });
17451
- await fs37.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
17494
+ await fs38.mkdir(path73.dirname(logPath), { recursive: true });
17495
+ await fs38.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
17452
17496
  }
17453
17497
  function splicedContent(fileContent, snippet2) {
17454
17498
  if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
@@ -17506,7 +17550,7 @@ function lookupInstrumentation(library, installedVersion) {
17506
17550
  }
17507
17551
  async function describeProjectInstrumentation(ctx) {
17508
17552
  const hookFiles = await findHookFiles(ctx.scanPath);
17509
- const envNeat = await fileExists2(path72.join(ctx.scanPath, ".env.neat"));
17553
+ const envNeat = await fileExists2(path73.join(ctx.scanPath, ".env.neat"));
17510
17554
  const registryInstrPackages = new Set(
17511
17555
  registryList().map((e) => e.instrumentation_package).filter((p) => !!p)
17512
17556
  );
@@ -17528,7 +17572,7 @@ async function applyExtension(ctx, args, options) {
17528
17572
  );
17529
17573
  }
17530
17574
  for (const file of hookFiles) {
17531
- const content = await fs37.readFile(path72.join(ctx.scanPath, file), "utf8");
17575
+ const content = await fs38.readFile(path73.join(ctx.scanPath, file), "utf8");
17532
17576
  if (content.includes(args.registration_snippet)) {
17533
17577
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
17534
17578
  }
@@ -17540,18 +17584,18 @@ async function applyExtension(ctx, args, options) {
17540
17584
  );
17541
17585
  }
17542
17586
  const primaryFile = primary.file;
17543
- const primaryPath = path72.join(ctx.scanPath, primaryFile);
17587
+ const primaryPath = path73.join(ctx.scanPath, primaryFile);
17544
17588
  const filesTouched = [];
17545
17589
  const depsAdded = [];
17546
- const pkgPath = path72.join(ctx.scanPath, "package.json");
17590
+ const pkgPath = path73.join(ctx.scanPath, "package.json");
17547
17591
  const pkg = await readPackageJson(ctx.scanPath);
17548
17592
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
17549
17593
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
17550
- await fs37.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
17594
+ await fs38.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
17551
17595
  filesTouched.push("package.json");
17552
17596
  depsAdded.push(`${args.instrumentation_package}@${args.version}`);
17553
17597
  }
17554
- await fs37.writeFile(primaryPath, primary.patched, "utf8");
17598
+ await fs38.writeFile(primaryPath, primary.patched, "utf8");
17555
17599
  filesTouched.push(primaryFile);
17556
17600
  const cmd = await detectPackageManager(ctx.scanPath);
17557
17601
  const installer = options?.runInstall ?? runPackageManagerInstall;
@@ -17582,7 +17626,7 @@ async function dryRunExtension(ctx, args) {
17582
17626
  };
17583
17627
  }
17584
17628
  for (const file of hookFiles) {
17585
- const content = await fs37.readFile(path72.join(ctx.scanPath, file), "utf8");
17629
+ const content = await fs38.readFile(path73.join(ctx.scanPath, file), "utf8");
17586
17630
  if (content.includes(args.registration_snippet)) {
17587
17631
  return {
17588
17632
  library: args.library,
@@ -17617,28 +17661,28 @@ async function rollbackExtension(ctx, args) {
17617
17661
  if (!await fileExists2(logPath)) {
17618
17662
  return { undone: false, message: "no apply found for library" };
17619
17663
  }
17620
- const raw = await fs37.readFile(logPath, "utf8");
17664
+ const raw = await fs38.readFile(logPath, "utf8");
17621
17665
  const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
17622
17666
  const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
17623
17667
  if (!match) {
17624
17668
  return { undone: false, message: "no apply found for library" };
17625
17669
  }
17626
- const pkgPath = path72.join(ctx.scanPath, "package.json");
17670
+ const pkgPath = path73.join(ctx.scanPath, "package.json");
17627
17671
  if (await fileExists2(pkgPath)) {
17628
17672
  const pkg = await readPackageJson(ctx.scanPath);
17629
17673
  if (pkg.dependencies?.[match.instrumentation_package]) {
17630
17674
  const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
17631
17675
  pkg.dependencies = rest;
17632
- await fs37.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
17676
+ await fs38.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
17633
17677
  }
17634
17678
  }
17635
17679
  const hookFiles = await findHookFiles(ctx.scanPath);
17636
17680
  for (const file of hookFiles) {
17637
- const filePath = path72.join(ctx.scanPath, file);
17638
- const content = await fs37.readFile(filePath, "utf8");
17681
+ const filePath = path73.join(ctx.scanPath, file);
17682
+ const content = await fs38.readFile(filePath, "utf8");
17639
17683
  if (content.includes(match.registration_snippet)) {
17640
17684
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
17641
- await fs37.writeFile(filePath, filtered, "utf8");
17685
+ await fs38.writeFile(filePath, filtered, "utf8");
17642
17686
  break;
17643
17687
  }
17644
17688
  }
@@ -18598,8 +18642,64 @@ async function askGraph(graph, question, opts = {}) {
18598
18642
  }
18599
18643
 
18600
18644
  // src/streaming.ts
18645
+ import { readFileSync as readFileSync2, statSync } from "fs";
18646
+ import { z } from "zod";
18647
+ import { ErrorEventSchema, IncidentEventPayloadSchema, incidentKindOf as incidentKindOf3 } from "@neat.is/types";
18601
18648
  var SSE_HEARTBEAT_MS = 3e4;
18602
18649
  var SSE_BACKPRESSURE_CAP = 1e3;
18650
+ var INCIDENT_TRIGGER_STREAM_SCOPE = "incident-only-v1";
18651
+ var IncidentTriggerSchema = IncidentEventPayloadSchema.extend({
18652
+ incidentId: z.string().regex(/^[A-Za-z0-9:_-]{1,100}$/),
18653
+ affectedNode: z.string().min(1).max(512),
18654
+ service: z.string().min(1).max(256),
18655
+ at: z.string().datetime()
18656
+ });
18657
+ var MAX_REPLAY_BYTES = 16 * 1024 * 1024;
18658
+ var MAX_REPLAY_EVENTS = 1e4;
18659
+ function isExtractionFailure(row) {
18660
+ const keys = Object.keys(row);
18661
+ return keys.every((key) => ["source", "producer", "file", "error", "stack", "ts"].includes(key)) && typeof row.producer === "string" && typeof row.file === "string" && typeof row.error === "string" && typeof row.ts === "string" && (row.stack === void 0 || typeof row.stack === "string") && !Number.isNaN(Date.parse(row.ts));
18662
+ }
18663
+ function loadIncidentReplay(errorsPath, cursor) {
18664
+ if (cursor !== void 0 && !/^[A-Za-z0-9:_-]{1,100}$/.test(cursor)) throw new Error("incident replay unavailable");
18665
+ try {
18666
+ if (statSync(errorsPath).size > MAX_REPLAY_BYTES) throw new Error("incident replay unavailable");
18667
+ const raw = readFileSync2(errorsPath, "utf8");
18668
+ if (raw && !raw.endsWith("\n")) throw new Error("incident replay unavailable");
18669
+ const seen = /* @__PURE__ */ new Map();
18670
+ const events = [];
18671
+ for (const line of raw.split("\n")) {
18672
+ if (!line) continue;
18673
+ if (events.length >= MAX_REPLAY_EVENTS) throw new Error("incident replay unavailable");
18674
+ const row = JSON.parse(line);
18675
+ if (row !== null && typeof row === "object" && !Array.isArray(row) && "source" in row && row.source === "extract") {
18676
+ if (!isExtractionFailure(row)) throw new Error("incident replay unavailable");
18677
+ continue;
18678
+ }
18679
+ const event = ErrorEventSchema.parse(row);
18680
+ const trigger = IncidentTriggerSchema.parse({
18681
+ incidentId: event.id,
18682
+ affectedNode: event.affectedNode,
18683
+ service: event.service,
18684
+ incidentKind: incidentKindOf3(event),
18685
+ at: event.timestamp
18686
+ });
18687
+ const prior = seen.get(trigger.incidentId), encoded = JSON.stringify(trigger);
18688
+ if (prior !== void 0) {
18689
+ if (prior !== encoded) throw new Error("incident replay unavailable");
18690
+ continue;
18691
+ }
18692
+ seen.set(trigger.incidentId, encoded);
18693
+ events.push(trigger);
18694
+ }
18695
+ if (cursor === void 0) return events;
18696
+ const at = events.findIndex((event) => event.incidentId === cursor);
18697
+ if (at < 0) throw new Error("incident replay unavailable");
18698
+ return events.slice(at + 1);
18699
+ } catch {
18700
+ throw new Error("incident replay unavailable");
18701
+ }
18702
+ }
18603
18703
  function handleSse(req, reply, opts) {
18604
18704
  const heartbeatMs = opts.heartbeatMs ?? SSE_HEARTBEAT_MS;
18605
18705
  const backpressureCap = opts.backpressureCap ?? SSE_BACKPRESSURE_CAP;
@@ -18607,8 +18707,20 @@ function handleSse(req, reply, opts) {
18607
18707
  reply.raw.setHeader("Cache-Control", "no-cache, no-transform");
18608
18708
  reply.raw.setHeader("Connection", "keep-alive");
18609
18709
  reply.raw.setHeader("X-Accel-Buffering", "no");
18710
+ if (opts.incidentOnly) {
18711
+ reply.raw.setHeader("X-NEAT-Event-Scope", INCIDENT_TRIGGER_STREAM_SCOPE);
18712
+ reply.raw.setHeader("X-NEAT-Project", opts.project);
18713
+ if (opts.incidentReplay) reply.raw.setHeader("X-NEAT-Replay-Complete", "1");
18714
+ }
18610
18715
  reply.raw.flushHeaders?.();
18611
18716
  reply.raw.write(":open\n\n");
18717
+ for (const trigger of opts.incidentReplay ?? []) {
18718
+ reply.raw.write(`id: ${trigger.incidentId}
18719
+ event: incident
18720
+ data: ${JSON.stringify(trigger)}
18721
+
18722
+ `);
18723
+ }
18612
18724
  let pending = 0;
18613
18725
  let dropped = false;
18614
18726
  const closeConnection = () => {
@@ -18636,6 +18748,17 @@ data: ${JSON.stringify({ reason: "backpressure" })}
18636
18748
  };
18637
18749
  const listener = (envelope) => {
18638
18750
  if (envelope.project !== opts.project) return;
18751
+ if (opts.incidentOnly) {
18752
+ if (envelope.type !== "incident") return;
18753
+ const trigger = IncidentTriggerSchema.safeParse(envelope.payload);
18754
+ if (!trigger.success) return;
18755
+ writeFrame(`id: ${trigger.data.incidentId}
18756
+ event: incident
18757
+ data: ${JSON.stringify(trigger.data)}
18758
+
18759
+ `);
18760
+ return;
18761
+ }
18639
18762
  writeFrame(`event: ${envelope.type}
18640
18763
  data: ${JSON.stringify(envelope.payload)}
18641
18764
 
@@ -18652,10 +18775,671 @@ data: ${JSON.stringify(envelope.payload)}
18652
18775
  reply.raw.on("error", closeConnection);
18653
18776
  }
18654
18777
 
18655
- // src/connectors-config.ts
18778
+ // src/pr-verdict.ts
18779
+ import { createHash } from "crypto";
18780
+ import { promises as fs39 } from "fs";
18781
+ import os5 from "os";
18782
+ import path75 from "path";
18783
+
18784
+ // ../action/src/graph.mjs
18785
+ import { readFileSync as readFileSync3 } from "fs";
18786
+ var MARKER = "<!-- neat-action:graph-impact -->";
18787
+ var shortId = (id) => String(id).split(":").slice(-1)[0];
18788
+ var fence = (s) => "`" + s + "`";
18789
+ function graphFromExport(raw) {
18790
+ const g = raw.graph ?? raw;
18791
+ const nodes = new Map((g.nodes ?? []).map((n) => [n.key, n.attributes ?? {}]));
18792
+ const edges = (g.edges ?? []).map((e) => ({
18793
+ key: e.key,
18794
+ source: e.source,
18795
+ target: e.target,
18796
+ ...e.attributes ?? {}
18797
+ }));
18798
+ return { nodes, edges };
18799
+ }
18800
+ var namesByType = (entries, type) => entries.filter(([, a]) => a.type === type).map(([k, a]) => a.name ?? shortId(k));
18801
+ function diffGraphs(base, head) {
18802
+ const added = [...head.nodes].filter(([k]) => !base.nodes.has(k));
18803
+ const removed = [...base.nodes].filter(([k]) => !head.nodes.has(k));
18804
+ return {
18805
+ routesAdded: namesByType(added, "RouteNode"),
18806
+ routesRemoved: namesByType(removed, "RouteNode"),
18807
+ tablesAdded: namesByType(added, "InfraNode"),
18808
+ tablesRemoved: namesByType(removed, "InfraNode")
18809
+ };
18810
+ }
18811
+ function changedFileNodeIds(graph, changedPaths) {
18812
+ const ids = [];
18813
+ for (const [key, attrs] of graph.nodes) {
18814
+ if (attrs.type !== "FileNode" || !attrs.path) continue;
18815
+ if (changedPaths.some((c) => c === attrs.path || c.endsWith("/" + attrs.path))) ids.push(key);
18816
+ }
18817
+ return ids;
18818
+ }
18819
+ function changedNodeIds(base, head) {
18820
+ if (!base) return [...head.nodes.keys()];
18821
+ return [
18822
+ ...[...head.nodes.keys()].filter((k) => !base.nodes.has(k)),
18823
+ ...[...base.nodes.keys()].filter((k) => !head.nodes.has(k))
18824
+ ];
18825
+ }
18826
+ function formatDivergences(data, changedIds) {
18827
+ const findings = Array.isArray(data) ? data : data?.divergences ?? data?.findings ?? data?.items ?? [];
18828
+ const changed = new Set(changedIds);
18829
+ return findings.filter((f) => changed.has(f.source) || changed.has(f.target) || changed.has(f.nodeId)).slice(0, 10).map((f) => {
18830
+ const kind = f.kind || f.type || "divergence";
18831
+ const s = shortId(f.source ?? f.nodeId ?? "");
18832
+ const t = f.target ? " \u2192 `" + shortId(f.target) + "`" : "";
18833
+ const why = f.reason || f.message || "";
18834
+ return `${kind}: \`${s}\`${t}${why ? " \u2014 " + why : ""}`;
18835
+ });
18836
+ }
18837
+ function observedBreakFrom(node, data) {
18838
+ const observed = Boolean(data?.observed);
18839
+ const dependentCount = Number(data?.inboundObservedCount ?? 0);
18840
+ const callCount = Array.isArray(data?.dependencies) ? data.dependencies.length : 0;
18841
+ if (!observed && dependentCount === 0 && callCount === 0) return null;
18842
+ const inboundVolume = typeof data.inboundVolume === "number" ? data.inboundVolume : void 0;
18843
+ const window = typeof data.window === "string" ? data.window : void 0;
18844
+ const inboundLastObserved = typeof data.inboundLastObserved === "string" ? data.inboundLastObserved : void 0;
18845
+ return {
18846
+ id: node.id,
18847
+ type: node.type,
18848
+ label: node.label,
18849
+ change: node.change,
18850
+ dependentCount,
18851
+ callCount,
18852
+ ...inboundVolume !== void 0 ? { inboundVolume } : {},
18853
+ ...window !== void 0 ? { window } : {},
18854
+ ...inboundLastObserved !== void 0 ? { inboundLastObserved } : {}
18855
+ };
18856
+ }
18857
+ function blastRadius(graph, nodeId, maxDepth = 10) {
18858
+ const inbound = /* @__PURE__ */ new Map();
18859
+ for (const e of graph.edges) {
18860
+ if (!inbound.has(e.target)) inbound.set(e.target, []);
18861
+ inbound.get(e.target).push({ source: e.source, type: e.type });
18862
+ }
18863
+ const seen = /* @__PURE__ */ new Set([nodeId]);
18864
+ const dependents = [];
18865
+ let frontier = [nodeId];
18866
+ for (let d = 0; d < maxDepth && frontier.length; d++) {
18867
+ const next = [];
18868
+ for (const n of frontier) {
18869
+ for (const { source } of inbound.get(n) ?? []) {
18870
+ if (seen.has(source)) continue;
18871
+ seen.add(source);
18872
+ dependents.push(source);
18873
+ next.push(source);
18874
+ }
18875
+ }
18876
+ frontier = next;
18877
+ }
18878
+ return dependents;
18879
+ }
18880
+ var stable = (obj) => JSON.stringify(obj, Object.keys(obj ?? {}).sort());
18881
+ function changedNodesForObservedScan(base, head, { limit = 25 } = {}) {
18882
+ const out = [];
18883
+ for (const [key, attrs] of base.nodes) {
18884
+ if (!head.nodes.has(key)) {
18885
+ out.push({ id: key, type: attrs.type, label: attrs.name ?? shortId(key), change: "removed" });
18886
+ }
18887
+ }
18888
+ for (const [key, attrs] of head.nodes) {
18889
+ const b = base.nodes.get(key);
18890
+ if (b && stable(b) !== stable(attrs)) {
18891
+ out.push({ id: key, type: attrs.type, label: attrs.name ?? shortId(key), change: "changed" });
18892
+ }
18893
+ }
18894
+ return out.slice(0, limit);
18895
+ }
18896
+ var plural = (n) => n === 1 ? "" : "s";
18897
+ function formatAge(iso) {
18898
+ const t = Date.parse(iso);
18899
+ if (!Number.isFinite(t)) return null;
18900
+ const ms = Date.now() - t;
18901
+ if (ms < 0) return "just now";
18902
+ const s = Math.round(ms / 1e3);
18903
+ if (s < 60) return `${s}s ago`;
18904
+ const m = Math.round(s / 60);
18905
+ if (m < 60) return `${m}m ago`;
18906
+ const h = Math.round(m / 60);
18907
+ if (h < 48) return `${h}h ago`;
18908
+ return `${Math.round(h / 24)}d ago`;
18909
+ }
18910
+ function windowPhrase(window) {
18911
+ if (window === "7d") return "in 7d";
18912
+ if (window === "lifetime") return "(lifetime)";
18913
+ return "";
18914
+ }
18915
+ function observedTraffic(b) {
18916
+ if (typeof b.inboundVolume !== "number") return null;
18917
+ const win = windowPhrase(b.window);
18918
+ const served = `served ${b.inboundVolume.toLocaleString("en-US")}\xD7${win ? " " + win : ""}`;
18919
+ const age = b.inboundLastObserved ? formatAge(b.inboundLastObserved) : null;
18920
+ return age ? `${served}, last seen ${age}` : served;
18921
+ }
18922
+ var FUSION_NOTE = "<sub>Fused view \u2014 the static graph (EXTRACTED) weighted against production traffic (OBSERVED) from the connected NEAT host. Observed-break checks ran live against that host.</sub>";
18923
+ var TONE_COPY = {
18924
+ loud: {
18925
+ green: "## \u2705 NEAT \u2014 all clear",
18926
+ yellow: "## \u26A0\uFE0F NEAT \u2014 a few minor problems",
18927
+ red: "## \u{1F6D1} NEAT \u2014 FUCK NO. Here are the problems:"
18928
+ },
18929
+ professional: {
18930
+ green: "## \u2705 NEAT \u2014 all clear",
18931
+ yellow: "## \u26A0\uFE0F NEAT \u2014 Minor issues to review",
18932
+ red: "## \u{1F6D1} NEAT \u2014 Critical: production will break"
18933
+ }
18934
+ };
18935
+ function breakHeadline(b) {
18936
+ const verb = b.change === "removed" ? "deletes" : "changes";
18937
+ const what = fence(b.label) + (b.type === "InfraNode" ? " (data store)" : "");
18938
+ const consequence = b.type === "RouteNode" ? "Live callers 404." : b.type === "InfraNode" ? "Live readers and writers fail." : "Live callers break.";
18939
+ const traffic = observedTraffic(b);
18940
+ if (traffic) {
18941
+ return `**\u{1F534} Production will break** \u2014 this PR ${verb} ${what}, ${traffic} (OBSERVED). ${consequence}`;
18942
+ }
18943
+ if (b.dependentCount > 0) {
18944
+ return `**\u{1F534} Production will break** \u2014 this PR ${verb} ${what}, ${b.dependentCount} observed dependent${plural(b.dependentCount)} (OBSERVED). ${consequence}`;
18945
+ }
18946
+ return `**\u{1F534} Production will break** \u2014 this PR ${verb} ${what}, seen live in production making ${b.callCount} observed call${plural(b.callCount)} (OBSERVED). Runtime behavior changes.`;
18947
+ }
18948
+ function breakDetail(b) {
18949
+ return "- " + fence(b.label) + ` \u2014 ${b.change} ${b.type ?? "node"}; ${b.dependentCount} observed inbound dependent${plural(b.dependentCount)}, ${b.callCount} observed outbound call${plural(b.callCount)}. Provenance: OBSERVED (live traffic on the connected host).`;
18950
+ }
18951
+ function renderVerdict({
18952
+ graph,
18953
+ delta,
18954
+ changedFiles = [],
18955
+ divergences = [],
18956
+ observedBreaks = [],
18957
+ tone = "loud",
18958
+ sniperDispatchUrl = "",
18959
+ graphDiffUrl = ""
18960
+ }) {
18961
+ const copy = TONE_COPY[tone] ?? TONE_COPY.loud;
18962
+ const brokenRoutes = new Set(
18963
+ observedBreaks.filter((b) => b.type === "RouteNode").map((b) => b.label)
18964
+ );
18965
+ const brokenTables = new Set(
18966
+ observedBreaks.filter((b) => b.type === "InfraNode").map((b) => b.label)
18967
+ );
18968
+ const staticConcerns = [];
18969
+ for (const r of delta.routesRemoved ?? []) {
18970
+ if (brokenRoutes.has(r)) continue;
18971
+ staticConcerns.push({
18972
+ headline: `**\u{1F7E0} Removed route** \u2014 ${fence(r)} no longer exists; confirm nothing in-repo still calls it.`,
18973
+ detail: `- Route ${fence(r)} removed. The connected host observed no production traffic on it \u2014 static-only concern.`
18974
+ });
18975
+ }
18976
+ for (const t of delta.tablesRemoved ?? []) {
18977
+ if (brokenTables.has(t)) continue;
18978
+ staticConcerns.push({
18979
+ headline: `**\u{1F7E0} Removed reference** \u2014 ${fence(t)} no longer exists; confirm nothing in-repo still reads it.`,
18980
+ detail: `- Table/infra ${fence(t)} removed. The connected host observed no production traffic on it \u2014 static-only concern.`
18981
+ });
18982
+ }
18983
+ const findings = [
18984
+ ...observedBreaks.map((b) => ({ headline: breakHeadline(b), detail: breakDetail(b) })),
18985
+ ...divergences.map((d) => ({
18986
+ headline: `**\u{1F7E0} Divergence** \u2014 ${d}`,
18987
+ detail: `- ${d}`
18988
+ })),
18989
+ ...staticConcerns
18990
+ ];
18991
+ const tier = observedBreaks.length > 0 ? "red" : findings.length > 0 ? "yellow" : "green";
18992
+ const actionLinks = [];
18993
+ if (sniperDispatchUrl)
18994
+ actionLinks.push(`**[\u26A1 Dispatch Sniper to fix these \u2192](${sniperDispatchUrl})**`);
18995
+ if (graphDiffUrl) actionLinks.push(`[Full graph diff \u2192](${graphDiffUrl})`);
18996
+ const L = [MARKER, ""];
18997
+ if (tier === "green") {
18998
+ L.push(
18999
+ copy.green,
19000
+ "",
19001
+ "Nothing this PR touches is observed in production, and it introduces no new divergences.",
19002
+ "",
19003
+ FUSION_NOTE
19004
+ );
19005
+ return { marker: MARKER, body: L.join("\n"), tier };
19006
+ }
19007
+ if (tier === "yellow") {
19008
+ L.push(
19009
+ copy.yellow,
19010
+ "",
19011
+ `${findings.length} minor problem${plural(findings.length)} \u2014 read ${findings.length === 1 ? "it" : "them"} below.`,
19012
+ "",
19013
+ "<details><summary>Read them \u2192</summary>",
19014
+ ""
19015
+ );
19016
+ findings.forEach((f, i) => L.push(`${i + 1}. ${f.headline}`));
19017
+ L.push("", ...findings.map((f) => f.detail));
19018
+ L.push("", "</details>");
19019
+ if (actionLinks.length) L.push("", ...actionLinks);
19020
+ L.push("", FUSION_NOTE);
19021
+ return { marker: MARKER, body: L.join("\n"), tier };
19022
+ }
19023
+ L.push(copy.red, "");
19024
+ findings.forEach((f, i) => L.push(`${i + 1}. ${f.headline}`));
19025
+ L.push("", "<details><summary>Full detail \u2192</summary>", "");
19026
+ L.push(...findings.map((f) => f.detail));
19027
+ const anyDelta = (delta.routesAdded?.length ?? 0) + (delta.routesRemoved?.length ?? 0) + (delta.tablesAdded?.length ?? 0) + (delta.tablesRemoved?.length ?? 0);
19028
+ if (anyDelta) {
19029
+ L.push("", "**Graph delta**");
19030
+ if (delta.routesAdded?.length) L.push("- \u2795 routes: " + delta.routesAdded.map(fence).join(", "));
19031
+ if (delta.routesRemoved?.length)
19032
+ L.push("- \u2796 routes: " + delta.routesRemoved.map(fence).join(", "));
19033
+ if (delta.tablesAdded?.length) L.push("- \u2795 tables: " + delta.tablesAdded.map(fence).join(", "));
19034
+ if (delta.tablesRemoved?.length)
19035
+ L.push("- \u2796 tables: " + delta.tablesRemoved.map(fence).join(", "));
19036
+ }
19037
+ const filesInGraph = (changedFiles ?? []).filter((f) => graph?.nodes?.has(f));
19038
+ if (filesInGraph.length) {
19039
+ L.push("", "**Blast radius (in-repo)**");
19040
+ for (const f of filesInGraph) {
19041
+ const deps = blastRadius(graph, f).filter((d) => d.startsWith("file:")).map(shortId);
19042
+ L.push(
19043
+ "- " + fence(shortId(f)) + " \u2192 depended on by " + (deps.length ? deps.map(fence).join(", ") : "_nothing else in-repo_")
19044
+ );
19045
+ }
19046
+ }
19047
+ L.push("", "</details>");
19048
+ if (actionLinks.length) L.push("", ...actionLinks);
19049
+ L.push("", FUSION_NOTE);
19050
+ return { marker: MARKER, body: L.join("\n"), tier };
19051
+ }
19052
+
19053
+ // src/pr-verdict.ts
19054
+ import { PrVerdictBodySchema } from "@neat.is/types";
19055
+
19056
+ // src/connectors/hosted-repos.ts
18656
19057
  import os4 from "os";
18657
- import path73 from "path";
18658
- import { promises as fs38 } from "fs";
19058
+ import path74 from "path";
19059
+ import { mkdtemp, rm } from "fs/promises";
19060
+ var CP_REQUEST_TIMEOUT_MS = 1e4;
19061
+ var DEFAULT_SYNC_INTERVAL_MS = 5 * 6e4;
19062
+ var CLONE_TIMEOUT_MS = 5 * 6e4;
19063
+ function scrubToken(s) {
19064
+ return s.replace(/x-access-token:[^@\s]+@/g, "x-access-token:***@");
19065
+ }
19066
+ async function cpGet(pathname, deps) {
19067
+ const f = deps.fetchImpl ?? fetch;
19068
+ const res = await f(`${deps.cpUrl.replace(/\/+$/, "")}${pathname}`, {
19069
+ headers: { authorization: `Bearer ${deps.daemonToken}`, accept: "application/json" },
19070
+ signal: AbortSignal.timeout(CP_REQUEST_TIMEOUT_MS)
19071
+ });
19072
+ if (!res.ok) throw new Error(`control plane ${pathname} \u2192 HTTP ${res.status}`);
19073
+ return await res.json();
19074
+ }
19075
+ async function cpPostStatus(deps, owner, name, body) {
19076
+ const f = deps.fetchImpl ?? fetch;
19077
+ const url = `${deps.cpUrl.replace(/\/+$/, "")}/internal/projects/${deps.projectId}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/status`;
19078
+ const res = await f(url, {
19079
+ method: "POST",
19080
+ headers: { authorization: `Bearer ${deps.daemonToken}`, "content-type": "application/json" },
19081
+ body: JSON.stringify(body),
19082
+ signal: AbortSignal.timeout(CP_REQUEST_TIMEOUT_MS)
19083
+ });
19084
+ if (!res.ok && res.status !== 404) throw new Error(`control plane status POST \u2192 HTTP ${res.status}`);
19085
+ }
19086
+ var defaultCloneRepo = async (cloneUrl, ref, destDir) => {
19087
+ const [{ default: git }, httpMod, fs41] = await Promise.all([
19088
+ import("isomorphic-git"),
19089
+ import("isomorphic-git/http/node"),
19090
+ import("fs")
19091
+ ]);
19092
+ const http = httpMod.default ?? httpMod;
19093
+ const parsed = new URL(cloneUrl);
19094
+ const password = parsed.password || parsed.username;
19095
+ const username = parsed.password ? parsed.username : "x-access-token";
19096
+ const cleanUrl = `${parsed.protocol}//${parsed.host}${parsed.pathname}`;
19097
+ const clone = git.clone({
19098
+ fs: fs41,
19099
+ http,
19100
+ dir: destDir,
19101
+ url: cleanUrl,
19102
+ // Absent ref → isomorphic-git clones the remote's default branch.
19103
+ ...ref ? { ref } : {},
19104
+ singleBranch: true,
19105
+ depth: 1,
19106
+ onAuth: () => ({ username, password })
19107
+ });
19108
+ let timer;
19109
+ const timeout = new Promise((_, reject) => {
19110
+ timer = setTimeout(() => reject(new Error(`clone timed out after ${CLONE_TIMEOUT_MS}ms`)), CLONE_TIMEOUT_MS);
19111
+ });
19112
+ try {
19113
+ await Promise.race([clone, timeout]);
19114
+ } finally {
19115
+ if (timer) clearTimeout(timer);
19116
+ }
19117
+ };
19118
+ var defaultCloneCommit = async (cloneUrl, sha, destDir) => {
19119
+ const [{ default: git }, httpMod, fs41] = await Promise.all([
19120
+ import("isomorphic-git"),
19121
+ import("isomorphic-git/http/node"),
19122
+ import("fs")
19123
+ ]);
19124
+ const http = httpMod.default ?? httpMod;
19125
+ const parsed = new URL(cloneUrl);
19126
+ const password = parsed.password || parsed.username;
19127
+ const username = parsed.password ? parsed.username : "x-access-token";
19128
+ const cleanUrl = `${parsed.protocol}//${parsed.host}${parsed.pathname}`;
19129
+ const clone = (async () => {
19130
+ await git.init({ fs: fs41, dir: destDir });
19131
+ await git.addRemote({ fs: fs41, dir: destDir, remote: "origin", url: cleanUrl });
19132
+ await git.fetch({
19133
+ fs: fs41,
19134
+ http,
19135
+ dir: destDir,
19136
+ remote: "origin",
19137
+ // A full SHA is fetched as itself; the remote doesn't have to advertise a ref that points at it.
19138
+ ref: sha,
19139
+ remoteRef: sha,
19140
+ singleBranch: true,
19141
+ depth: 1,
19142
+ tags: false,
19143
+ ...password ? { onAuth: () => ({ username, password }) } : {}
19144
+ });
19145
+ await git.checkout({ fs: fs41, dir: destDir, ref: sha, force: true });
19146
+ })();
19147
+ clone.catch(() => {
19148
+ });
19149
+ let timer;
19150
+ const timeout = new Promise((_, reject) => {
19151
+ timer = setTimeout(() => reject(new Error(`clone timed out after ${CLONE_TIMEOUT_MS}ms`)), CLONE_TIMEOUT_MS);
19152
+ });
19153
+ try {
19154
+ await Promise.race([clone, timeout]);
19155
+ } finally {
19156
+ if (timer) clearTimeout(timer);
19157
+ }
19158
+ };
19159
+ function scrubCloneToken(s) {
19160
+ return scrubToken(s);
19161
+ }
19162
+ function needsSync(r) {
19163
+ return r.syncStatus === void 0 || r.syncStatus === "syncing";
19164
+ }
19165
+ async function syncOneRepo(r, input) {
19166
+ const { deps, graph } = input;
19167
+ const cloneRepo = input.cloneRepo ?? defaultCloneRepo;
19168
+ const extract = input.extract ?? extractFromDirectory;
19169
+ const label = `${r.owner}/${r.name}`;
19170
+ const tmpRoot = input.tmpRoot ?? os4.tmpdir();
19171
+ let dir;
19172
+ try {
19173
+ dir = await mkdtemp(path74.join(tmpRoot, "neat-repo-"));
19174
+ await cpPostStatus(deps, r.owner, r.name, { syncStatus: "syncing", detail: "cloning" }).catch(() => {
19175
+ });
19176
+ await cloneRepo(r.cloneUrl, r.defaultBranch, dir);
19177
+ const extracted = await extract(graph, dir, { source: label });
19178
+ const nodes = extracted?.nodesAdded ?? 0;
19179
+ const edges = extracted?.edgesAdded ?? 0;
19180
+ await cpPostStatus(deps, r.owner, r.name, {
19181
+ syncStatus: "synced",
19182
+ detail: `extracted ${nodes} node${nodes === 1 ? "" : "s"}, ${edges} edge${edges === 1 ? "" : "s"}`,
19183
+ lastSyncAt: new Date(input.now?.() ?? Date.now()).toISOString()
19184
+ });
19185
+ return true;
19186
+ } catch (err) {
19187
+ input.onError?.(label, err);
19188
+ await cpPostStatus(deps, r.owner, r.name, {
19189
+ syncStatus: "failed",
19190
+ detail: scrubToken(err.message).slice(0, 300)
19191
+ }).catch(() => {
19192
+ });
19193
+ return false;
19194
+ } finally {
19195
+ if (dir) await rm(dir, { recursive: true, force: true }).catch(() => {
19196
+ });
19197
+ }
19198
+ }
19199
+ async function runRepoSyncPassCounted(input) {
19200
+ const counts = { listed: false, synced: 0, failed: 0 };
19201
+ let repos;
19202
+ try {
19203
+ repos = await cpGet(`/internal/projects/${input.deps.projectId}/repos`, input.deps);
19204
+ } catch (err) {
19205
+ input.onSkip?.("(all)", `control plane repo list unreadable \u2014 ${err.message}`);
19206
+ return counts;
19207
+ }
19208
+ if (!Array.isArray(repos)) return counts;
19209
+ counts.listed = true;
19210
+ for (const r of repos) {
19211
+ if (!input.forceResync && !needsSync(r)) continue;
19212
+ if (await syncOneRepo(r, input)) counts.synced++;
19213
+ else counts.failed++;
19214
+ }
19215
+ return counts;
19216
+ }
19217
+ async function startRepoSync(input) {
19218
+ const intervalMs = input.intervalMs ?? DEFAULT_SYNC_INTERVAL_MS;
19219
+ const now = () => input.now?.() ?? Date.now();
19220
+ let stopped = false;
19221
+ let inFlight = null;
19222
+ let again = false;
19223
+ let lastPass;
19224
+ let bootResyncDone = false;
19225
+ const run = async () => {
19226
+ try {
19227
+ do {
19228
+ again = false;
19229
+ const startedAt = new Date(now()).toISOString();
19230
+ const counts = await runRepoSyncPassCounted({ ...input, forceResync: !bootResyncDone });
19231
+ if (counts.listed) bootResyncDone = true;
19232
+ lastPass = { ...counts, startedAt, finishedAt: new Date(now()).toISOString() };
19233
+ } while (again && !stopped);
19234
+ } finally {
19235
+ inFlight = null;
19236
+ }
19237
+ };
19238
+ const tick = () => {
19239
+ if (stopped || inFlight) return;
19240
+ inFlight = run();
19241
+ };
19242
+ tick();
19243
+ const timer = setInterval(tick, intervalMs);
19244
+ if (typeof timer.unref === "function") timer.unref();
19245
+ const stop = () => {
19246
+ stopped = true;
19247
+ clearInterval(timer);
19248
+ };
19249
+ return Object.assign(stop, {
19250
+ syncNow: () => {
19251
+ const summary = lastPass ? { lastPass } : {};
19252
+ if (inFlight) {
19253
+ again = true;
19254
+ return { status: "queued", ...summary };
19255
+ }
19256
+ inFlight = run();
19257
+ return { status: "started", ...summary };
19258
+ },
19259
+ settled: async () => {
19260
+ while (inFlight) await inFlight;
19261
+ }
19262
+ });
19263
+ }
19264
+ async function maybeStartRepoSync(input) {
19265
+ const env = input.env ?? process.env;
19266
+ const cpUrl = env.NEAT_CP_URL;
19267
+ const projectId = env.NEAT_CP_PROJECT_ID;
19268
+ const daemonToken = env.NEAT_AUTH_TOKEN;
19269
+ if (!cpUrl || !projectId || !daemonToken) return () => {
19270
+ };
19271
+ return startRepoSync({
19272
+ deps: { cpUrl, projectId, daemonToken, ...input.fetchImpl ? { fetchImpl: input.fetchImpl } : {} },
19273
+ graph: input.graph,
19274
+ project: input.project,
19275
+ ...input.cloneRepo ? { cloneRepo: input.cloneRepo } : {},
19276
+ ...input.extract ? { extract: input.extract } : {},
19277
+ ...input.intervalMs !== void 0 ? { intervalMs: input.intervalMs } : {},
19278
+ ...input.tmpRoot ? { tmpRoot: input.tmpRoot } : {},
19279
+ ...input.onSkip ? { onSkip: input.onSkip } : {},
19280
+ ...input.onError ? { onError: input.onError } : {}
19281
+ });
19282
+ }
19283
+
19284
+ // src/pr-verdict.ts
19285
+ var DEFAULT_PR_VERDICT_TIMEOUT_MS = 24e4;
19286
+ var MAX_CHANGED_FILES = 3e3;
19287
+ var PrVerdictError = class extends Error {
19288
+ constructor(message, status, stage, details) {
19289
+ super(message);
19290
+ this.status = status;
19291
+ this.stage = stage;
19292
+ this.details = details;
19293
+ this.name = "PrVerdictError";
19294
+ }
19295
+ status;
19296
+ stage;
19297
+ details;
19298
+ };
19299
+ function parsePrVerdictRequest(body) {
19300
+ const parsed = PrVerdictBodySchema.safeParse(body);
19301
+ if (!parsed.success) {
19302
+ throw new PrVerdictError("invalid pr-verdict body", 400, void 0, parsed.error.issues);
19303
+ }
19304
+ const { changedFiles, tone, ...rest } = parsed.data;
19305
+ return {
19306
+ ...rest,
19307
+ ...changedFiles ? { changedFiles: changedFiles.slice(0, MAX_CHANGED_FILES) } : {},
19308
+ ...tone ? { tone } : {}
19309
+ };
19310
+ }
19311
+ function createPrVerdictRunner(deps = {}) {
19312
+ let busy = false;
19313
+ return async (req, ctx) => {
19314
+ if (busy) throw new PrVerdictError("a verdict is already being computed on this daemon", 429);
19315
+ busy = true;
19316
+ const timeoutMs = deps.timeoutMs ?? DEFAULT_PR_VERDICT_TIMEOUT_MS;
19317
+ const deadline = (deps.now ?? Date.now)() + timeoutMs;
19318
+ const work = computePrVerdict(req, ctx, deps, deadline).finally(() => {
19319
+ busy = false;
19320
+ });
19321
+ let timer;
19322
+ const timeout = new Promise((_, reject) => {
19323
+ timer = setTimeout(
19324
+ () => reject(new PrVerdictError(`verdict did not finish within ${timeoutMs}ms`, 504)),
19325
+ timeoutMs
19326
+ );
19327
+ });
19328
+ try {
19329
+ return await Promise.race([work, timeout]);
19330
+ } finally {
19331
+ if (timer) clearTimeout(timer);
19332
+ work.catch(() => {
19333
+ });
19334
+ }
19335
+ };
19336
+ }
19337
+ async function computePrVerdict(req, ctx, deps, deadline) {
19338
+ const now = deps.now ?? Date.now;
19339
+ const startedAt = now();
19340
+ const cloneCommit = deps.cloneCommit ?? defaultCloneCommit;
19341
+ const extract = deps.extract ?? extractFromDirectory;
19342
+ const root = await fs39.mkdtemp(path75.join(deps.tmpRoot ?? os5.tmpdir(), "neat-pr-"));
19343
+ const baseDir = path75.join(root, "base");
19344
+ const headDir = path75.join(root, "head");
19345
+ const stage = async (name, run) => {
19346
+ const left = deadline - now();
19347
+ if (left <= 0) throw new PrVerdictError(`verdict ran out of time before ${name}`, 504, name);
19348
+ const work = run();
19349
+ work.catch(() => {
19350
+ });
19351
+ let timer;
19352
+ const expired = new Promise((_, reject) => {
19353
+ timer = setTimeout(() => reject(new PrVerdictError(`verdict ran out of time during ${name}`, 504, name)), left);
19354
+ });
19355
+ try {
19356
+ return await Promise.race([work, expired]);
19357
+ } catch (err) {
19358
+ if (err instanceof PrVerdictError) throw err;
19359
+ const why = scrubCloneToken(err.message ?? String(err)).slice(0, 300);
19360
+ throw new PrVerdictError(`${name} failed \u2014 ${why}`, 422, name);
19361
+ } finally {
19362
+ if (timer) clearTimeout(timer);
19363
+ }
19364
+ };
19365
+ const extractOpts = { source: `${req.owner}/${req.name}`, announce: false };
19366
+ try {
19367
+ await fs39.mkdir(baseDir);
19368
+ await fs39.mkdir(headDir);
19369
+ await stage("clone-base", () => cloneCommit(req.cloneUrl, req.baseSha, baseDir));
19370
+ await stage("clone-head", () => cloneCommit(req.cloneUrl, req.headSha, headDir));
19371
+ const baseGraph = makeGraph();
19372
+ const headGraph = makeGraph();
19373
+ await stage("extract-base", () => extract(baseGraph, baseDir, extractOpts));
19374
+ await stage("extract-head", () => extract(headGraph, headDir, extractOpts));
19375
+ const base = graphFromExport(baseGraph.export());
19376
+ const head = graphFromExport(headGraph.export());
19377
+ const delta = diffGraphs(base, head);
19378
+ const changedPaths = req.changedFiles ?? await changedPathsBetween(baseDir, headDir);
19379
+ const changedFiles = changedFileNodeIds(head, changedPaths);
19380
+ const divergences = formatDivergences(
19381
+ computeDivergences(ctx.liveGraph, { incidents: ctx.incidents ?? [] }),
19382
+ changedNodeIds(base, head)
19383
+ );
19384
+ const observedBreaks = [];
19385
+ for (const node of changedNodesForObservedScan(base, head)) {
19386
+ if (!ctx.liveGraph.hasNode(node.id)) continue;
19387
+ const found = observedBreakFrom(node, getObservedDependencies(ctx.liveGraph, node.id));
19388
+ if (found) observedBreaks.push(found);
19389
+ }
19390
+ const { marker, body } = renderVerdict({
19391
+ graph: head,
19392
+ delta,
19393
+ changedFiles,
19394
+ divergences,
19395
+ observedBreaks,
19396
+ tone: req.tone ?? "loud"
19397
+ });
19398
+ return {
19399
+ marker,
19400
+ body,
19401
+ base: { sha: req.baseSha, nodes: baseGraph.order, edges: baseGraph.size },
19402
+ head: { sha: req.headSha, nodes: headGraph.order, edges: headGraph.size },
19403
+ changedFiles: changedFiles.length,
19404
+ observedBreaks: observedBreaks.length,
19405
+ divergences: divergences.length,
19406
+ durationMs: now() - startedAt
19407
+ };
19408
+ } finally {
19409
+ await fs39.rm(root, { recursive: true, force: true }).catch(() => {
19410
+ });
19411
+ }
19412
+ }
19413
+ var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", "neat-out"]);
19414
+ async function hashTree(dir) {
19415
+ const out = /* @__PURE__ */ new Map();
19416
+ const walk11 = async (rel) => {
19417
+ const entries = await fs39.readdir(path75.join(dir, rel), { withFileTypes: true }).catch(() => []);
19418
+ for (const entry of entries) {
19419
+ const child = rel ? `${rel}/${entry.name}` : entry.name;
19420
+ if (entry.isDirectory()) {
19421
+ if (!SKIP_DIRS.has(entry.name)) await walk11(child);
19422
+ } else if (entry.isFile()) {
19423
+ const bytes = await fs39.readFile(path75.join(dir, child)).catch(() => null);
19424
+ if (bytes) out.set(child, createHash("sha1").update(bytes).digest("hex"));
19425
+ }
19426
+ }
19427
+ };
19428
+ await walk11("");
19429
+ return out;
19430
+ }
19431
+ async function changedPathsBetween(baseDir, headDir) {
19432
+ const [base, head] = await Promise.all([hashTree(baseDir), hashTree(headDir)]);
19433
+ const changed = [];
19434
+ for (const [file, hash] of head) if (base.get(file) !== hash) changed.push(file);
19435
+ for (const file of base.keys()) if (!head.has(file)) changed.push(file);
19436
+ return changed.sort().slice(0, MAX_CHANGED_FILES);
19437
+ }
19438
+
19439
+ // src/connectors-config.ts
19440
+ import os6 from "os";
19441
+ import path76 from "path";
19442
+ import { promises as fs40 } from "fs";
18659
19443
  var CONNECTORS_CONFIG_VERSION = 1;
18660
19444
  var EnvRefUnsetError = class extends Error {
18661
19445
  ref;
@@ -18669,17 +19453,17 @@ var EnvRefUnsetError = class extends Error {
18669
19453
  };
18670
19454
  function neatHome2() {
18671
19455
  const override = process.env.NEAT_HOME;
18672
- if (override && override.length > 0) return path73.resolve(override);
18673
- return path73.join(os4.homedir(), ".neat");
19456
+ if (override && override.length > 0) return path76.resolve(override);
19457
+ return path76.join(os6.homedir(), ".neat");
18674
19458
  }
18675
19459
  function connectorsConfigPath(home = neatHome2()) {
18676
- return path73.join(home, "connectors.json");
19460
+ return path76.join(home, "connectors.json");
18677
19461
  }
18678
19462
  var MODE_MASK_LOOSER_THAN_0600 = 63;
18679
19463
  async function warnIfModeLooserThan0600(file) {
18680
19464
  if (process.platform === "win32") return;
18681
19465
  try {
18682
- const stat = await fs38.stat(file);
19466
+ const stat = await fs40.stat(file);
18683
19467
  if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
18684
19468
  const mode = (stat.mode & 511).toString(8).padStart(3, "0");
18685
19469
  console.warn(
@@ -18693,7 +19477,7 @@ async function readConnectorsConfig(home = neatHome2()) {
18693
19477
  const file = connectorsConfigPath(home);
18694
19478
  let raw;
18695
19479
  try {
18696
- raw = await fs38.readFile(file, "utf8");
19480
+ raw = await fs40.readFile(file, "utf8");
18697
19481
  } catch (err) {
18698
19482
  if (err.code === "ENOENT") {
18699
19483
  return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
@@ -18804,7 +19588,7 @@ function connectorMatchesProject(entry, project) {
18804
19588
  var CONNECTORS_LOCK_TIMEOUT_MS = 5e3;
18805
19589
  var CONNECTORS_LOCK_RETRY_MS = 50;
18806
19590
  function connectorsConfigLockPath(home = neatHome2()) {
18807
- return path73.join(home, "connectors.json.lock");
19591
+ return path76.join(home, "connectors.json.lock");
18808
19592
  }
18809
19593
  function isEnvRef(value) {
18810
19594
  return value.length > 1 && value.startsWith("$");
@@ -18817,9 +19601,9 @@ function redactCredentialRef(ref) {
18817
19601
  return out;
18818
19602
  }
18819
19603
  async function writeConfigAtomically0600(file, contents) {
18820
- await fs38.mkdir(path73.dirname(file), { recursive: true });
19604
+ await fs40.mkdir(path76.dirname(file), { recursive: true });
18821
19605
  const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
18822
- const fd = await fs38.open(tmp, "w", 384);
19606
+ const fd = await fs40.open(tmp, "w", 384);
18823
19607
  try {
18824
19608
  await fd.writeFile(contents, "utf8");
18825
19609
  await fd.chmod(384);
@@ -18827,14 +19611,14 @@ async function writeConfigAtomically0600(file, contents) {
18827
19611
  } finally {
18828
19612
  await fd.close();
18829
19613
  }
18830
- await fs38.rename(tmp, file);
19614
+ await fs40.rename(tmp, file);
18831
19615
  }
18832
19616
  async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEOUT_MS) {
18833
19617
  const deadline = Date.now() + timeoutMs;
18834
- await fs38.mkdir(path73.dirname(lockPath), { recursive: true });
19618
+ await fs40.mkdir(path76.dirname(lockPath), { recursive: true });
18835
19619
  for (; ; ) {
18836
19620
  try {
18837
- const fd = await fs38.open(lockPath, "wx");
19621
+ const fd = await fs40.open(lockPath, "wx");
18838
19622
  try {
18839
19623
  await fd.writeFile(`${process.pid}
18840
19624
  `, "utf8");
@@ -18854,7 +19638,7 @@ async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEO
18854
19638
  }
18855
19639
  }
18856
19640
  async function releaseConnectorsLock(lockPath) {
18857
- await fs38.unlink(lockPath).catch(() => {
19641
+ await fs40.unlink(lockPath).catch(() => {
18858
19642
  });
18859
19643
  }
18860
19644
  async function withConnectorsLock(home, fn) {
@@ -19594,10 +20378,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
19594
20378
  // src/connectors/supabase/map.ts
19595
20379
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
19596
20380
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
19597
- function targetFromRestPath(path74) {
19598
- const rpcMatch = REST_RPC_PATH_RE.exec(path74);
20381
+ function targetFromRestPath(path77) {
20382
+ const rpcMatch = REST_RPC_PATH_RE.exec(path77);
19599
20383
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
19600
- const tableMatch = REST_TABLE_PATH_RE.exec(path74);
20384
+ const tableMatch = REST_TABLE_PATH_RE.exec(path77);
19601
20385
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
19602
20386
  return null;
19603
20387
  }
@@ -20192,9 +20976,9 @@ function parseFirebaseTargetName(targetName) {
20192
20976
  const secondSep = rest.indexOf(FIELD_SEP);
20193
20977
  if (secondSep === -1) return null;
20194
20978
  const method = rest.slice(0, secondSep);
20195
- const path74 = rest.slice(secondSep + 1);
20196
- if (!resourceName || !method || !path74) return null;
20197
- return { resourceName, method, path: path74 };
20979
+ const path77 = rest.slice(secondSep + 1);
20980
+ if (!resourceName || !method || !path77) return null;
20981
+ return { resourceName, method, path: path77 };
20198
20982
  }
20199
20983
  function resourceNameFor(type, labels) {
20200
20984
  if (!labels) return null;
@@ -20232,14 +21016,14 @@ function mapLogEntryToSignal(entry) {
20232
21016
  if (!req) return null;
20233
21017
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
20234
21018
  const method = req.requestMethod.toUpperCase();
20235
- const path74 = pathFromRequestUrl(req.requestUrl);
20236
- if (path74 === null) return null;
21019
+ const path77 = pathFromRequestUrl(req.requestUrl);
21020
+ if (path77 === null) return null;
20237
21021
  const timestamp = entry.timestamp;
20238
21022
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
20239
21023
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
20240
21024
  return {
20241
21025
  targetKind: resourceType,
20242
- targetName: packFirebaseTargetName({ resourceName, method, path: path74 }),
21026
+ targetName: packFirebaseTargetName({ resourceName, method, path: path77 }),
20243
21027
  callCount: 1,
20244
21028
  errorCount: isError ? 1 : 0,
20245
21029
  lastObservedIso: timestamp
@@ -20262,9 +21046,9 @@ import { NodeType as NodeType35 } from "@neat.is/types";
20262
21046
  function normalizeName(name) {
20263
21047
  return name.toLowerCase().replace(/[^a-z0-9]/g, "");
20264
21048
  }
20265
- function inferServiceName(graph, resourceName, method, path74) {
21049
+ function inferServiceName(graph, resourceName, method, path77) {
20266
21050
  const wantedMethod = method.toUpperCase();
20267
- const wantedPath = normalizePathTemplate(path74);
21051
+ const wantedPath = normalizePathTemplate(path77);
20268
21052
  const services = [];
20269
21053
  const owners = /* @__PURE__ */ new Set();
20270
21054
  graph.forEachNode((_id, attrs) => {
@@ -20472,7 +21256,7 @@ function mapEventToSignal(event) {
20472
21256
  if (Number.isNaN(observedAt.getTime())) return null;
20473
21257
  const statusCode = metadata?.statusCode;
20474
21258
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
20475
- const path74 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
21259
+ const path77 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
20476
21260
  return {
20477
21261
  targetKind: CLOUDFLARE_TARGET_KIND,
20478
21262
  targetName: scriptName,
@@ -20480,7 +21264,7 @@ function mapEventToSignal(event) {
20480
21264
  errorCount: isError ? 1 : 0,
20481
21265
  lastObservedIso: observedAt.toISOString(),
20482
21266
  method,
20483
- ...path74 ? { path: path74 } : {},
21267
+ ...path77 ? { path: path77 } : {},
20484
21268
  ...typeof statusCode === "number" ? { statusCode } : {},
20485
21269
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
20486
21270
  };
@@ -20526,8 +21310,8 @@ function findTaggedWorkerFileNode(graph, workerName) {
20526
21310
  });
20527
21311
  return found;
20528
21312
  }
20529
- function findMatchingRouteNode(graph, serviceName, method, path74) {
20530
- const normalizedPath = normalizePathTemplate(path74);
21313
+ function findMatchingRouteNode(graph, serviceName, method, path77) {
21314
+ const normalizedPath = normalizePathTemplate(path77);
20531
21315
  let found = null;
20532
21316
  graph.forEachNode((id, attrs) => {
20533
21317
  if (found) return;
@@ -20544,10 +21328,10 @@ function createCloudflareResolveTarget(config, graph) {
20544
21328
  return (signal) => {
20545
21329
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
20546
21330
  const scriptName = signal.targetName;
20547
- const { method, path: path74 } = signal;
21331
+ const { method, path: path77 } = signal;
20548
21332
  const resolveRouteGrain = (serviceName, wholeFileId) => {
20549
- if (!method || !path74) return wholeFileId;
20550
- return findMatchingRouteNode(graph, serviceName, method, path74) ?? wholeFileId;
21333
+ if (!method || !path77) return wholeFileId;
21334
+ return findMatchingRouteNode(graph, serviceName, method, path77) ?? wholeFileId;
20551
21335
  };
20552
21336
  const mapping = config.workers?.[scriptName];
20553
21337
  if (mapping) {
@@ -20877,9 +21661,9 @@ function parseCloudRunTargetName(targetName) {
20877
21661
  const secondSep = rest.indexOf(FIELD_SEP2);
20878
21662
  if (secondSep === -1) return null;
20879
21663
  const method = rest.slice(0, secondSep);
20880
- const path74 = rest.slice(secondSep + 1);
20881
- if (!serviceName || !method || !path74) return null;
20882
- return { serviceName, method, path: path74 };
21664
+ const path77 = rest.slice(secondSep + 1);
21665
+ if (!serviceName || !method || !path77) return null;
21666
+ return { serviceName, method, path: path77 };
20883
21667
  }
20884
21668
 
20885
21669
  // src/connectors/cloud-run/map.ts
@@ -20908,14 +21692,14 @@ function mapLogEntryToSignal2(entry) {
20908
21692
  if (!req) return null;
20909
21693
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
20910
21694
  const method = req.requestMethod.toUpperCase();
20911
- const path74 = pathFromRequestUrl2(req.requestUrl);
20912
- if (path74 === null) return null;
21695
+ const path77 = pathFromRequestUrl2(req.requestUrl);
21696
+ if (path77 === null) return null;
20913
21697
  const timestamp = entry.timestamp;
20914
21698
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
20915
21699
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
20916
21700
  return {
20917
21701
  targetKind: CLOUD_RUN_TARGET_KIND,
20918
- targetName: packCloudRunTargetName({ serviceName, method, path: path74 }),
21702
+ targetName: packCloudRunTargetName({ serviceName, method, path: path77 }),
20919
21703
  callCount: 1,
20920
21704
  errorCount: isError ? 1 : 0,
20921
21705
  lastObservedIso: timestamp
@@ -20953,14 +21737,14 @@ function createCloudRunResolveTarget(graph, config) {
20953
21737
  if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
20954
21738
  const identity = parseCloudRunTargetName(signal.targetName);
20955
21739
  if (!identity) return null;
20956
- const { serviceName: gcpServiceName, method, path: path74 } = identity;
20957
- const mappedService = config.serviceMap?.[gcpServiceName] ?? (config.inferServices ? inferServiceName(graph, gcpServiceName, method, path74) ?? void 0 : void 0);
21740
+ const { serviceName: gcpServiceName, method, path: path77 } = identity;
21741
+ const mappedService = config.serviceMap?.[gcpServiceName] ?? (config.inferServices ? inferServiceName(graph, gcpServiceName, method, path77) ?? void 0 : void 0);
20958
21742
  if (mappedService) {
20959
21743
  const routeNodeId = findMatchingRouteNode2(
20960
21744
  graph,
20961
21745
  mappedService,
20962
21746
  method,
20963
- normalizePathTemplate(path74)
21747
+ normalizePathTemplate(path77)
20964
21748
  );
20965
21749
  if (routeNodeId) {
20966
21750
  return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: EdgeType32.CALLS };
@@ -21088,9 +21872,9 @@ function parseGcpLbTargetName(targetName) {
21088
21872
  const secondSep = rest.indexOf(FIELD_SEP3);
21089
21873
  if (secondSep === -1) return null;
21090
21874
  const method = rest.slice(0, secondSep);
21091
- const path74 = rest.slice(secondSep + 1);
21092
- if (!backendServiceName || !method || !path74) return null;
21093
- return { backendServiceName, method, path: path74 };
21875
+ const path77 = rest.slice(secondSep + 1);
21876
+ if (!backendServiceName || !method || !path77) return null;
21877
+ return { backendServiceName, method, path: path77 };
21094
21878
  }
21095
21879
 
21096
21880
  // src/connectors/gcp-lb/map.ts
@@ -21119,14 +21903,14 @@ function mapLogEntryToSignal3(entry) {
21119
21903
  if (!req) return null;
21120
21904
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
21121
21905
  const method = req.requestMethod.toUpperCase();
21122
- const path74 = pathFromRequestUrl3(req.requestUrl);
21123
- if (path74 === null) return null;
21906
+ const path77 = pathFromRequestUrl3(req.requestUrl);
21907
+ if (path77 === null) return null;
21124
21908
  const timestamp = entry.timestamp;
21125
21909
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
21126
21910
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD5;
21127
21911
  return {
21128
21912
  targetKind: GCP_LB_TARGET_KIND,
21129
- targetName: packGcpLbTargetName({ backendServiceName, method, path: path74 }),
21913
+ targetName: packGcpLbTargetName({ backendServiceName, method, path: path77 }),
21130
21914
  callCount: 1,
21131
21915
  errorCount: isError ? 1 : 0,
21132
21916
  lastObservedIso: timestamp
@@ -21164,14 +21948,14 @@ function createGcpLbResolveTarget(graph, config) {
21164
21948
  if (signal.targetKind !== GCP_LB_TARGET_KIND) return null;
21165
21949
  const identity = parseGcpLbTargetName(signal.targetName);
21166
21950
  if (!identity) return null;
21167
- const { backendServiceName, method, path: path74 } = identity;
21168
- const mappedService = config.backendServiceMap?.[backendServiceName] ?? (config.inferServices ? inferServiceName(graph, backendServiceName, method, path74) ?? void 0 : void 0);
21951
+ const { backendServiceName, method, path: path77 } = identity;
21952
+ const mappedService = config.backendServiceMap?.[backendServiceName] ?? (config.inferServices ? inferServiceName(graph, backendServiceName, method, path77) ?? void 0 : void 0);
21169
21953
  if (mappedService) {
21170
21954
  const routeNodeId = findMatchingRouteNode3(
21171
21955
  graph,
21172
21956
  mappedService,
21173
21957
  method,
21174
- normalizePathTemplate(path74)
21958
+ normalizePathTemplate(path77)
21175
21959
  );
21176
21960
  if (routeNodeId) {
21177
21961
  return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: EdgeType33.CALLS };
@@ -22618,6 +23402,35 @@ function registerRoutes(scope, ctx) {
22618
23402
  if (!proj) return;
22619
23403
  handleSse(req, reply, { project: proj.name });
22620
23404
  });
23405
+ scope.get(
23406
+ "/incident-triggers",
23407
+ { config: BEARER_DELEGATED },
23408
+ (req, reply) => {
23409
+ const token = ctx.incidentStreamToken;
23410
+ if (!token || Buffer.byteLength(token, "utf8") < 32 || token === ctx.generalAuthToken || !ctx.incidentReplayDurable) {
23411
+ return reply.code(503).send({ error: "incident trigger stream unavailable" });
23412
+ }
23413
+ const header = req.headers.authorization;
23414
+ const supplied = typeof header === "string" && header.startsWith("Bearer ") ? Buffer.from(header.slice("Bearer ".length), "utf8") : void 0;
23415
+ const expected = Buffer.from(token, "utf8");
23416
+ if (!supplied || supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) {
23417
+ return reply.code(401).send({ error: "unauthorized" });
23418
+ }
23419
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
23420
+ if (!proj) return;
23421
+ const errorsPath = errorsPathFor(proj);
23422
+ if (!errorsPath) return reply.code(503).send({ error: "incident replay unavailable" });
23423
+ let replay;
23424
+ try {
23425
+ const cursor = req.headers["last-event-id"];
23426
+ if (Array.isArray(cursor)) throw new Error("invalid cursor");
23427
+ replay = loadIncidentReplay(errorsPath, cursor);
23428
+ } catch {
23429
+ return reply.code(409).send({ error: "incident replay unavailable" });
23430
+ }
23431
+ handleSse(req, reply, { project: proj.name, incidentOnly: true, incidentReplay: replay });
23432
+ }
23433
+ );
22621
23434
  if (ctx.scope === "project") {
22622
23435
  scope.get("/health", async (req, reply) => {
22623
23436
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
@@ -22903,7 +23716,7 @@ function registerRoutes(scope, ctx) {
22903
23716
  const incidents = epath ? await readErrorEvents(epath) : [];
22904
23717
  let errorEvent;
22905
23718
  if (req.query.errorId) {
22906
- errorEvent = incidents.find((e) => e.id === req.query.errorId);
23719
+ errorEvent = incidents.find((e) => e.id === req.query.errorId) ?? (epath ? await readErrorEventById(epath, req.query.errorId) : void 0);
22907
23720
  if (!errorEvent) {
22908
23721
  return reply.code(404).send({ error: "error event not found", id: req.query.errorId });
22909
23722
  }
@@ -23089,6 +23902,26 @@ function registerRoutes(scope, ctx) {
23089
23902
  }
23090
23903
  return reply.code(202).send({ project: proj.name, ...syncNow() });
23091
23904
  });
23905
+ scope.post("/pr-verdict", async (req, reply) => {
23906
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
23907
+ if (!proj) return;
23908
+ try {
23909
+ const request = parsePrVerdictRequest(req.body);
23910
+ const epath = errorsPathFor(proj);
23911
+ const incidents = epath ? await readErrorEvents(epath) : [];
23912
+ const verdict = await ctx.prVerdict(request, { liveGraph: proj.graph, incidents });
23913
+ return { project: proj.name, ...verdict };
23914
+ } catch (err) {
23915
+ if (!(err instanceof PrVerdictError)) throw err;
23916
+ if (err.status === 429) void reply.header("retry-after", "30");
23917
+ return reply.code(err.status).send({
23918
+ error: err.message,
23919
+ project: proj.name,
23920
+ ...err.stage ? { stage: err.stage } : {},
23921
+ ...err.details !== void 0 ? { details: err.details } : {}
23922
+ });
23923
+ }
23924
+ });
23092
23925
  scope.post("/graph/scan", async (req, reply) => {
23093
23926
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
23094
23927
  if (!proj) return;
@@ -23376,6 +24209,8 @@ async function buildApi(opts) {
23376
24209
  const authToken = opts.authToken ?? env.authToken;
23377
24210
  const trustProxy = opts.trustProxy ?? env.trustProxy;
23378
24211
  const publicRead = opts.publicRead ?? env.publicRead;
24212
+ const incidentStreamToken = opts.incidentStreamToken ?? process.env.NEAT_INCIDENT_STREAM_TOKEN;
24213
+ const incidentReplayDurable = opts.incidentReplayDurable ?? (process.env.NEAT_INCIDENT_REPLAY_DURABLE === "1" && Buffer.byteLength(process.env.NEAT_INCIDENT_DURABLE_TOKEN ?? "", "utf8") >= 32);
23379
24214
  mountBearerAuth(app, {
23380
24215
  token: authToken,
23381
24216
  trustProxy,
@@ -23411,6 +24246,9 @@ async function buildApi(opts) {
23411
24246
  const routeCtx = {
23412
24247
  registry,
23413
24248
  startedAt,
24249
+ incidentStreamToken,
24250
+ incidentReplayDurable,
24251
+ generalAuthToken: authToken,
23414
24252
  scope: "root",
23415
24253
  errorsPathFor,
23416
24254
  staleEventsPathFor,
@@ -23419,6 +24257,7 @@ async function buildApi(opts) {
23419
24257
  singleProject: opts.singleProject?.name,
23420
24258
  connectorsHome: opts.connectorsHome,
23421
24259
  repoSync: opts.repoSync,
24260
+ prVerdict: opts.prVerdict ?? createPrVerdictRunner(),
23422
24261
  runPoll: opts.runPoll ?? runConnectorPoll
23423
24262
  };
23424
24263
  app.get("/health", async () => {
@@ -23596,6 +24435,7 @@ export {
23596
24435
  touchLastSeen,
23597
24436
  removeProject,
23598
24437
  pruneRegistry,
24438
+ maybeStartRepoSync,
23599
24439
  readConnectorsConfig,
23600
24440
  resolveCredential,
23601
24441
  isEnvRef,
@@ -23617,4 +24457,4 @@ export {
23617
24457
  deprovisionConnector,
23618
24458
  buildApi
23619
24459
  };
23620
- //# sourceMappingURL=chunk-DVCGDMTZ.js.map
24460
+ //# sourceMappingURL=chunk-KTJK2PAH.js.map