@neat.is/core 0.4.10 → 0.4.12

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.
package/dist/server.cjs CHANGED
@@ -56,9 +56,9 @@ function mountBearerAuth(app, opts) {
56
56
  const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
57
57
  const publicRead = opts.publicRead === true;
58
58
  app.addHook("preHandler", (req, reply, done) => {
59
- const path38 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
59
+ const path40 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
60
60
  for (const suffix of suffixes) {
61
- if (path38 === suffix || path38.endsWith(suffix)) {
61
+ if (path40 === suffix || path40.endsWith(suffix)) {
62
62
  done();
63
63
  return;
64
64
  }
@@ -184,8 +184,8 @@ function reshapeGrpcRequest(req) {
184
184
  };
185
185
  }
186
186
  function resolveProtoRoot() {
187
- const here = import_node_path34.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
188
- return import_node_path34.default.resolve(here, "..", "proto");
187
+ const here = import_node_path36.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
188
+ return import_node_path36.default.resolve(here, "..", "proto");
189
189
  }
190
190
  function loadTraceService() {
191
191
  const protoRoot = resolveProtoRoot();
@@ -253,13 +253,13 @@ async function startOtelGrpcReceiver(opts) {
253
253
  })
254
254
  };
255
255
  }
256
- var import_node_url, import_node_path34, import_node_crypto2, grpc, protoLoader;
256
+ var import_node_url, import_node_path36, import_node_crypto2, grpc, protoLoader;
257
257
  var init_otel_grpc = __esm({
258
258
  "src/otel-grpc.ts"() {
259
259
  "use strict";
260
260
  init_cjs_shims();
261
261
  import_node_url = require("url");
262
- import_node_path34 = __toESM(require("path"), 1);
262
+ import_node_path36 = __toESM(require("path"), 1);
263
263
  import_node_crypto2 = require("crypto");
264
264
  grpc = __toESM(require("@grpc/grpc-js"), 1);
265
265
  protoLoader = __toESM(require("@grpc/proto-loader"), 1);
@@ -369,10 +369,10 @@ function parseOtlpRequest(body) {
369
369
  return out;
370
370
  }
371
371
  function loadProtoRoot() {
372
- const here = import_node_path35.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
373
- const protoRoot = import_node_path35.default.resolve(here, "..", "proto");
372
+ const here = import_node_path37.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
373
+ const protoRoot = import_node_path37.default.resolve(here, "..", "proto");
374
374
  const root = new import_protobufjs.default.Root();
375
- root.resolvePath = (_origin, target) => import_node_path35.default.resolve(protoRoot, target);
375
+ root.resolvePath = (_origin, target) => import_node_path37.default.resolve(protoRoot, target);
376
376
  root.loadSync(
377
377
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
378
378
  { keepCase: true }
@@ -565,12 +565,12 @@ async function buildOtelReceiver(opts) {
565
565
  };
566
566
  return decorated;
567
567
  }
568
- var import_node_path35, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
568
+ var import_node_path37, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
569
569
  var init_otel = __esm({
570
570
  "src/otel.ts"() {
571
571
  "use strict";
572
572
  init_cjs_shims();
573
- import_node_path35 = __toESM(require("path"), 1);
573
+ import_node_path37 = __toESM(require("path"), 1);
574
574
  import_node_url2 = require("url");
575
575
  import_fastify2 = __toESM(require("fastify"), 1);
576
576
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -586,7 +586,7 @@ var init_otel = __esm({
586
586
 
587
587
  // src/server.ts
588
588
  init_cjs_shims();
589
- var import_node_path37 = __toESM(require("path"), 1);
589
+ var import_node_path39 = __toESM(require("path"), 1);
590
590
 
591
591
  // src/graph.ts
592
592
  init_cjs_shims();
@@ -612,15 +612,330 @@ var import_fastify = __toESM(require("fastify"), 1);
612
612
  var import_cors = __toESM(require("@fastify/cors"), 1);
613
613
  var import_types23 = require("@neat.is/types");
614
614
 
615
+ // src/extend/index.ts
616
+ init_cjs_shims();
617
+ var import_node_fs2 = require("fs");
618
+ var import_node_path2 = __toESM(require("path"), 1);
619
+ var import_node_os = __toESM(require("os"), 1);
620
+ var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
621
+
622
+ // src/installers/package-manager.ts
623
+ init_cjs_shims();
624
+ var import_node_fs = require("fs");
625
+ var import_node_path = __toESM(require("path"), 1);
626
+ var import_node_child_process = require("child_process");
627
+ var LOCKFILE_PRIORITY = [
628
+ { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
629
+ { lockfile: "pnpm-lock.yaml", pm: "pnpm", args: ["install", "--no-summary"] },
630
+ { lockfile: "yarn.lock", pm: "yarn", args: ["install", "--silent"] },
631
+ {
632
+ lockfile: "package-lock.json",
633
+ pm: "npm",
634
+ args: ["install", "--no-audit", "--no-fund", "--prefer-offline"]
635
+ }
636
+ ];
637
+ var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
638
+ async function exists(p) {
639
+ try {
640
+ await import_node_fs.promises.access(p);
641
+ return true;
642
+ } catch {
643
+ return false;
644
+ }
645
+ }
646
+ async function detectPackageManager(serviceDir) {
647
+ let dir = import_node_path.default.resolve(serviceDir);
648
+ const stops = /* @__PURE__ */ new Set();
649
+ for (let i = 0; i < 64; i++) {
650
+ if (stops.has(dir)) break;
651
+ stops.add(dir);
652
+ for (const candidate of LOCKFILE_PRIORITY) {
653
+ const lockPath = import_node_path.default.join(dir, candidate.lockfile);
654
+ if (await exists(lockPath)) {
655
+ return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
656
+ }
657
+ }
658
+ const parent = import_node_path.default.dirname(dir);
659
+ if (parent === dir) break;
660
+ dir = parent;
661
+ }
662
+ return { pm: "npm", cwd: import_node_path.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
663
+ }
664
+ async function runPackageManagerInstall(cmd) {
665
+ return new Promise((resolve) => {
666
+ const child = (0, import_node_child_process.spawn)(cmd.pm, cmd.args, {
667
+ cwd: cmd.cwd,
668
+ // Inherit PATH + HOME so the user's installed managers resolve.
669
+ env: process.env,
670
+ // `false` keeps the parent in control of cleanup if the orchestrator
671
+ // exits before install finishes. Cross-platform-safe.
672
+ shell: false,
673
+ stdio: ["ignore", "ignore", "pipe"]
674
+ });
675
+ let stderr = "";
676
+ child.stderr?.on("data", (chunk) => {
677
+ stderr += chunk.toString("utf8");
678
+ });
679
+ child.on("error", (err) => {
680
+ resolve({
681
+ pm: cmd.pm,
682
+ cwd: cmd.cwd,
683
+ args: cmd.args,
684
+ exitCode: 127,
685
+ stderr: stderr + `
686
+ ${err.message}`
687
+ });
688
+ });
689
+ child.on("close", (code) => {
690
+ resolve({
691
+ pm: cmd.pm,
692
+ cwd: cmd.cwd,
693
+ args: cmd.args,
694
+ exitCode: code ?? 1,
695
+ stderr: stderr.trim()
696
+ });
697
+ });
698
+ });
699
+ }
700
+
701
+ // src/extend/index.ts
702
+ async function fileExists(p) {
703
+ try {
704
+ await import_node_fs2.promises.access(p);
705
+ return true;
706
+ } catch {
707
+ return false;
708
+ }
709
+ }
710
+ async function readPackageJson(scanPath) {
711
+ const pkgPath = import_node_path2.default.join(scanPath, "package.json");
712
+ const raw = await import_node_fs2.promises.readFile(pkgPath, "utf8");
713
+ return JSON.parse(raw);
714
+ }
715
+ async function findHookFiles(scanPath) {
716
+ const entries = await import_node_fs2.promises.readdir(scanPath);
717
+ return entries.filter(
718
+ (e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
719
+ ).sort();
720
+ }
721
+ function extendLogPath() {
722
+ return process.env.NEAT_EXTEND_LOG ?? import_node_path2.default.join(import_node_os.default.homedir(), ".neat", "extend-log.ndjson");
723
+ }
724
+ async function appendExtendLog(entry) {
725
+ const logPath = extendLogPath();
726
+ await import_node_fs2.promises.mkdir(import_node_path2.default.dirname(logPath), { recursive: true });
727
+ await import_node_fs2.promises.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
728
+ }
729
+ function splicedContent(fileContent, snippet2) {
730
+ if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
731
+ return fileContent.replace("__INSTRUMENTATION_BLOCK__", `${snippet2}
732
+ __INSTRUMENTATION_BLOCK__`);
733
+ }
734
+ const lines = fileContent.split("\n");
735
+ let lastPushIdx = -1;
736
+ for (let i = 0; i < lines.length; i++) {
737
+ if (lines[i].includes("instrumentations.push(")) lastPushIdx = i;
738
+ }
739
+ if (lastPushIdx >= 0) {
740
+ lines.splice(lastPushIdx + 1, 0, snippet2);
741
+ return lines.join("\n");
742
+ }
743
+ for (let i = 0; i < lines.length; i++) {
744
+ if (lines[i].includes("new NodeSDK(")) {
745
+ lines.splice(i, 0, snippet2);
746
+ return lines.join("\n");
747
+ }
748
+ }
749
+ return null;
750
+ }
751
+ async function listUninstrumented(ctx) {
752
+ const pkg = await readPackageJson(ctx.scanPath);
753
+ const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
754
+ const results = [];
755
+ for (const [library, installedVersion] of Object.entries(allDeps)) {
756
+ const entry = (0, import_instrumentation_registry.resolve)(library, installedVersion);
757
+ if (!entry) continue;
758
+ if (entry.coverage === "bundled" || entry.coverage === "http-only") continue;
759
+ results.push({
760
+ library,
761
+ coverage: entry.coverage,
762
+ installedVersion,
763
+ instrumentation_package: entry.instrumentation_package,
764
+ package_version: entry.package_version,
765
+ registration: entry.registration,
766
+ notes: entry.notes
767
+ });
768
+ }
769
+ return results;
770
+ }
771
+ function lookupInstrumentation(library, installedVersion) {
772
+ const entry = (0, import_instrumentation_registry.resolve)(library, installedVersion);
773
+ if (!entry) return null;
774
+ return {
775
+ library: entry.library,
776
+ coverage: entry.coverage,
777
+ instrumentation_package: entry.instrumentation_package,
778
+ package_version: entry.package_version,
779
+ registration: entry.registration,
780
+ notes: entry.notes
781
+ };
782
+ }
783
+ async function describeProjectInstrumentation(ctx) {
784
+ const hookFiles = await findHookFiles(ctx.scanPath);
785
+ const envNeat = await fileExists(import_node_path2.default.join(ctx.scanPath, ".env.neat"));
786
+ const registryInstrPackages = new Set(
787
+ (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
788
+ );
789
+ const pkg = await readPackageJson(ctx.scanPath);
790
+ const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
791
+ const installedDeps = {};
792
+ for (const [key, version] of Object.entries(allDeps)) {
793
+ if (key.startsWith("@opentelemetry/") || registryInstrPackages.has(key)) {
794
+ installedDeps[key] = version;
795
+ }
796
+ }
797
+ return { hookFiles, envNeat, installedDeps };
798
+ }
799
+ async function applyExtension(ctx, args, options) {
800
+ const hookFiles = await findHookFiles(ctx.scanPath);
801
+ if (hookFiles.length === 0) {
802
+ throw new Error(
803
+ `No instrumentation hook files found in ${ctx.scanPath}. Run \`neat init\` first.`
804
+ );
805
+ }
806
+ for (const file of hookFiles) {
807
+ const content = await import_node_fs2.promises.readFile(import_node_path2.default.join(ctx.scanPath, file), "utf8");
808
+ if (content.includes(args.registration_snippet)) {
809
+ return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
810
+ }
811
+ }
812
+ const primaryFile = hookFiles[0];
813
+ const primaryPath = import_node_path2.default.join(ctx.scanPath, primaryFile);
814
+ const filesTouched = [];
815
+ const depsAdded = [];
816
+ const pkgPath = import_node_path2.default.join(ctx.scanPath, "package.json");
817
+ const pkg = await readPackageJson(ctx.scanPath);
818
+ if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
819
+ pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
820
+ await import_node_fs2.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
821
+ filesTouched.push("package.json");
822
+ depsAdded.push(`${args.instrumentation_package}@${args.version}`);
823
+ }
824
+ const hookContent = await import_node_fs2.promises.readFile(primaryPath, "utf8");
825
+ const patched = splicedContent(hookContent, args.registration_snippet);
826
+ if (!patched) {
827
+ throw new Error(
828
+ `Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
829
+ );
830
+ }
831
+ await import_node_fs2.promises.writeFile(primaryPath, patched, "utf8");
832
+ filesTouched.push(primaryFile);
833
+ const cmd = await detectPackageManager(ctx.scanPath);
834
+ const installer = options?.runInstall ?? runPackageManagerInstall;
835
+ const install = await installer(cmd);
836
+ const installOutput = install.exitCode === 0 ? `${cmd.pm} install succeeded` : install.stderr || `${cmd.pm} install failed (exit ${install.exitCode})`;
837
+ await appendExtendLog({
838
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
839
+ project: ctx.project,
840
+ library: args.library,
841
+ instrumentation_package: args.instrumentation_package,
842
+ version: args.version,
843
+ registration_snippet: args.registration_snippet,
844
+ filesTouched,
845
+ depsAdded,
846
+ installOutput
847
+ });
848
+ return { library: args.library, filesTouched, depsAdded, installOutput, alreadyApplied: false };
849
+ }
850
+ async function dryRunExtension(ctx, args) {
851
+ const hookFiles = await findHookFiles(ctx.scanPath);
852
+ if (hookFiles.length === 0) {
853
+ return {
854
+ library: args.library,
855
+ filesTouched: [],
856
+ depsToAdd: [],
857
+ packageJsonPatch: {},
858
+ templatePatch: "No hook files found. Run 'neat init' first."
859
+ };
860
+ }
861
+ for (const file of hookFiles) {
862
+ const content = await import_node_fs2.promises.readFile(import_node_path2.default.join(ctx.scanPath, file), "utf8");
863
+ if (content.includes(args.registration_snippet)) {
864
+ return {
865
+ library: args.library,
866
+ filesTouched: [],
867
+ depsToAdd: [],
868
+ packageJsonPatch: {},
869
+ templatePatch: "Already applied \u2014 no changes would be made."
870
+ };
871
+ }
872
+ }
873
+ const primaryFile = hookFiles[0];
874
+ const filesTouched = [];
875
+ const depsToAdd = [];
876
+ let packageJsonPatch = {};
877
+ let templatePatch = "";
878
+ const pkg = await readPackageJson(ctx.scanPath);
879
+ if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
880
+ packageJsonPatch = { dependencies: { [args.instrumentation_package]: args.version } };
881
+ depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
882
+ filesTouched.push("package.json");
883
+ }
884
+ const hookContent = await import_node_fs2.promises.readFile(import_node_path2.default.join(ctx.scanPath, primaryFile), "utf8");
885
+ const patched = splicedContent(hookContent, args.registration_snippet);
886
+ if (patched) {
887
+ filesTouched.push(primaryFile);
888
+ templatePatch = `+ ${args.registration_snippet}`;
889
+ } else {
890
+ templatePatch = "Could not find insertion point in hook file.";
891
+ }
892
+ return { library: args.library, filesTouched, depsToAdd, packageJsonPatch, templatePatch };
893
+ }
894
+ async function rollbackExtension(ctx, args) {
895
+ const logPath = extendLogPath();
896
+ if (!await fileExists(logPath)) {
897
+ return { undone: false, message: "no apply found for library" };
898
+ }
899
+ const raw = await import_node_fs2.promises.readFile(logPath, "utf8");
900
+ const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
901
+ const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
902
+ if (!match) {
903
+ return { undone: false, message: "no apply found for library" };
904
+ }
905
+ const pkgPath = import_node_path2.default.join(ctx.scanPath, "package.json");
906
+ if (await fileExists(pkgPath)) {
907
+ const pkg = await readPackageJson(ctx.scanPath);
908
+ if (pkg.dependencies?.[match.instrumentation_package]) {
909
+ const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
910
+ pkg.dependencies = rest;
911
+ await import_node_fs2.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
912
+ }
913
+ }
914
+ const hookFiles = await findHookFiles(ctx.scanPath);
915
+ for (const file of hookFiles) {
916
+ const filePath = import_node_path2.default.join(ctx.scanPath, file);
917
+ const content = await import_node_fs2.promises.readFile(filePath, "utf8");
918
+ if (content.includes(match.registration_snippet)) {
919
+ const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
920
+ await import_node_fs2.promises.writeFile(filePath, filtered, "utf8");
921
+ break;
922
+ }
923
+ }
924
+ return {
925
+ undone: true,
926
+ message: `rolled back ${match.library} (${match.instrumentation_package})`
927
+ };
928
+ }
929
+
615
930
  // src/divergences.ts
616
931
  init_cjs_shims();
617
932
  var import_types2 = require("@neat.is/types");
618
933
 
619
934
  // src/compat.ts
620
935
  init_cjs_shims();
621
- var import_node_fs = require("fs");
622
- var import_node_os = __toESM(require("os"), 1);
623
- var import_node_path = __toESM(require("path"), 1);
936
+ var import_node_fs3 = require("fs");
937
+ var import_node_os2 = __toESM(require("os"), 1);
938
+ var import_node_path3 = __toESM(require("path"), 1);
624
939
  var import_semver = __toESM(require("semver"), 1);
625
940
 
626
941
  // compat.json
@@ -749,8 +1064,8 @@ var compat_default = {
749
1064
  var bundledMatrix = compat_default;
750
1065
  var mergedMatrix = null;
751
1066
  var remoteLoadAttempted = false;
752
- var REMOTE_CACHE_DIR = import_node_path.default.join(import_node_os.default.homedir(), ".neat");
753
- var REMOTE_CACHE_PATH = import_node_path.default.join(REMOTE_CACHE_DIR, "compat-cache.json");
1067
+ var REMOTE_CACHE_DIR = import_node_path3.default.join(import_node_os2.default.homedir(), ".neat");
1068
+ var REMOTE_CACHE_PATH = import_node_path3.default.join(REMOTE_CACHE_DIR, "compat-cache.json");
754
1069
  var REMOTE_TTL_MS = 24 * 60 * 60 * 1e3;
755
1070
  function engineMeetsThreshold(engineVersion, threshold) {
756
1071
  const e = parseInt(engineVersion, 10);
@@ -861,7 +1176,7 @@ function mergeMatrices(a, b) {
861
1176
  }
862
1177
  async function readRemoteCache(url) {
863
1178
  try {
864
- const raw = await import_node_fs.promises.readFile(REMOTE_CACHE_PATH, "utf8");
1179
+ const raw = await import_node_fs3.promises.readFile(REMOTE_CACHE_PATH, "utf8");
865
1180
  const parsed = JSON.parse(raw);
866
1181
  if (parsed.url !== url) return null;
867
1182
  const age = Date.now() - new Date(parsed.fetchedAt).getTime();
@@ -878,8 +1193,8 @@ async function writeRemoteCache(url, matrix) {
878
1193
  matrix
879
1194
  };
880
1195
  try {
881
- await import_node_fs.promises.mkdir(REMOTE_CACHE_DIR, { recursive: true });
882
- await import_node_fs.promises.writeFile(REMOTE_CACHE_PATH, JSON.stringify(file), "utf8");
1196
+ await import_node_fs3.promises.mkdir(REMOTE_CACHE_DIR, { recursive: true });
1197
+ await import_node_fs3.promises.writeFile(REMOTE_CACHE_PATH, JSON.stringify(file), "utf8");
883
1198
  } catch (err) {
884
1199
  console.warn(`[neat] failed to cache compat matrix: ${err.message}`);
885
1200
  }
@@ -1038,19 +1353,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1038
1353
  function longestIncomingWalk(graph, start, maxDepth) {
1039
1354
  let best = { path: [start], edges: [] };
1040
1355
  const visited = /* @__PURE__ */ new Set([start]);
1041
- function step(node, path38, edges) {
1042
- if (path38.length > best.path.length) {
1043
- best = { path: [...path38], edges: [...edges] };
1356
+ function step(node, path40, edges) {
1357
+ if (path40.length > best.path.length) {
1358
+ best = { path: [...path40], edges: [...edges] };
1044
1359
  }
1045
- if (path38.length - 1 >= maxDepth) return;
1360
+ if (path40.length - 1 >= maxDepth) return;
1046
1361
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1047
1362
  for (const [srcId, edge] of incoming) {
1048
1363
  if (visited.has(srcId)) continue;
1049
1364
  visited.add(srcId);
1050
- path38.push(srcId);
1365
+ path40.push(srcId);
1051
1366
  edges.push(edge);
1052
- step(srcId, path38, edges);
1053
- path38.pop();
1367
+ step(srcId, path40, edges);
1368
+ path40.pop();
1054
1369
  edges.pop();
1055
1370
  visited.delete(srcId);
1056
1371
  }
@@ -1476,8 +1791,8 @@ function computeDivergences(graph, opts = {}) {
1476
1791
 
1477
1792
  // src/policy.ts
1478
1793
  init_cjs_shims();
1479
- var import_node_fs2 = require("fs");
1480
- var import_node_path2 = __toESM(require("path"), 1);
1794
+ var import_node_fs4 = require("fs");
1795
+ var import_node_path4 = __toESM(require("path"), 1);
1481
1796
  var import_types3 = require("@neat.is/types");
1482
1797
 
1483
1798
  // src/events.ts
@@ -1763,7 +2078,7 @@ function evaluateAllPolicies(graph, policies, ctx) {
1763
2078
  async function loadPolicyFile(policyPath) {
1764
2079
  let raw;
1765
2080
  try {
1766
- raw = await import_node_fs2.promises.readFile(policyPath, "utf8");
2081
+ raw = await import_node_fs4.promises.readFile(policyPath, "utf8");
1767
2082
  } catch (err) {
1768
2083
  if (err.code === "ENOENT") return [];
1769
2084
  throw err;
@@ -1784,8 +2099,8 @@ var PolicyViolationsLog = class {
1784
2099
  if (!this.seen) await this.hydrate();
1785
2100
  if (this.seen.has(v.id)) return false;
1786
2101
  this.seen.add(v.id);
1787
- await import_node_fs2.promises.mkdir(import_node_path2.default.dirname(this.path), { recursive: true });
1788
- await import_node_fs2.promises.appendFile(this.path, JSON.stringify(v) + "\n", "utf8");
2102
+ await import_node_fs4.promises.mkdir(import_node_path4.default.dirname(this.path), { recursive: true });
2103
+ await import_node_fs4.promises.appendFile(this.path, JSON.stringify(v) + "\n", "utf8");
1789
2104
  emitNeatEvent({
1790
2105
  type: "policy-violation",
1791
2106
  project: this.project,
@@ -1795,7 +2110,7 @@ var PolicyViolationsLog = class {
1795
2110
  }
1796
2111
  async readAll() {
1797
2112
  try {
1798
- const raw = await import_node_fs2.promises.readFile(this.path, "utf8");
2113
+ const raw = await import_node_fs4.promises.readFile(this.path, "utf8");
1799
2114
  return raw.split("\n").filter(Boolean).map((line) => JSON.parse(line));
1800
2115
  } catch (err) {
1801
2116
  if (err.code === "ENOENT") return [];
@@ -1817,8 +2132,9 @@ init_cjs_shims();
1817
2132
 
1818
2133
  // src/ingest.ts
1819
2134
  init_cjs_shims();
1820
- var import_node_fs3 = require("fs");
1821
- var import_node_path3 = __toESM(require("path"), 1);
2135
+ var import_node_fs5 = require("fs");
2136
+ var import_node_path5 = __toESM(require("path"), 1);
2137
+ var sourceMapJs = __toESM(require("source-map-js"), 1);
1822
2138
  var import_types4 = require("@neat.is/types");
1823
2139
  var HOUR_MS = 60 * 60 * 1e3;
1824
2140
  var DAY_MS = 24 * HOUR_MS;
@@ -1864,6 +2180,14 @@ function warnUnidentifiedSpan(project) {
1864
2180
  `[neatd] span lacked service.name; routed to 'unidentified' in project ${project}; check your OTel SDK config.`
1865
2181
  );
1866
2182
  }
2183
+ var noSourceMapWarnedServices = /* @__PURE__ */ new Set();
2184
+ function warnNoSourceMaps(serviceName) {
2185
+ if (noSourceMapWarnedServices.has(serviceName)) return;
2186
+ noSourceMapWarnedServices.add(serviceName);
2187
+ console.warn(
2188
+ `[neat] ${serviceName}: no .map files found under dist/; observed file edges will land on dist paths, not src. Set sourceMap: true in tsconfig to enable file-level reconciliation.`
2189
+ );
2190
+ }
1867
2191
  function pickAttr(span, ...keys) {
1868
2192
  for (const k of keys) {
1869
2193
  const v = span.attributes[k];
@@ -1906,8 +2230,13 @@ function languageForExt(relPath) {
1906
2230
  return void 0;
1907
2231
  }
1908
2232
  }
1909
- function relPathForRuntimeFile(filepath, serviceNode) {
2233
+ function relPathForRuntimeFile(filepath, serviceNode, scanPath) {
1910
2234
  let p = toPosix(filepath).replace(/^file:\/\//, "");
2235
+ if (scanPath && scanPath.length > 0) {
2236
+ const absRoot = toPosix(import_node_path5.default.resolve(scanPath, serviceNode?.repoPath ?? ""));
2237
+ const anchor = absRoot.endsWith("/") ? absRoot : `${absRoot}/`;
2238
+ if (p.startsWith(anchor)) return p.slice(anchor.length);
2239
+ }
1911
2240
  const root = serviceNode?.repoPath;
1912
2241
  if (root && root !== "." && root.length > 0) {
1913
2242
  const rootPosix = toPosix(root);
@@ -1924,16 +2253,65 @@ function relPathForRuntimeFile(filepath, serviceNode) {
1924
2253
  p = p.replace(/^[A-Za-z]:/, "").replace(/^\/+/, "");
1925
2254
  return p.length > 0 ? p : null;
1926
2255
  }
1927
- function callSiteFromSpan(span, serviceNode) {
2256
+ var sourceMapCache = /* @__PURE__ */ new Map();
2257
+ function resolveDistToSrc(absFilepath, line) {
2258
+ if (!absFilepath.endsWith(".js")) return null;
2259
+ let entry = sourceMapCache.get(absFilepath);
2260
+ if (entry === void 0) {
2261
+ entry = null;
2262
+ const mapPath = `${absFilepath}.map`;
2263
+ try {
2264
+ if ((0, import_node_fs5.existsSync)(mapPath)) {
2265
+ const raw = JSON.parse((0, import_node_fs5.readFileSync)(mapPath, "utf8"));
2266
+ const consumer = new sourceMapJs.SourceMapConsumer(raw);
2267
+ entry = { consumer, dir: import_node_path5.default.dirname(mapPath) };
2268
+ }
2269
+ } catch {
2270
+ entry = null;
2271
+ }
2272
+ sourceMapCache.set(absFilepath, entry);
2273
+ }
2274
+ if (!entry) return null;
2275
+ try {
2276
+ const pos = entry.consumer.originalPositionFor({
2277
+ line: line !== void 0 && Number.isFinite(line) ? line : 1,
2278
+ column: 0
2279
+ });
2280
+ if (!pos || !pos.source) return null;
2281
+ const root = entry.consumer.sourceRoot ?? "";
2282
+ const resolved = import_node_path5.default.resolve(entry.dir, root, pos.source);
2283
+ return { filepath: resolved, ...pos.line ? { line: pos.line } : {} };
2284
+ } catch {
2285
+ return null;
2286
+ }
2287
+ }
2288
+ function callSiteFromSpan(span, serviceNode, scanPath) {
1928
2289
  const filepath = span.attributes[CODE_FILEPATH_ATTR];
1929
2290
  if (typeof filepath !== "string" || filepath.length === 0) return null;
1930
- const relPath = relPathForRuntimeFile(filepath, serviceNode);
1931
- if (!relPath) return null;
1932
2291
  const linenoRaw = span.attributes[CODE_LINENO_ATTR];
1933
- const line = typeof linenoRaw === "number" && Number.isFinite(linenoRaw) ? linenoRaw : void 0;
2292
+ let line = typeof linenoRaw === "number" && Number.isFinite(linenoRaw) ? linenoRaw : void 0;
2293
+ const abs = toPosix(filepath).replace(/^file:\/\//, "");
2294
+ const resolved = resolveDistToSrc(abs, line);
2295
+ let effectivePath = filepath;
2296
+ let originalRelPath;
2297
+ if (resolved) {
2298
+ originalRelPath = relPathForRuntimeFile(filepath, serviceNode, scanPath) ?? void 0;
2299
+ effectivePath = resolved.filepath;
2300
+ if (resolved.line !== void 0) line = resolved.line;
2301
+ }
2302
+ const relPath = relPathForRuntimeFile(effectivePath, serviceNode, scanPath);
2303
+ if (!relPath) return null;
2304
+ if (!resolved && abs.endsWith(".js") && relPath.startsWith("dist/") && serviceNode?.name) {
2305
+ warnNoSourceMaps(serviceNode.name);
2306
+ }
1934
2307
  const fnRaw = span.attributes[CODE_FUNCTION_ATTR];
1935
2308
  const fn = typeof fnRaw === "string" && fnRaw.length > 0 ? fnRaw : void 0;
1936
- return { relPath, ...line !== void 0 ? { line } : {}, ...fn ? { fn } : {} };
2309
+ return {
2310
+ relPath,
2311
+ ...line !== void 0 ? { line } : {},
2312
+ ...fn ? { fn } : {},
2313
+ ...originalRelPath && originalRelPath !== relPath ? { originalRelPath } : {}
2314
+ };
1937
2315
  }
1938
2316
  function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
1939
2317
  const fileNodeId = (0, import_types4.fileId)(serviceName, callSite.relPath);
@@ -1945,6 +2323,7 @@ function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
1945
2323
  service: serviceName,
1946
2324
  path: callSite.relPath,
1947
2325
  ...language ? { language } : {},
2326
+ ...callSite.originalRelPath ? { originalPath: callSite.originalRelPath } : {},
1948
2327
  discoveredVia: "otel"
1949
2328
  };
1950
2329
  graph.addNode(fileNodeId, node);
@@ -1970,6 +2349,12 @@ function makeInferredEdgeId(type, source, target) {
1970
2349
  }
1971
2350
  var INFERRED_CONFIDENCE = 0.6;
1972
2351
  var STITCH_MAX_DEPTH = 2;
2352
+ var WIRE_SPAN_KIND_CLIENT = 3;
2353
+ var WIRE_SPAN_KIND_PRODUCER = 4;
2354
+ function spanMintsObservedEdge(kind) {
2355
+ if (kind === void 0 || kind === 0) return true;
2356
+ return kind === WIRE_SPAN_KIND_CLIENT || kind === WIRE_SPAN_KIND_PRODUCER;
2357
+ }
1973
2358
  var PARENT_SPAN_CACHE_SIZE = 1e4;
1974
2359
  var PARENT_SPAN_CACHE_TTL_MS = 5 * 60 * 1e3;
1975
2360
  var parentSpanCache = /* @__PURE__ */ new Map();
@@ -2076,7 +2461,7 @@ function ensureFrontierNode(graph, host, ts) {
2076
2461
  graph.addNode(id, node);
2077
2462
  return id;
2078
2463
  }
2079
- function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
2464
+ function upsertObservedEdge(graph, type, source, target, ts, isError = false, evidence) {
2080
2465
  if (!graph.hasNode(source) || !graph.hasNode(target)) return null;
2081
2466
  const id = makeObservedEdgeId(type, source, target);
2082
2467
  if (graph.hasEdge(id)) {
@@ -2113,7 +2498,10 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
2113
2498
  confidence: (0, import_types4.confidenceForObservedSignal)(signal),
2114
2499
  lastObserved: ts,
2115
2500
  callCount: 1,
2116
- signal
2501
+ signal,
2502
+ // Call-site evidence from span code.* semconv (file-awareness.md §4 + §6).
2503
+ // Only set when code.filepath was present on the span — never fabricated.
2504
+ ...evidence ? { evidence } : {}
2117
2505
  };
2118
2506
  graph.addEdgeWithKey(id, source, target, edge);
2119
2507
  return { edge, created: true };
@@ -2158,8 +2546,8 @@ function upsertInferredEdge(graph, type, source, target, ts) {
2158
2546
  graph.addEdgeWithKey(id, source, target, edge);
2159
2547
  }
2160
2548
  async function appendErrorEvent(ctx, ev) {
2161
- await import_node_fs3.promises.mkdir(import_node_path3.default.dirname(ctx.errorsPath), { recursive: true });
2162
- await import_node_fs3.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
2549
+ await import_node_fs5.promises.mkdir(import_node_path5.default.dirname(ctx.errorsPath), { recursive: true });
2550
+ await import_node_fs5.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
2163
2551
  }
2164
2552
  function sanitizeAttributes(attrs) {
2165
2553
  const out = {};
@@ -2180,12 +2568,14 @@ async function handleSpan(ctx, span) {
2180
2568
  const isError = span.statusCode === 2;
2181
2569
  cacheSpanService(span, nowMs);
2182
2570
  const sourceServiceNode = ctx.graph.getNodeAttributes(sourceId);
2183
- const callSite = callSiteFromSpan(span, sourceServiceNode);
2571
+ const callSite = callSiteFromSpan(span, sourceServiceNode, ctx.scanPath);
2184
2572
  const observedSource = () => callSite ? ensureObservedFileNode(ctx.graph, span.service, sourceId, callSite) : sourceId;
2573
+ const callSiteEvidence = callSite ? { file: callSite.relPath, ...callSite.line !== void 0 ? { line: callSite.line } : {} } : void 0;
2185
2574
  let affectedNode = sourceId;
2575
+ const mintsFromCallerSide = spanMintsObservedEdge(span.kind);
2186
2576
  if (span.dbSystem) {
2187
2577
  const host = pickAddress(span);
2188
- if (host) {
2578
+ if (mintsFromCallerSide && host) {
2189
2579
  ensureDatabaseNode(ctx.graph, host, span.dbSystem);
2190
2580
  const targetId = (0, import_types4.databaseId)(host);
2191
2581
  const result = upsertObservedEdge(
@@ -2194,14 +2584,15 @@ async function handleSpan(ctx, span) {
2194
2584
  observedSource(),
2195
2585
  targetId,
2196
2586
  ts,
2197
- isError
2587
+ isError,
2588
+ callSiteEvidence
2198
2589
  );
2199
2590
  if (result) affectedNode = targetId;
2200
2591
  }
2201
2592
  } else {
2202
2593
  const host = pickAddress(span);
2203
2594
  let resolvedViaAddress = false;
2204
- if (host && host !== span.service) {
2595
+ if (mintsFromCallerSide && host && host !== span.service) {
2205
2596
  const targetId = resolveServiceId(ctx.graph, host, env);
2206
2597
  if (targetId && targetId !== sourceId) {
2207
2598
  upsertObservedEdge(
@@ -2210,7 +2601,8 @@ async function handleSpan(ctx, span) {
2210
2601
  observedSource(),
2211
2602
  targetId,
2212
2603
  ts,
2213
- isError
2604
+ isError,
2605
+ callSiteEvidence
2214
2606
  );
2215
2607
  affectedNode = targetId;
2216
2608
  resolvedViaAddress = true;
@@ -2222,7 +2614,8 @@ async function handleSpan(ctx, span) {
2222
2614
  observedSource(),
2223
2615
  frontierNodeId,
2224
2616
  ts,
2225
- isError
2617
+ isError,
2618
+ callSiteEvidence
2226
2619
  );
2227
2620
  affectedNode = frontierNodeId;
2228
2621
  resolvedViaAddress = true;
@@ -2380,13 +2773,13 @@ async function markStaleEdges(graph, options = {}) {
2380
2773
  return { count: events.length, events };
2381
2774
  }
2382
2775
  async function appendStaleEvents(staleEventsPath, events) {
2383
- await import_node_fs3.promises.mkdir(import_node_path3.default.dirname(staleEventsPath), { recursive: true });
2776
+ await import_node_fs5.promises.mkdir(import_node_path5.default.dirname(staleEventsPath), { recursive: true });
2384
2777
  const lines = events.map((e) => JSON.stringify(e)).join("\n") + "\n";
2385
- await import_node_fs3.promises.appendFile(staleEventsPath, lines, "utf8");
2778
+ await import_node_fs5.promises.appendFile(staleEventsPath, lines, "utf8");
2386
2779
  }
2387
2780
  async function readStaleEvents(staleEventsPath) {
2388
2781
  try {
2389
- const raw = await import_node_fs3.promises.readFile(staleEventsPath, "utf8");
2782
+ const raw = await import_node_fs5.promises.readFile(staleEventsPath, "utf8");
2390
2783
  return raw.split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
2391
2784
  } catch (err) {
2392
2785
  if (err.code === "ENOENT") return [];
@@ -2420,7 +2813,7 @@ function startStalenessLoop(graph, options = {}) {
2420
2813
  }
2421
2814
  async function readErrorEvents(errorsPath) {
2422
2815
  try {
2423
- const raw = await import_node_fs3.promises.readFile(errorsPath, "utf8");
2816
+ const raw = await import_node_fs5.promises.readFile(errorsPath, "utf8");
2424
2817
  return raw.split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
2425
2818
  } catch (err) {
2426
2819
  if (err.code === "ENOENT") return [];
@@ -2452,16 +2845,16 @@ function mergeSnapshot(graph, snapshot) {
2452
2845
 
2453
2846
  // src/extract/services.ts
2454
2847
  init_cjs_shims();
2455
- var import_node_fs8 = require("fs");
2456
- var import_node_path8 = __toESM(require("path"), 1);
2848
+ var import_node_fs10 = require("fs");
2849
+ var import_node_path10 = __toESM(require("path"), 1);
2457
2850
  var import_ignore = __toESM(require("ignore"), 1);
2458
2851
  var import_minimatch2 = require("minimatch");
2459
2852
  var import_types6 = require("@neat.is/types");
2460
2853
 
2461
2854
  // src/extract/shared.ts
2462
2855
  init_cjs_shims();
2463
- var import_node_fs4 = require("fs");
2464
- var import_node_path4 = __toESM(require("path"), 1);
2856
+ var import_node_fs6 = require("fs");
2857
+ var import_node_path6 = __toESM(require("path"), 1);
2465
2858
  var import_yaml = require("yaml");
2466
2859
  var import_types5 = require("@neat.is/types");
2467
2860
  var SERVICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".py"]);
@@ -2493,7 +2886,7 @@ async function isPythonVenvDir(dir) {
2493
2886
  const cached = PYVENV_MARKER_CACHE.get(dir);
2494
2887
  if (cached !== void 0) return cached;
2495
2888
  try {
2496
- const stat = await import_node_fs4.promises.stat(import_node_path4.default.join(dir, "pyvenv.cfg"));
2889
+ const stat = await import_node_fs6.promises.stat(import_node_path6.default.join(dir, "pyvenv.cfg"));
2497
2890
  const ok = stat.isFile();
2498
2891
  PYVENV_MARKER_CACHE.set(dir, ok);
2499
2892
  return ok;
@@ -2503,7 +2896,7 @@ async function isPythonVenvDir(dir) {
2503
2896
  }
2504
2897
  }
2505
2898
  function isConfigFile(name) {
2506
- const ext = import_node_path4.default.extname(name);
2899
+ const ext = import_node_path6.default.extname(name);
2507
2900
  if (CONFIG_FILE_EXTENSIONS.has(ext)) return { match: true, fileType: ext.slice(1) };
2508
2901
  if (name === ".env" || name.startsWith(".env.")) {
2509
2902
  if (isEnvTemplateFile(name)) return { match: false, fileType: "" };
@@ -2611,16 +3004,16 @@ function cleanVersion(raw) {
2611
3004
  return raw.replace(/^[\^~><=v\s]+/, "").trim() || void 0;
2612
3005
  }
2613
3006
  async function readJson(filePath) {
2614
- const raw = await import_node_fs4.promises.readFile(filePath, "utf8");
3007
+ const raw = await import_node_fs6.promises.readFile(filePath, "utf8");
2615
3008
  return JSON.parse(raw);
2616
3009
  }
2617
3010
  async function readYaml(filePath) {
2618
- const raw = await import_node_fs4.promises.readFile(filePath, "utf8");
3011
+ const raw = await import_node_fs6.promises.readFile(filePath, "utf8");
2619
3012
  return (0, import_yaml.parse)(raw);
2620
3013
  }
2621
- async function exists(p) {
3014
+ async function exists2(p) {
2622
3015
  try {
2623
- await import_node_fs4.promises.access(p);
3016
+ await import_node_fs6.promises.access(p);
2624
3017
  return true;
2625
3018
  } catch {
2626
3019
  return false;
@@ -2629,8 +3022,8 @@ async function exists(p) {
2629
3022
 
2630
3023
  // src/extract/python.ts
2631
3024
  init_cjs_shims();
2632
- var import_node_fs5 = require("fs");
2633
- var import_node_path5 = __toESM(require("path"), 1);
3025
+ var import_node_fs7 = require("fs");
3026
+ var import_node_path7 = __toESM(require("path"), 1);
2634
3027
  var import_smol_toml = require("smol-toml");
2635
3028
  var REQUIREMENT_LINE = /^\s*([A-Za-z0-9_.-]+)(?:\[[^\]]*\])?\s*(?:(==)\s*([A-Za-z0-9_.+-]+))?/;
2636
3029
  function parseRequirementsTxt(content) {
@@ -2663,25 +3056,25 @@ function depsFromPyProject(pyproject) {
2663
3056
  return out;
2664
3057
  }
2665
3058
  async function discoverPythonService(serviceDir) {
2666
- const pyprojectPath = import_node_path5.default.join(serviceDir, "pyproject.toml");
2667
- const requirementsPath = import_node_path5.default.join(serviceDir, "requirements.txt");
2668
- const setupPath = import_node_path5.default.join(serviceDir, "setup.py");
2669
- const hasPyproject = await exists(pyprojectPath);
2670
- const hasRequirements = await exists(requirementsPath);
2671
- const hasSetup = await exists(setupPath);
3059
+ const pyprojectPath = import_node_path7.default.join(serviceDir, "pyproject.toml");
3060
+ const requirementsPath = import_node_path7.default.join(serviceDir, "requirements.txt");
3061
+ const setupPath = import_node_path7.default.join(serviceDir, "setup.py");
3062
+ const hasPyproject = await exists2(pyprojectPath);
3063
+ const hasRequirements = await exists2(requirementsPath);
3064
+ const hasSetup = await exists2(setupPath);
2672
3065
  if (!hasPyproject && !hasRequirements && !hasSetup) return null;
2673
- let name = import_node_path5.default.basename(serviceDir);
3066
+ let name = import_node_path7.default.basename(serviceDir);
2674
3067
  let version;
2675
3068
  const dependencies = {};
2676
3069
  if (hasPyproject) {
2677
- const raw = await import_node_fs5.promises.readFile(pyprojectPath, "utf8");
3070
+ const raw = await import_node_fs7.promises.readFile(pyprojectPath, "utf8");
2678
3071
  const pyproject = (0, import_smol_toml.parse)(raw);
2679
3072
  name = pyproject.project?.name ?? pyproject.tool?.poetry?.name ?? name;
2680
3073
  version = pyproject.project?.version ?? pyproject.tool?.poetry?.version ?? void 0;
2681
3074
  Object.assign(dependencies, depsFromPyProject(pyproject));
2682
3075
  }
2683
3076
  if (hasRequirements) {
2684
- const raw = await import_node_fs5.promises.readFile(requirementsPath, "utf8");
3077
+ const raw = await import_node_fs7.promises.readFile(requirementsPath, "utf8");
2685
3078
  Object.assign(dependencies, parseRequirementsTxt(raw));
2686
3079
  }
2687
3080
  return { name, version, dependencies };
@@ -2696,17 +3089,17 @@ function pythonToPackage(service) {
2696
3089
 
2697
3090
  // src/extract/owners.ts
2698
3091
  init_cjs_shims();
2699
- var import_node_fs6 = require("fs");
2700
- var import_node_path6 = __toESM(require("path"), 1);
3092
+ var import_node_fs8 = require("fs");
3093
+ var import_node_path8 = __toESM(require("path"), 1);
2701
3094
  var import_minimatch = require("minimatch");
2702
3095
  async function loadCodeowners(scanPath) {
2703
3096
  const candidates = [
2704
- import_node_path6.default.join(scanPath, "CODEOWNERS"),
2705
- import_node_path6.default.join(scanPath, ".github", "CODEOWNERS")
3097
+ import_node_path8.default.join(scanPath, "CODEOWNERS"),
3098
+ import_node_path8.default.join(scanPath, ".github", "CODEOWNERS")
2706
3099
  ];
2707
3100
  for (const file of candidates) {
2708
- if (await exists(file)) {
2709
- const raw = await import_node_fs6.promises.readFile(file, "utf8");
3101
+ if (await exists2(file)) {
3102
+ const raw = await import_node_fs8.promises.readFile(file, "utf8");
2710
3103
  return parseCodeowners(raw);
2711
3104
  }
2712
3105
  }
@@ -2724,7 +3117,7 @@ function parseCodeowners(raw) {
2724
3117
  return { rules };
2725
3118
  }
2726
3119
  function matchOwner(file, repoPath) {
2727
- const normalized = repoPath.split(import_node_path6.default.sep).join("/");
3120
+ const normalized = repoPath.split(import_node_path8.default.sep).join("/");
2728
3121
  for (const rule of file.rules) {
2729
3122
  if (matchesPattern(rule.pattern, normalized)) return rule.owners;
2730
3123
  }
@@ -2740,8 +3133,8 @@ function matchesPattern(rawPattern, repoPath) {
2740
3133
  return false;
2741
3134
  }
2742
3135
  async function readPackageJsonAuthor(serviceDir) {
2743
- const pkgPath = import_node_path6.default.join(serviceDir, "package.json");
2744
- if (!await exists(pkgPath)) return null;
3136
+ const pkgPath = import_node_path8.default.join(serviceDir, "package.json");
3137
+ if (!await exists2(pkgPath)) return null;
2745
3138
  try {
2746
3139
  const pkg = await readJson(pkgPath);
2747
3140
  if (!pkg.author) return null;
@@ -2763,8 +3156,8 @@ async function computeServiceOwner(codeowners, repoPath, serviceDir) {
2763
3156
 
2764
3157
  // src/extract/errors.ts
2765
3158
  init_cjs_shims();
2766
- var import_node_fs7 = require("fs");
2767
- var import_node_path7 = __toESM(require("path"), 1);
3159
+ var import_node_fs9 = require("fs");
3160
+ var import_node_path9 = __toESM(require("path"), 1);
2768
3161
  var sink = [];
2769
3162
  function recordExtractionError(producer, file, err) {
2770
3163
  const e = err instanceof Error ? err : new Error(String(err));
@@ -2783,9 +3176,9 @@ function drainExtractionErrors() {
2783
3176
  }
2784
3177
  async function writeExtractionErrors(errors, errorsPath) {
2785
3178
  if (errors.length === 0) return;
2786
- await import_node_fs7.promises.mkdir(import_node_path7.default.dirname(errorsPath), { recursive: true });
3179
+ await import_node_fs9.promises.mkdir(import_node_path9.default.dirname(errorsPath), { recursive: true });
2787
3180
  const lines = errors.map((e) => JSON.stringify(e)).join("\n") + "\n";
2788
- await import_node_fs7.promises.appendFile(errorsPath, lines, "utf8");
3181
+ await import_node_fs9.promises.appendFile(errorsPath, lines, "utf8");
2789
3182
  }
2790
3183
  var droppedSink = [];
2791
3184
  function noteExtractedDropped(edge) {
@@ -2800,9 +3193,9 @@ function isRejectedLogEnabled() {
2800
3193
  }
2801
3194
  async function writeRejectedExtracted(drops, rejectedPath) {
2802
3195
  if (drops.length === 0) return;
2803
- await import_node_fs7.promises.mkdir(import_node_path7.default.dirname(rejectedPath), { recursive: true });
3196
+ await import_node_fs9.promises.mkdir(import_node_path9.default.dirname(rejectedPath), { recursive: true });
2804
3197
  const lines = drops.map((d) => JSON.stringify({ ...d, ts: (/* @__PURE__ */ new Date()).toISOString() })).join("\n") + "\n";
2805
- await import_node_fs7.promises.appendFile(rejectedPath, lines, "utf8");
3198
+ await import_node_fs9.promises.appendFile(rejectedPath, lines, "utf8");
2806
3199
  }
2807
3200
 
2808
3201
  // src/extract/services.ts
@@ -2821,21 +3214,21 @@ function workspaceGlobs(pkg) {
2821
3214
  return null;
2822
3215
  }
2823
3216
  async function loadGitignore(scanPath) {
2824
- const gitignorePath = import_node_path8.default.join(scanPath, ".gitignore");
2825
- if (!await exists(gitignorePath)) return null;
2826
- const raw = await import_node_fs8.promises.readFile(gitignorePath, "utf8");
3217
+ const gitignorePath = import_node_path10.default.join(scanPath, ".gitignore");
3218
+ if (!await exists2(gitignorePath)) return null;
3219
+ const raw = await import_node_fs10.promises.readFile(gitignorePath, "utf8");
2827
3220
  return (0, import_ignore.default)().add(raw);
2828
3221
  }
2829
3222
  async function walkDirs(start, scanPath, options, visit) {
2830
3223
  async function recurse(current, depth) {
2831
3224
  if (depth > options.maxDepth) return;
2832
- const entries = await import_node_fs8.promises.readdir(current, { withFileTypes: true }).catch(() => []);
3225
+ const entries = await import_node_fs10.promises.readdir(current, { withFileTypes: true }).catch(() => []);
2833
3226
  for (const entry of entries) {
2834
3227
  if (!entry.isDirectory()) continue;
2835
3228
  if (IGNORED_DIRS.has(entry.name)) continue;
2836
- const child = import_node_path8.default.join(current, entry.name);
3229
+ const child = import_node_path10.default.join(current, entry.name);
2837
3230
  if (options.ig) {
2838
- const rel = import_node_path8.default.relative(scanPath, child).split(import_node_path8.default.sep).join("/");
3231
+ const rel = import_node_path10.default.relative(scanPath, child).split(import_node_path10.default.sep).join("/");
2839
3232
  if (rel && options.ig.ignores(rel + "/")) continue;
2840
3233
  }
2841
3234
  if (await isPythonVenvDir(child)) continue;
@@ -2851,8 +3244,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
2851
3244
  for (const raw of globs) {
2852
3245
  const pattern = raw.replace(/^\.\//, "");
2853
3246
  if (!pattern.includes("*")) {
2854
- const candidate = import_node_path8.default.join(scanPath, pattern);
2855
- if (await exists(import_node_path8.default.join(candidate, "package.json"))) found.add(candidate);
3247
+ const candidate = import_node_path10.default.join(scanPath, pattern);
3248
+ if (await exists2(import_node_path10.default.join(candidate, "package.json"))) found.add(candidate);
2856
3249
  continue;
2857
3250
  }
2858
3251
  const segments = pattern.split("/");
@@ -2861,13 +3254,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
2861
3254
  if (seg.includes("*")) break;
2862
3255
  staticSegments.push(seg);
2863
3256
  }
2864
- const start = import_node_path8.default.join(scanPath, ...staticSegments);
2865
- if (!await exists(start)) continue;
3257
+ const start = import_node_path10.default.join(scanPath, ...staticSegments);
3258
+ if (!await exists2(start)) continue;
2866
3259
  const hasDoubleStar = pattern.includes("**");
2867
3260
  const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
2868
3261
  await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
2869
- const rel = import_node_path8.default.relative(scanPath, dir).split(import_node_path8.default.sep).join("/");
2870
- if ((0, import_minimatch2.minimatch)(rel, pattern) && await exists(import_node_path8.default.join(dir, "package.json"))) {
3262
+ const rel = import_node_path10.default.relative(scanPath, dir).split(import_node_path10.default.sep).join("/");
3263
+ if ((0, import_minimatch2.minimatch)(rel, pattern) && await exists2(import_node_path10.default.join(dir, "package.json"))) {
2871
3264
  found.add(dir);
2872
3265
  }
2873
3266
  });
@@ -2887,13 +3280,13 @@ function detectJsFramework(pkg) {
2887
3280
  return void 0;
2888
3281
  }
2889
3282
  async function discoverNodeService(scanPath, dir) {
2890
- const pkgPath = import_node_path8.default.join(dir, "package.json");
2891
- if (!await exists(pkgPath)) return null;
3283
+ const pkgPath = import_node_path10.default.join(dir, "package.json");
3284
+ if (!await exists2(pkgPath)) return null;
2892
3285
  let pkg;
2893
3286
  try {
2894
3287
  pkg = await readJson(pkgPath);
2895
3288
  } catch (err) {
2896
- recordExtractionError("services", import_node_path8.default.relative(scanPath, pkgPath), err);
3289
+ recordExtractionError("services", import_node_path10.default.relative(scanPath, pkgPath), err);
2897
3290
  return null;
2898
3291
  }
2899
3292
  if (!pkg.name) return null;
@@ -2905,7 +3298,7 @@ async function discoverNodeService(scanPath, dir) {
2905
3298
  language: "javascript",
2906
3299
  version: pkg.version,
2907
3300
  dependencies: pkg.dependencies ?? {},
2908
- repoPath: import_node_path8.default.relative(scanPath, dir),
3301
+ repoPath: import_node_path10.default.relative(scanPath, dir),
2909
3302
  ...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {},
2910
3303
  ...framework ? { framework } : {}
2911
3304
  };
@@ -2922,20 +3315,20 @@ async function discoverPyService(scanPath, dir) {
2922
3315
  language: "python",
2923
3316
  version: py.version,
2924
3317
  dependencies: py.dependencies,
2925
- repoPath: import_node_path8.default.relative(scanPath, dir)
3318
+ repoPath: import_node_path10.default.relative(scanPath, dir)
2926
3319
  };
2927
3320
  return { pkg, dir, node };
2928
3321
  }
2929
3322
  async function discoverServices(scanPath) {
2930
- const rootPkgPath = import_node_path8.default.join(scanPath, "package.json");
3323
+ const rootPkgPath = import_node_path10.default.join(scanPath, "package.json");
2931
3324
  let rootPkg = null;
2932
- if (await exists(rootPkgPath)) {
3325
+ if (await exists2(rootPkgPath)) {
2933
3326
  try {
2934
3327
  rootPkg = await readJson(rootPkgPath);
2935
3328
  } catch (err) {
2936
3329
  recordExtractionError(
2937
3330
  "services workspaces",
2938
- import_node_path8.default.relative(scanPath, rootPkgPath),
3331
+ import_node_path10.default.relative(scanPath, rootPkgPath),
2939
3332
  err
2940
3333
  );
2941
3334
  }
@@ -2952,9 +3345,9 @@ async function discoverServices(scanPath) {
2952
3345
  scanPath,
2953
3346
  { maxDepth: parseScanDepth(), ig },
2954
3347
  async (dir) => {
2955
- if (await exists(import_node_path8.default.join(dir, "package.json"))) {
3348
+ if (await exists2(import_node_path10.default.join(dir, "package.json"))) {
2956
3349
  candidateDirs.push(dir);
2957
- } else if (await exists(import_node_path8.default.join(dir, "pyproject.toml")) || await exists(import_node_path8.default.join(dir, "requirements.txt")) || await exists(import_node_path8.default.join(dir, "setup.py"))) {
3350
+ } else if (await exists2(import_node_path10.default.join(dir, "pyproject.toml")) || await exists2(import_node_path10.default.join(dir, "requirements.txt")) || await exists2(import_node_path10.default.join(dir, "setup.py"))) {
2958
3351
  candidateDirs.push(dir);
2959
3352
  }
2960
3353
  }
@@ -2968,8 +3361,8 @@ async function discoverServices(scanPath) {
2968
3361
  if (!service) continue;
2969
3362
  const existingDir = seen.get(service.node.name);
2970
3363
  if (existingDir !== void 0) {
2971
- const a = import_node_path8.default.relative(scanPath, existingDir) || ".";
2972
- const b = import_node_path8.default.relative(scanPath, dir) || ".";
3364
+ const a = import_node_path10.default.relative(scanPath, existingDir) || ".";
3365
+ const b = import_node_path10.default.relative(scanPath, dir) || ".";
2973
3366
  console.warn(
2974
3367
  `[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
2975
3368
  );
@@ -3006,8 +3399,8 @@ function addServiceNodes(graph, services) {
3006
3399
 
3007
3400
  // src/extract/aliases.ts
3008
3401
  init_cjs_shims();
3009
- var import_node_path9 = __toESM(require("path"), 1);
3010
- var import_node_fs9 = require("fs");
3402
+ var import_node_path11 = __toESM(require("path"), 1);
3403
+ var import_node_fs11 = require("fs");
3011
3404
  var import_yaml2 = require("yaml");
3012
3405
  var import_types7 = require("@neat.is/types");
3013
3406
  var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
@@ -3034,15 +3427,15 @@ function indexServicesByName(services) {
3034
3427
  const map = /* @__PURE__ */ new Map();
3035
3428
  for (const s of services) {
3036
3429
  map.set(s.node.name, s.node.id);
3037
- map.set(import_node_path9.default.basename(s.dir), s.node.id);
3430
+ map.set(import_node_path11.default.basename(s.dir), s.node.id);
3038
3431
  }
3039
3432
  return map;
3040
3433
  }
3041
3434
  async function collectComposeAliases(graph, scanPath, serviceIndex) {
3042
3435
  let composePath = null;
3043
3436
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
3044
- const abs = import_node_path9.default.join(scanPath, name);
3045
- if (await exists(abs)) {
3437
+ const abs = import_node_path11.default.join(scanPath, name);
3438
+ if (await exists2(abs)) {
3046
3439
  composePath = abs;
3047
3440
  break;
3048
3441
  }
@@ -3054,7 +3447,7 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
3054
3447
  } catch (err) {
3055
3448
  recordExtractionError(
3056
3449
  "aliases compose",
3057
- import_node_path9.default.relative(scanPath, composePath),
3450
+ import_node_path11.default.relative(scanPath, composePath),
3058
3451
  err
3059
3452
  );
3060
3453
  return;
@@ -3097,11 +3490,11 @@ function parseDockerfileLabels(content) {
3097
3490
  }
3098
3491
  async function collectDockerfileAliases(graph, services) {
3099
3492
  for (const service of services) {
3100
- const dockerfilePath = import_node_path9.default.join(service.dir, "Dockerfile");
3101
- if (!await exists(dockerfilePath)) continue;
3493
+ const dockerfilePath = import_node_path11.default.join(service.dir, "Dockerfile");
3494
+ if (!await exists2(dockerfilePath)) continue;
3102
3495
  let content;
3103
3496
  try {
3104
- content = await import_node_fs9.promises.readFile(dockerfilePath, "utf8");
3497
+ content = await import_node_fs11.promises.readFile(dockerfilePath, "utf8");
3105
3498
  } catch (err) {
3106
3499
  recordExtractionError("aliases dockerfile", dockerfilePath, err);
3107
3500
  continue;
@@ -3113,15 +3506,15 @@ async function collectDockerfileAliases(graph, services) {
3113
3506
  async function walkYamlFiles(start, depth = 0, max = 5) {
3114
3507
  if (depth > max) return [];
3115
3508
  const out = [];
3116
- const entries = await import_node_fs9.promises.readdir(start, { withFileTypes: true }).catch(() => []);
3509
+ const entries = await import_node_fs11.promises.readdir(start, { withFileTypes: true }).catch(() => []);
3117
3510
  for (const entry of entries) {
3118
3511
  if (entry.isDirectory()) {
3119
3512
  if (IGNORED_DIRS.has(entry.name)) continue;
3120
- const child = import_node_path9.default.join(start, entry.name);
3513
+ const child = import_node_path11.default.join(start, entry.name);
3121
3514
  if (await isPythonVenvDir(child)) continue;
3122
3515
  out.push(...await walkYamlFiles(child, depth + 1, max));
3123
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path9.default.extname(entry.name))) {
3124
- out.push(import_node_path9.default.join(start, entry.name));
3516
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path11.default.extname(entry.name))) {
3517
+ out.push(import_node_path11.default.join(start, entry.name));
3125
3518
  }
3126
3519
  }
3127
3520
  return out;
@@ -3148,7 +3541,7 @@ function k8sServiceTarget(doc, byName) {
3148
3541
  async function collectK8sAliases(graph, scanPath, serviceIndex) {
3149
3542
  const files = await walkYamlFiles(scanPath);
3150
3543
  for (const file of files) {
3151
- const content = await import_node_fs9.promises.readFile(file, "utf8");
3544
+ const content = await import_node_fs11.promises.readFile(file, "utf8");
3152
3545
  let docs;
3153
3546
  try {
3154
3547
  docs = (0, import_yaml2.parseAllDocuments)(content).map((d) => d.toJSON());
@@ -3173,15 +3566,112 @@ async function addServiceAliases(graph, scanPath, services) {
3173
3566
 
3174
3567
  // src/extract/databases/index.ts
3175
3568
  init_cjs_shims();
3176
- var import_node_path17 = __toESM(require("path"), 1);
3569
+ var import_node_path20 = __toESM(require("path"), 1);
3570
+ var import_types9 = require("@neat.is/types");
3571
+
3572
+ // src/extract/calls/shared.ts
3573
+ init_cjs_shims();
3574
+ var import_node_fs12 = require("fs");
3575
+ var import_node_path12 = __toESM(require("path"), 1);
3177
3576
  var import_types8 = require("@neat.is/types");
3577
+ async function walkSourceFiles(dir) {
3578
+ const out = [];
3579
+ async function walk(current) {
3580
+ const entries = await import_node_fs12.promises.readdir(current, { withFileTypes: true }).catch(() => []);
3581
+ for (const entry of entries) {
3582
+ const full = import_node_path12.default.join(current, entry.name);
3583
+ if (entry.isDirectory()) {
3584
+ if (IGNORED_DIRS.has(entry.name)) continue;
3585
+ if (await isPythonVenvDir(full)) continue;
3586
+ await walk(full);
3587
+ } else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path12.default.extname(entry.name))) {
3588
+ out.push(full);
3589
+ }
3590
+ }
3591
+ }
3592
+ await walk(dir);
3593
+ return out;
3594
+ }
3595
+ async function loadSourceFiles(dir) {
3596
+ const paths = await walkSourceFiles(dir);
3597
+ const out = [];
3598
+ for (const p of paths) {
3599
+ try {
3600
+ const content = await import_node_fs12.promises.readFile(p, "utf8");
3601
+ out.push({ path: p, content });
3602
+ } catch {
3603
+ }
3604
+ }
3605
+ return out;
3606
+ }
3607
+ function lineOf(text, needle) {
3608
+ const idx = text.indexOf(needle);
3609
+ if (idx < 0) return 1;
3610
+ return text.slice(0, idx).split("\n").length;
3611
+ }
3612
+ function snippet(text, line) {
3613
+ const lines = text.split("\n");
3614
+ return (lines[line - 1] ?? "").trim();
3615
+ }
3616
+ function toPosix2(p) {
3617
+ return p.split("\\").join("/");
3618
+ }
3619
+ function languageForPath(relPath) {
3620
+ switch (import_node_path12.default.extname(relPath).toLowerCase()) {
3621
+ case ".py":
3622
+ return "python";
3623
+ case ".ts":
3624
+ case ".tsx":
3625
+ return "typescript";
3626
+ case ".js":
3627
+ case ".jsx":
3628
+ case ".mjs":
3629
+ case ".cjs":
3630
+ return "javascript";
3631
+ default:
3632
+ return void 0;
3633
+ }
3634
+ }
3635
+ function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
3636
+ let nodesAdded = 0;
3637
+ let edgesAdded = 0;
3638
+ const fileNodeId = (0, import_types8.fileId)(serviceName, relPath);
3639
+ if (!graph.hasNode(fileNodeId)) {
3640
+ const language = languageForPath(relPath);
3641
+ const node = {
3642
+ id: fileNodeId,
3643
+ type: import_types8.NodeType.FileNode,
3644
+ service: serviceName,
3645
+ path: relPath,
3646
+ ...language ? { language } : {},
3647
+ discoveredVia: "static"
3648
+ };
3649
+ graph.addNode(fileNodeId, node);
3650
+ nodesAdded++;
3651
+ }
3652
+ const containsId = (0, import_types8.extractedEdgeId)(serviceNodeId, fileNodeId, import_types8.EdgeType.CONTAINS);
3653
+ if (!graph.hasEdge(containsId)) {
3654
+ const edge = {
3655
+ id: containsId,
3656
+ source: serviceNodeId,
3657
+ target: fileNodeId,
3658
+ type: import_types8.EdgeType.CONTAINS,
3659
+ provenance: import_types8.Provenance.EXTRACTED,
3660
+ confidence: (0, import_types8.confidenceForExtracted)("structural"),
3661
+ evidence: { file: relPath }
3662
+ };
3663
+ graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
3664
+ edgesAdded++;
3665
+ }
3666
+ return { fileNodeId, nodesAdded, edgesAdded };
3667
+ }
3178
3668
 
3179
3669
  // src/extract/databases/db-config-yaml.ts
3180
3670
  init_cjs_shims();
3181
- var import_node_path10 = __toESM(require("path"), 1);
3671
+ var import_node_path13 = __toESM(require("path"), 1);
3182
3672
  async function parse(serviceDir) {
3183
- const yamlPath = import_node_path10.default.join(serviceDir, "db-config.yaml");
3184
- if (!await exists(yamlPath)) return [];
3673
+ const yamlPath = import_node_path13.default.join(serviceDir, "db-config.yaml");
3674
+ if (!await exists2(yamlPath)) return [];
3185
3675
  const raw = await readYaml(yamlPath);
3186
3676
  return [
3187
3677
  {
@@ -3198,13 +3688,13 @@ var dbConfigYamlParser = { name: "db-config.yaml", parse };
3198
3688
 
3199
3689
  // src/extract/databases/dotenv.ts
3200
3690
  init_cjs_shims();
3201
- var import_node_fs11 = require("fs");
3202
- var import_node_path12 = __toESM(require("path"), 1);
3691
+ var import_node_fs14 = require("fs");
3692
+ var import_node_path15 = __toESM(require("path"), 1);
3203
3693
 
3204
3694
  // src/extract/databases/shared.ts
3205
3695
  init_cjs_shims();
3206
- var import_node_fs10 = require("fs");
3207
- var import_node_path11 = __toESM(require("path"), 1);
3696
+ var import_node_fs13 = require("fs");
3697
+ var import_node_path14 = __toESM(require("path"), 1);
3208
3698
  function schemeToEngine(scheme) {
3209
3699
  const s = scheme.toLowerCase().split("+")[0];
3210
3700
  switch (s) {
@@ -3243,14 +3733,14 @@ function parseConnectionString(url) {
3243
3733
  }
3244
3734
  async function readIfExists(filePath) {
3245
3735
  try {
3246
- return await import_node_fs10.promises.readFile(filePath, "utf8");
3736
+ return await import_node_fs13.promises.readFile(filePath, "utf8");
3247
3737
  } catch {
3248
3738
  return null;
3249
3739
  }
3250
3740
  }
3251
3741
  async function findFirst(serviceDir, candidates) {
3252
3742
  for (const rel of candidates) {
3253
- const abs = import_node_path11.default.join(serviceDir, rel);
3743
+ const abs = import_node_path14.default.join(serviceDir, rel);
3254
3744
  const content = await readIfExists(abs);
3255
3745
  if (content !== null) return abs;
3256
3746
  }
@@ -3301,15 +3791,15 @@ function parseDotenvLine(line) {
3301
3791
  return { key, value };
3302
3792
  }
3303
3793
  async function parse2(serviceDir) {
3304
- const entries = await import_node_fs11.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
3794
+ const entries = await import_node_fs14.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
3305
3795
  const configs = [];
3306
3796
  const seen = /* @__PURE__ */ new Set();
3307
3797
  for (const entry of entries) {
3308
3798
  if (!entry.isFile()) continue;
3309
3799
  const match = isConfigFile(entry.name);
3310
3800
  if (!match.match || match.fileType !== "env") continue;
3311
- const filePath = import_node_path12.default.join(serviceDir, entry.name);
3312
- const content = await import_node_fs11.promises.readFile(filePath, "utf8");
3801
+ const filePath = import_node_path15.default.join(serviceDir, entry.name);
3802
+ const content = await import_node_fs14.promises.readFile(filePath, "utf8");
3313
3803
  for (const line of content.split("\n")) {
3314
3804
  const parsed = parseDotenvLine(line);
3315
3805
  if (!parsed) continue;
@@ -3328,9 +3818,9 @@ var dotenvParser = { name: ".env", parse: parse2 };
3328
3818
 
3329
3819
  // src/extract/databases/prisma.ts
3330
3820
  init_cjs_shims();
3331
- var import_node_path13 = __toESM(require("path"), 1);
3821
+ var import_node_path16 = __toESM(require("path"), 1);
3332
3822
  async function parse3(serviceDir) {
3333
- const schemaPath = import_node_path13.default.join(serviceDir, "prisma", "schema.prisma");
3823
+ const schemaPath = import_node_path16.default.join(serviceDir, "prisma", "schema.prisma");
3334
3824
  const content = await readIfExists(schemaPath);
3335
3825
  if (!content) return [];
3336
3826
  const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
@@ -3462,11 +3952,11 @@ var knexParser = { name: "knex", parse: parse5 };
3462
3952
 
3463
3953
  // src/extract/databases/ormconfig.ts
3464
3954
  init_cjs_shims();
3465
- var import_node_path14 = __toESM(require("path"), 1);
3955
+ var import_node_path17 = __toESM(require("path"), 1);
3466
3956
  async function parse6(serviceDir) {
3467
3957
  for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
3468
- const abs = import_node_path14.default.join(serviceDir, candidate);
3469
- if (!await exists(abs)) continue;
3958
+ const abs = import_node_path17.default.join(serviceDir, candidate);
3959
+ if (!await exists2(abs)) continue;
3470
3960
  const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
3471
3961
  const entries = Array.isArray(raw) ? raw : [raw];
3472
3962
  const out = [];
@@ -3525,10 +4015,10 @@ var typeormParser = { name: "typeorm", parse: parse7 };
3525
4015
 
3526
4016
  // src/extract/databases/sequelize.ts
3527
4017
  init_cjs_shims();
3528
- var import_node_path15 = __toESM(require("path"), 1);
4018
+ var import_node_path18 = __toESM(require("path"), 1);
3529
4019
  async function parse8(serviceDir) {
3530
- const configPath = import_node_path15.default.join(serviceDir, "config", "config.json");
3531
- if (!await exists(configPath)) return [];
4020
+ const configPath = import_node_path18.default.join(serviceDir, "config", "config.json");
4021
+ if (!await exists2(configPath)) return [];
3532
4022
  const raw = await readJson(configPath);
3533
4023
  const out = [];
3534
4024
  const seen = /* @__PURE__ */ new Set();
@@ -3554,7 +4044,7 @@ var sequelizeParser = { name: "sequelize", parse: parse8 };
3554
4044
 
3555
4045
  // src/extract/databases/docker-compose.ts
3556
4046
  init_cjs_shims();
3557
- var import_node_path16 = __toESM(require("path"), 1);
4047
+ var import_node_path19 = __toESM(require("path"), 1);
3558
4048
  function portFromService(svc) {
3559
4049
  for (const raw of svc.ports ?? []) {
3560
4050
  const str = String(raw);
@@ -3581,8 +4071,8 @@ function databaseFromEnv(svc) {
3581
4071
  }
3582
4072
  async function parse9(serviceDir) {
3583
4073
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
3584
- const abs = import_node_path16.default.join(serviceDir, name);
3585
- if (!await exists(abs)) continue;
4074
+ const abs = import_node_path19.default.join(serviceDir, name);
4075
+ if (!await exists2(abs)) continue;
3586
4076
  const raw = await readYaml(abs);
3587
4077
  if (!raw?.services) return [];
3588
4078
  const out = [];
@@ -3622,8 +4112,8 @@ function compatibleDriversFor(engine) {
3622
4112
  }
3623
4113
  function toDatabaseNode(config) {
3624
4114
  return {
3625
- id: (0, import_types8.databaseId)(config.host),
3626
- type: import_types8.NodeType.DatabaseNode,
4115
+ id: (0, import_types9.databaseId)(config.host),
4116
+ type: import_types9.NodeType.DatabaseNode,
3627
4117
  name: config.database || config.host,
3628
4118
  engine: config.engine,
3629
4119
  engineVersion: config.engineVersion,
@@ -3753,19 +4243,24 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
3753
4243
  discoveredVia: mergedDiscoveredVia
3754
4244
  });
3755
4245
  }
4246
+ const relConfigFile = toPosix2(import_node_path20.default.relative(service.dir, config.sourceFile));
4247
+ const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
4248
+ graph,
4249
+ service.pkg.name,
4250
+ service.node.id,
4251
+ relConfigFile
4252
+ );
4253
+ nodesAdded += fn;
4254
+ edgesAdded += fe;
4255
+ const evidenceFile = toPosix2(import_node_path20.default.relative(scanPath, config.sourceFile));
3756
4256
  const edge = {
3757
- id: (0, import_types5.extractedEdgeId)(service.node.id, dbNode.id, import_types8.EdgeType.CONNECTS_TO),
3758
- source: service.node.id,
4257
+ id: (0, import_types5.extractedEdgeId)(fileNodeId, dbNode.id, import_types9.EdgeType.CONNECTS_TO),
4258
+ source: fileNodeId,
3759
4259
  target: dbNode.id,
3760
- type: import_types8.EdgeType.CONNECTS_TO,
3761
- provenance: import_types8.Provenance.EXTRACTED,
3762
- confidence: (0, import_types8.confidenceForExtracted)("structural"),
3763
- // ADR-032 / #140 — every EXTRACTED edge carries evidence.file.
3764
- // Ghost-edge cleanup keys retirement on this; the conditional
3765
- // sourceFile spread that used to live here was a v0.1.x leftover.
3766
- evidence: {
3767
- file: import_node_path17.default.relative(scanPath, config.sourceFile).split(import_node_path17.default.sep).join("/")
3768
- }
4260
+ type: import_types9.EdgeType.CONNECTS_TO,
4261
+ provenance: import_types9.Provenance.EXTRACTED,
4262
+ confidence: (0, import_types9.confidenceForExtracted)("structural"),
4263
+ evidence: { file: evidenceFile }
3769
4264
  };
3770
4265
  if (!graph.hasEdge(edge.id)) {
3771
4266
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -3791,15 +4286,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
3791
4286
 
3792
4287
  // src/extract/configs.ts
3793
4288
  init_cjs_shims();
3794
- var import_node_fs12 = require("fs");
3795
- var import_node_path18 = __toESM(require("path"), 1);
3796
- var import_types9 = require("@neat.is/types");
4289
+ var import_node_fs15 = require("fs");
4290
+ var import_node_path21 = __toESM(require("path"), 1);
4291
+ var import_types10 = require("@neat.is/types");
3797
4292
  async function walkConfigFiles(dir) {
3798
4293
  const out = [];
3799
4294
  async function walk(current) {
3800
- const entries = await import_node_fs12.promises.readdir(current, { withFileTypes: true });
4295
+ const entries = await import_node_fs15.promises.readdir(current, { withFileTypes: true });
3801
4296
  for (const entry of entries) {
3802
- const full = import_node_path18.default.join(current, entry.name);
4297
+ const full = import_node_path21.default.join(current, entry.name);
3803
4298
  if (entry.isDirectory()) {
3804
4299
  if (IGNORED_DIRS.has(entry.name)) continue;
3805
4300
  if (await isPythonVenvDir(full)) continue;
@@ -3818,26 +4313,35 @@ async function addConfigNodes(graph, services, scanPath) {
3818
4313
  for (const service of services) {
3819
4314
  const configFiles = await walkConfigFiles(service.dir);
3820
4315
  for (const file of configFiles) {
3821
- const relPath = import_node_path18.default.relative(scanPath, file);
4316
+ const relPath = import_node_path21.default.relative(scanPath, file);
3822
4317
  const node = {
3823
- id: (0, import_types9.configId)(relPath),
3824
- type: import_types9.NodeType.ConfigNode,
3825
- name: import_node_path18.default.basename(file),
4318
+ id: (0, import_types10.configId)(relPath),
4319
+ type: import_types10.NodeType.ConfigNode,
4320
+ name: import_node_path21.default.basename(file),
3826
4321
  path: relPath,
3827
- fileType: isConfigFile(import_node_path18.default.basename(file)).fileType
4322
+ fileType: isConfigFile(import_node_path21.default.basename(file)).fileType
3828
4323
  };
3829
4324
  if (!graph.hasNode(node.id)) {
3830
4325
  graph.addNode(node.id, node);
3831
4326
  nodesAdded++;
3832
4327
  }
4328
+ const relToService = toPosix2(import_node_path21.default.relative(service.dir, file));
4329
+ const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
4330
+ graph,
4331
+ service.pkg.name,
4332
+ service.node.id,
4333
+ relToService
4334
+ );
4335
+ nodesAdded += fn;
4336
+ edgesAdded += fe;
3833
4337
  const edge = {
3834
- id: (0, import_types5.extractedEdgeId)(service.node.id, node.id, import_types9.EdgeType.CONFIGURED_BY),
3835
- source: service.node.id,
4338
+ id: (0, import_types5.extractedEdgeId)(fileNodeId, node.id, import_types10.EdgeType.CONFIGURED_BY),
4339
+ source: fileNodeId,
3836
4340
  target: node.id,
3837
- type: import_types9.EdgeType.CONFIGURED_BY,
3838
- provenance: import_types9.Provenance.EXTRACTED,
3839
- confidence: (0, import_types9.confidenceForExtracted)("structural"),
3840
- evidence: { file: relPath.split(import_node_path18.default.sep).join("/") }
4341
+ type: import_types10.EdgeType.CONFIGURED_BY,
4342
+ provenance: import_types10.Provenance.EXTRACTED,
4343
+ confidence: (0, import_types10.confidenceForExtracted)("structural"),
4344
+ evidence: { file: relPath.split(import_node_path21.default.sep).join("/") }
3841
4345
  };
3842
4346
  if (!graph.hasEdge(edge.id)) {
3843
4347
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -3854,110 +4358,11 @@ var import_types16 = require("@neat.is/types");
3854
4358
 
3855
4359
  // src/extract/calls/http.ts
3856
4360
  init_cjs_shims();
3857
- var import_node_path20 = __toESM(require("path"), 1);
4361
+ var import_node_path22 = __toESM(require("path"), 1);
3858
4362
  var import_tree_sitter = __toESM(require("tree-sitter"), 1);
3859
4363
  var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
3860
4364
  var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
3861
4365
  var import_types11 = require("@neat.is/types");
3862
-
3863
- // src/extract/calls/shared.ts
3864
- init_cjs_shims();
3865
- var import_node_fs13 = require("fs");
3866
- var import_node_path19 = __toESM(require("path"), 1);
3867
- var import_types10 = require("@neat.is/types");
3868
- async function walkSourceFiles(dir) {
3869
- const out = [];
3870
- async function walk(current) {
3871
- const entries = await import_node_fs13.promises.readdir(current, { withFileTypes: true }).catch(() => []);
3872
- for (const entry of entries) {
3873
- const full = import_node_path19.default.join(current, entry.name);
3874
- if (entry.isDirectory()) {
3875
- if (IGNORED_DIRS.has(entry.name)) continue;
3876
- if (await isPythonVenvDir(full)) continue;
3877
- await walk(full);
3878
- } else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path19.default.extname(entry.name))) {
3879
- out.push(full);
3880
- }
3881
- }
3882
- }
3883
- await walk(dir);
3884
- return out;
3885
- }
3886
- async function loadSourceFiles(dir) {
3887
- const paths = await walkSourceFiles(dir);
3888
- const out = [];
3889
- for (const p of paths) {
3890
- try {
3891
- const content = await import_node_fs13.promises.readFile(p, "utf8");
3892
- out.push({ path: p, content });
3893
- } catch {
3894
- }
3895
- }
3896
- return out;
3897
- }
3898
- function lineOf(text, needle) {
3899
- const idx = text.indexOf(needle);
3900
- if (idx < 0) return 1;
3901
- return text.slice(0, idx).split("\n").length;
3902
- }
3903
- function snippet(text, line) {
3904
- const lines = text.split("\n");
3905
- return (lines[line - 1] ?? "").trim();
3906
- }
3907
- function toPosix2(p) {
3908
- return p.split("\\").join("/");
3909
- }
3910
- function languageForPath(relPath) {
3911
- switch (import_node_path19.default.extname(relPath).toLowerCase()) {
3912
- case ".py":
3913
- return "python";
3914
- case ".ts":
3915
- case ".tsx":
3916
- return "typescript";
3917
- case ".js":
3918
- case ".jsx":
3919
- case ".mjs":
3920
- case ".cjs":
3921
- return "javascript";
3922
- default:
3923
- return void 0;
3924
- }
3925
- }
3926
- function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
3927
- let nodesAdded = 0;
3928
- let edgesAdded = 0;
3929
- const fileNodeId = (0, import_types10.fileId)(serviceName, relPath);
3930
- if (!graph.hasNode(fileNodeId)) {
3931
- const language = languageForPath(relPath);
3932
- const node = {
3933
- id: fileNodeId,
3934
- type: import_types10.NodeType.FileNode,
3935
- service: serviceName,
3936
- path: relPath,
3937
- ...language ? { language } : {},
3938
- discoveredVia: "static"
3939
- };
3940
- graph.addNode(fileNodeId, node);
3941
- nodesAdded++;
3942
- }
3943
- const containsId = (0, import_types10.extractedEdgeId)(serviceNodeId, fileNodeId, import_types10.EdgeType.CONTAINS);
3944
- if (!graph.hasEdge(containsId)) {
3945
- const edge = {
3946
- id: containsId,
3947
- source: serviceNodeId,
3948
- target: fileNodeId,
3949
- type: import_types10.EdgeType.CONTAINS,
3950
- provenance: import_types10.Provenance.EXTRACTED,
3951
- confidence: (0, import_types10.confidenceForExtracted)("structural"),
3952
- evidence: { file: relPath }
3953
- };
3954
- graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
3955
- edgesAdded++;
3956
- }
3957
- return { fileNodeId, nodesAdded, edgesAdded };
3958
- }
3959
-
3960
- // src/extract/calls/http.ts
3961
4366
  var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
3962
4367
  var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
3963
4368
  function isInsideJsxExternalLink(node) {
@@ -3985,8 +4390,14 @@ function collectStringLiterals(node, out) {
3985
4390
  if (child) collectStringLiterals(child, out);
3986
4391
  }
3987
4392
  }
4393
+ var PARSE_CHUNK = 16384;
4394
+ function parseSource(parser, source) {
4395
+ return parser.parse(
4396
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK)
4397
+ );
4398
+ }
3988
4399
  function callsFromSource(source, parser, knownHosts) {
3989
- const tree = parser.parse(source);
4400
+ const tree = parseSource(parser, source);
3990
4401
  const literals = [];
3991
4402
  collectStringLiterals(tree.rootNode, literals);
3992
4403
  const out = [];
@@ -4016,9 +4427,9 @@ async function addHttpCallEdges(graph, services) {
4016
4427
  const knownHosts = /* @__PURE__ */ new Set();
4017
4428
  const hostToNodeId = /* @__PURE__ */ new Map();
4018
4429
  for (const service of services) {
4019
- knownHosts.add(import_node_path20.default.basename(service.dir));
4430
+ knownHosts.add(import_node_path22.default.basename(service.dir));
4020
4431
  knownHosts.add(service.pkg.name);
4021
- hostToNodeId.set(import_node_path20.default.basename(service.dir), service.node.id);
4432
+ hostToNodeId.set(import_node_path22.default.basename(service.dir), service.node.id);
4022
4433
  hostToNodeId.set(service.pkg.name, service.node.id);
4023
4434
  }
4024
4435
  let nodesAdded = 0;
@@ -4028,7 +4439,7 @@ async function addHttpCallEdges(graph, services) {
4028
4439
  const seen = /* @__PURE__ */ new Set();
4029
4440
  for (const file of files) {
4030
4441
  if (isTestPath(file.path)) continue;
4031
- const parser = import_node_path20.default.extname(file.path) === ".py" ? pyParser : jsParser;
4442
+ const parser = import_node_path22.default.extname(file.path) === ".py" ? pyParser : jsParser;
4032
4443
  let sites;
4033
4444
  try {
4034
4445
  sites = callsFromSource(file.content, parser, knownHosts);
@@ -4037,7 +4448,7 @@ async function addHttpCallEdges(graph, services) {
4037
4448
  continue;
4038
4449
  }
4039
4450
  if (sites.length === 0) continue;
4040
- const relFile = toPosix2(import_node_path20.default.relative(service.dir, file.path));
4451
+ const relFile = toPosix2(import_node_path22.default.relative(service.dir, file.path));
4041
4452
  for (const site of sites) {
4042
4453
  const targetId = hostToNodeId.get(site.host);
4043
4454
  if (!targetId || targetId === service.node.id) continue;
@@ -4091,7 +4502,7 @@ async function addHttpCallEdges(graph, services) {
4091
4502
 
4092
4503
  // src/extract/calls/kafka.ts
4093
4504
  init_cjs_shims();
4094
- var import_node_path21 = __toESM(require("path"), 1);
4505
+ var import_node_path23 = __toESM(require("path"), 1);
4095
4506
  var import_types12 = require("@neat.is/types");
4096
4507
  var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
4097
4508
  var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
@@ -4122,7 +4533,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
4122
4533
  // tier (ADR-066).
4123
4534
  confidenceKind: "verified-call-site",
4124
4535
  evidence: {
4125
- file: import_node_path21.default.relative(serviceDir, file.path),
4536
+ file: import_node_path23.default.relative(serviceDir, file.path),
4126
4537
  line,
4127
4538
  snippet: snippet(file.content, line)
4128
4539
  }
@@ -4135,7 +4546,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
4135
4546
 
4136
4547
  // src/extract/calls/redis.ts
4137
4548
  init_cjs_shims();
4138
- var import_node_path22 = __toESM(require("path"), 1);
4549
+ var import_node_path24 = __toESM(require("path"), 1);
4139
4550
  var import_types13 = require("@neat.is/types");
4140
4551
  var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
4141
4552
  function redisEndpointsFromFile(file, serviceDir) {
@@ -4158,7 +4569,7 @@ function redisEndpointsFromFile(file, serviceDir) {
4158
4569
  // support tier (ADR-066).
4159
4570
  confidenceKind: "url-with-structural-support",
4160
4571
  evidence: {
4161
- file: import_node_path22.default.relative(serviceDir, file.path),
4572
+ file: import_node_path24.default.relative(serviceDir, file.path),
4162
4573
  line,
4163
4574
  snippet: snippet(file.content, line)
4164
4575
  }
@@ -4169,7 +4580,7 @@ function redisEndpointsFromFile(file, serviceDir) {
4169
4580
 
4170
4581
  // src/extract/calls/aws.ts
4171
4582
  init_cjs_shims();
4172
- var import_node_path23 = __toESM(require("path"), 1);
4583
+ var import_node_path25 = __toESM(require("path"), 1);
4173
4584
  var import_types14 = require("@neat.is/types");
4174
4585
  var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
4175
4586
  var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
@@ -4203,7 +4614,7 @@ function awsEndpointsFromFile(file, serviceDir) {
4203
4614
  // (ADR-066).
4204
4615
  confidenceKind: "verified-call-site",
4205
4616
  evidence: {
4206
- file: import_node_path23.default.relative(serviceDir, file.path),
4617
+ file: import_node_path25.default.relative(serviceDir, file.path),
4207
4618
  line,
4208
4619
  snippet: snippet(file.content, line)
4209
4620
  }
@@ -4228,13 +4639,14 @@ function awsEndpointsFromFile(file, serviceDir) {
4228
4639
 
4229
4640
  // src/extract/calls/grpc.ts
4230
4641
  init_cjs_shims();
4231
- var import_node_path24 = __toESM(require("path"), 1);
4642
+ var import_node_path26 = __toESM(require("path"), 1);
4232
4643
  var import_types15 = require("@neat.is/types");
4233
4644
  var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
4234
4645
  var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
4235
4646
  var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
4236
4647
  function isLikelyAddress(value) {
4237
4648
  if (!value) return false;
4649
+ if (/\s/.test(value) || value.startsWith("{")) return false;
4238
4650
  return /:\d{2,5}$/.test(value) || value.includes(".");
4239
4651
  }
4240
4652
  function normaliseForMatch(s) {
@@ -4286,7 +4698,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
4286
4698
  // tier (ADR-066).
4287
4699
  confidenceKind: "verified-call-site",
4288
4700
  evidence: {
4289
- file: import_node_path24.default.relative(serviceDir, file.path),
4701
+ file: import_node_path26.default.relative(serviceDir, file.path),
4290
4702
  line,
4291
4703
  snippet: snippet(file.content, line)
4292
4704
  }
@@ -4397,7 +4809,7 @@ init_cjs_shims();
4397
4809
 
4398
4810
  // src/extract/infra/docker-compose.ts
4399
4811
  init_cjs_shims();
4400
- var import_node_path25 = __toESM(require("path"), 1);
4812
+ var import_node_path27 = __toESM(require("path"), 1);
4401
4813
  var import_types18 = require("@neat.is/types");
4402
4814
 
4403
4815
  // src/extract/infra/shared.ts
@@ -4435,7 +4847,7 @@ function dependsOnList(value) {
4435
4847
  }
4436
4848
  function serviceNameToServiceNode(name, services) {
4437
4849
  for (const s of services) {
4438
- if (s.node.name === name || import_node_path25.default.basename(s.dir) === name) return s.node.id;
4850
+ if (s.node.name === name || import_node_path27.default.basename(s.dir) === name) return s.node.id;
4439
4851
  }
4440
4852
  return null;
4441
4853
  }
@@ -4444,8 +4856,8 @@ async function addComposeInfra(graph, scanPath, services) {
4444
4856
  let edgesAdded = 0;
4445
4857
  let composePath = null;
4446
4858
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
4447
- const abs = import_node_path25.default.join(scanPath, name);
4448
- if (await exists(abs)) {
4859
+ const abs = import_node_path27.default.join(scanPath, name);
4860
+ if (await exists2(abs)) {
4449
4861
  composePath = abs;
4450
4862
  break;
4451
4863
  }
@@ -4457,13 +4869,13 @@ async function addComposeInfra(graph, scanPath, services) {
4457
4869
  } catch (err) {
4458
4870
  recordExtractionError(
4459
4871
  "infra docker-compose",
4460
- import_node_path25.default.relative(scanPath, composePath),
4872
+ import_node_path27.default.relative(scanPath, composePath),
4461
4873
  err
4462
4874
  );
4463
4875
  return { nodesAdded, edgesAdded };
4464
4876
  }
4465
4877
  if (!compose?.services) return { nodesAdded, edgesAdded };
4466
- const evidenceFile = import_node_path25.default.relative(scanPath, composePath).split(import_node_path25.default.sep).join("/");
4878
+ const evidenceFile = import_node_path27.default.relative(scanPath, composePath).split(import_node_path27.default.sep).join("/");
4467
4879
  const composeNameToNodeId = /* @__PURE__ */ new Map();
4468
4880
  for (const [composeName, svc] of Object.entries(compose.services)) {
4469
4881
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -4505,8 +4917,8 @@ async function addComposeInfra(graph, scanPath, services) {
4505
4917
 
4506
4918
  // src/extract/infra/dockerfile.ts
4507
4919
  init_cjs_shims();
4508
- var import_node_path26 = __toESM(require("path"), 1);
4509
- var import_node_fs14 = require("fs");
4920
+ var import_node_path28 = __toESM(require("path"), 1);
4921
+ var import_node_fs16 = require("fs");
4510
4922
  var import_types19 = require("@neat.is/types");
4511
4923
  function runtimeImage(content) {
4512
4924
  const lines = content.split("\n");
@@ -4526,15 +4938,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4526
4938
  let nodesAdded = 0;
4527
4939
  let edgesAdded = 0;
4528
4940
  for (const service of services) {
4529
- const dockerfilePath = import_node_path26.default.join(service.dir, "Dockerfile");
4530
- if (!await exists(dockerfilePath)) continue;
4941
+ const dockerfilePath = import_node_path28.default.join(service.dir, "Dockerfile");
4942
+ if (!await exists2(dockerfilePath)) continue;
4531
4943
  let content;
4532
4944
  try {
4533
- content = await import_node_fs14.promises.readFile(dockerfilePath, "utf8");
4945
+ content = await import_node_fs16.promises.readFile(dockerfilePath, "utf8");
4534
4946
  } catch (err) {
4535
4947
  recordExtractionError(
4536
4948
  "infra dockerfile",
4537
- import_node_path26.default.relative(scanPath, dockerfilePath),
4949
+ import_node_path28.default.relative(scanPath, dockerfilePath),
4538
4950
  err
4539
4951
  );
4540
4952
  continue;
@@ -4546,17 +4958,26 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4546
4958
  graph.addNode(node.id, node);
4547
4959
  nodesAdded++;
4548
4960
  }
4549
- const edgeId = (0, import_types5.extractedEdgeId)(service.node.id, node.id, import_types19.EdgeType.RUNS_ON);
4961
+ const relDockerfile = toPosix2(import_node_path28.default.relative(service.dir, dockerfilePath));
4962
+ const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
4963
+ graph,
4964
+ service.pkg.name,
4965
+ service.node.id,
4966
+ relDockerfile
4967
+ );
4968
+ nodesAdded += fn;
4969
+ edgesAdded += fe;
4970
+ const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, node.id, import_types19.EdgeType.RUNS_ON);
4550
4971
  if (!graph.hasEdge(edgeId)) {
4551
4972
  const edge = {
4552
4973
  id: edgeId,
4553
- source: service.node.id,
4974
+ source: fileNodeId,
4554
4975
  target: node.id,
4555
4976
  type: import_types19.EdgeType.RUNS_ON,
4556
4977
  provenance: import_types19.Provenance.EXTRACTED,
4557
4978
  confidence: (0, import_types19.confidenceForExtracted)("structural"),
4558
4979
  evidence: {
4559
- file: import_node_path26.default.relative(scanPath, dockerfilePath).split(import_node_path26.default.sep).join("/")
4980
+ file: toPosix2(import_node_path28.default.relative(scanPath, dockerfilePath))
4560
4981
  }
4561
4982
  };
4562
4983
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -4568,21 +4989,21 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4568
4989
 
4569
4990
  // src/extract/infra/terraform.ts
4570
4991
  init_cjs_shims();
4571
- var import_node_fs15 = require("fs");
4572
- var import_node_path27 = __toESM(require("path"), 1);
4992
+ var import_node_fs17 = require("fs");
4993
+ var import_node_path29 = __toESM(require("path"), 1);
4573
4994
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
4574
4995
  async function walkTfFiles(start, depth = 0, max = 5) {
4575
4996
  if (depth > max) return [];
4576
4997
  const out = [];
4577
- const entries = await import_node_fs15.promises.readdir(start, { withFileTypes: true }).catch(() => []);
4998
+ const entries = await import_node_fs17.promises.readdir(start, { withFileTypes: true }).catch(() => []);
4578
4999
  for (const entry of entries) {
4579
5000
  if (entry.isDirectory()) {
4580
5001
  if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
4581
- const child = import_node_path27.default.join(start, entry.name);
5002
+ const child = import_node_path29.default.join(start, entry.name);
4582
5003
  if (await isPythonVenvDir(child)) continue;
4583
5004
  out.push(...await walkTfFiles(child, depth + 1, max));
4584
5005
  } else if (entry.isFile() && entry.name.endsWith(".tf")) {
4585
- out.push(import_node_path27.default.join(start, entry.name));
5006
+ out.push(import_node_path29.default.join(start, entry.name));
4586
5007
  }
4587
5008
  }
4588
5009
  return out;
@@ -4591,7 +5012,7 @@ async function addTerraformResources(graph, scanPath) {
4591
5012
  let nodesAdded = 0;
4592
5013
  const files = await walkTfFiles(scanPath);
4593
5014
  for (const file of files) {
4594
- const content = await import_node_fs15.promises.readFile(file, "utf8");
5015
+ const content = await import_node_fs17.promises.readFile(file, "utf8");
4595
5016
  RESOURCE_RE.lastIndex = 0;
4596
5017
  let m;
4597
5018
  while ((m = RESOURCE_RE.exec(content)) !== null) {
@@ -4609,8 +5030,8 @@ async function addTerraformResources(graph, scanPath) {
4609
5030
 
4610
5031
  // src/extract/infra/k8s.ts
4611
5032
  init_cjs_shims();
4612
- var import_node_fs16 = require("fs");
4613
- var import_node_path28 = __toESM(require("path"), 1);
5033
+ var import_node_fs18 = require("fs");
5034
+ var import_node_path30 = __toESM(require("path"), 1);
4614
5035
  var import_yaml3 = require("yaml");
4615
5036
  var K8S_KIND_TO_INFRA_KIND = {
4616
5037
  Service: "k8s-service",
@@ -4624,15 +5045,15 @@ var K8S_KIND_TO_INFRA_KIND = {
4624
5045
  async function walkYamlFiles2(start, depth = 0, max = 5) {
4625
5046
  if (depth > max) return [];
4626
5047
  const out = [];
4627
- const entries = await import_node_fs16.promises.readdir(start, { withFileTypes: true }).catch(() => []);
5048
+ const entries = await import_node_fs18.promises.readdir(start, { withFileTypes: true }).catch(() => []);
4628
5049
  for (const entry of entries) {
4629
5050
  if (entry.isDirectory()) {
4630
5051
  if (IGNORED_DIRS.has(entry.name)) continue;
4631
- const child = import_node_path28.default.join(start, entry.name);
5052
+ const child = import_node_path30.default.join(start, entry.name);
4632
5053
  if (await isPythonVenvDir(child)) continue;
4633
5054
  out.push(...await walkYamlFiles2(child, depth + 1, max));
4634
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path28.default.extname(entry.name))) {
4635
- out.push(import_node_path28.default.join(start, entry.name));
5055
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path30.default.extname(entry.name))) {
5056
+ out.push(import_node_path30.default.join(start, entry.name));
4636
5057
  }
4637
5058
  }
4638
5059
  return out;
@@ -4641,7 +5062,7 @@ async function addK8sResources(graph, scanPath) {
4641
5062
  let nodesAdded = 0;
4642
5063
  const files = await walkYamlFiles2(scanPath);
4643
5064
  for (const file of files) {
4644
- const content = await import_node_fs16.promises.readFile(file, "utf8");
5065
+ const content = await import_node_fs18.promises.readFile(file, "utf8");
4645
5066
  let docs;
4646
5067
  try {
4647
5068
  docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
@@ -4676,12 +5097,12 @@ async function addInfra(graph, scanPath, services) {
4676
5097
  }
4677
5098
 
4678
5099
  // src/extract/index.ts
4679
- var import_node_path30 = __toESM(require("path"), 1);
5100
+ var import_node_path32 = __toESM(require("path"), 1);
4680
5101
 
4681
5102
  // src/extract/retire.ts
4682
5103
  init_cjs_shims();
4683
- var import_node_fs17 = require("fs");
4684
- var import_node_path29 = __toESM(require("path"), 1);
5104
+ var import_node_fs19 = require("fs");
5105
+ var import_node_path31 = __toESM(require("path"), 1);
4685
5106
  var import_types20 = require("@neat.is/types");
4686
5107
  function dropOrphanedFileNodes(graph) {
4687
5108
  const orphans = [];
@@ -4702,11 +5123,11 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
4702
5123
  if (edge.provenance !== import_types20.Provenance.EXTRACTED) return;
4703
5124
  const evidenceFile = edge.evidence?.file;
4704
5125
  if (!evidenceFile) return;
4705
- if (import_node_path29.default.isAbsolute(evidenceFile)) {
4706
- if (!(0, import_node_fs17.existsSync)(evidenceFile)) toDrop.push(id);
5126
+ if (import_node_path31.default.isAbsolute(evidenceFile)) {
5127
+ if (!(0, import_node_fs19.existsSync)(evidenceFile)) toDrop.push(id);
4707
5128
  return;
4708
5129
  }
4709
- const found = bases.some((base) => (0, import_node_fs17.existsSync)(import_node_path29.default.join(base, evidenceFile)));
5130
+ const found = bases.some((base) => (0, import_node_fs19.existsSync)(import_node_path31.default.join(base, evidenceFile)));
4710
5131
  if (!found) toDrop.push(id);
4711
5132
  });
4712
5133
  for (const id of toDrop) graph.dropEdge(id);
@@ -4744,7 +5165,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
4744
5165
  }
4745
5166
  const droppedEntries = drainDroppedExtracted();
4746
5167
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
4747
- const rejectedPath = import_node_path30.default.join(import_node_path30.default.dirname(opts.errorsPath), "rejected.ndjson");
5168
+ const rejectedPath = import_node_path32.default.join(import_node_path32.default.dirname(opts.errorsPath), "rejected.ndjson");
4748
5169
  try {
4749
5170
  await writeRejectedExtracted(droppedEntries, rejectedPath);
4750
5171
  } catch (err) {
@@ -4778,7 +5199,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
4778
5199
 
4779
5200
  // src/diff.ts
4780
5201
  init_cjs_shims();
4781
- var import_node_fs18 = require("fs");
5202
+ var import_node_fs20 = require("fs");
4782
5203
  async function loadSnapshotForDiff(target) {
4783
5204
  if (/^https?:\/\//i.test(target)) {
4784
5205
  const res = await fetch(target);
@@ -4787,7 +5208,7 @@ async function loadSnapshotForDiff(target) {
4787
5208
  }
4788
5209
  return await res.json();
4789
5210
  }
4790
- const raw = await import_node_fs18.promises.readFile(target, "utf8");
5211
+ const raw = await import_node_fs20.promises.readFile(target, "utf8");
4791
5212
  return JSON.parse(raw);
4792
5213
  }
4793
5214
  function indexEntries(entries) {
@@ -4855,8 +5276,8 @@ function canonicalJson(value) {
4855
5276
 
4856
5277
  // src/persist.ts
4857
5278
  init_cjs_shims();
4858
- var import_node_fs19 = require("fs");
4859
- var import_node_path31 = __toESM(require("path"), 1);
5279
+ var import_node_fs21 = require("fs");
5280
+ var import_node_path33 = __toESM(require("path"), 1);
4860
5281
  var import_types21 = require("@neat.is/types");
4861
5282
  var SCHEMA_VERSION = 4;
4862
5283
  function migrateV1ToV2(payload) {
@@ -4893,7 +5314,7 @@ function migrateV2ToV3(payload) {
4893
5314
  return { ...payload, schemaVersion: 3 };
4894
5315
  }
4895
5316
  async function ensureDir(filePath) {
4896
- await import_node_fs19.promises.mkdir(import_node_path31.default.dirname(filePath), { recursive: true });
5317
+ await import_node_fs21.promises.mkdir(import_node_path33.default.dirname(filePath), { recursive: true });
4897
5318
  }
4898
5319
  async function saveGraphToDisk(graph, outPath) {
4899
5320
  await ensureDir(outPath);
@@ -4903,13 +5324,13 @@ async function saveGraphToDisk(graph, outPath) {
4903
5324
  graph: graph.export()
4904
5325
  };
4905
5326
  const tmp = `${outPath}.tmp`;
4906
- await import_node_fs19.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
4907
- await import_node_fs19.promises.rename(tmp, outPath);
5327
+ await import_node_fs21.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
5328
+ await import_node_fs21.promises.rename(tmp, outPath);
4908
5329
  }
4909
5330
  async function loadGraphFromDisk(graph, outPath) {
4910
5331
  let raw;
4911
5332
  try {
4912
- raw = await import_node_fs19.promises.readFile(outPath, "utf8");
5333
+ raw = await import_node_fs21.promises.readFile(outPath, "utf8");
4913
5334
  } catch (err) {
4914
5335
  if (err.code === "ENOENT") return;
4915
5336
  throw err;
@@ -4968,23 +5389,23 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
4968
5389
 
4969
5390
  // src/projects.ts
4970
5391
  init_cjs_shims();
4971
- var import_node_path32 = __toESM(require("path"), 1);
5392
+ var import_node_path34 = __toESM(require("path"), 1);
4972
5393
  function pathsForProject(project, baseDir) {
4973
5394
  if (project === DEFAULT_PROJECT) {
4974
5395
  return {
4975
- snapshotPath: import_node_path32.default.join(baseDir, "graph.json"),
4976
- errorsPath: import_node_path32.default.join(baseDir, "errors.ndjson"),
4977
- staleEventsPath: import_node_path32.default.join(baseDir, "stale-events.ndjson"),
4978
- embeddingsCachePath: import_node_path32.default.join(baseDir, "embeddings.json"),
4979
- policyViolationsPath: import_node_path32.default.join(baseDir, "policy-violations.ndjson")
5396
+ snapshotPath: import_node_path34.default.join(baseDir, "graph.json"),
5397
+ errorsPath: import_node_path34.default.join(baseDir, "errors.ndjson"),
5398
+ staleEventsPath: import_node_path34.default.join(baseDir, "stale-events.ndjson"),
5399
+ embeddingsCachePath: import_node_path34.default.join(baseDir, "embeddings.json"),
5400
+ policyViolationsPath: import_node_path34.default.join(baseDir, "policy-violations.ndjson")
4980
5401
  };
4981
5402
  }
4982
5403
  return {
4983
- snapshotPath: import_node_path32.default.join(baseDir, `${project}.json`),
4984
- errorsPath: import_node_path32.default.join(baseDir, `errors.${project}.ndjson`),
4985
- staleEventsPath: import_node_path32.default.join(baseDir, `stale-events.${project}.ndjson`),
4986
- embeddingsCachePath: import_node_path32.default.join(baseDir, `embeddings.${project}.json`),
4987
- policyViolationsPath: import_node_path32.default.join(baseDir, `policy-violations.${project}.ndjson`)
5404
+ snapshotPath: import_node_path34.default.join(baseDir, `${project}.json`),
5405
+ errorsPath: import_node_path34.default.join(baseDir, `errors.${project}.ndjson`),
5406
+ staleEventsPath: import_node_path34.default.join(baseDir, `stale-events.${project}.ndjson`),
5407
+ embeddingsCachePath: import_node_path34.default.join(baseDir, `embeddings.${project}.json`),
5408
+ policyViolationsPath: import_node_path34.default.join(baseDir, `policy-violations.${project}.ndjson`)
4988
5409
  };
4989
5410
  }
4990
5411
  var Projects = class {
@@ -5024,23 +5445,23 @@ function parseExtraProjects(raw) {
5024
5445
 
5025
5446
  // src/registry.ts
5026
5447
  init_cjs_shims();
5027
- var import_node_fs20 = require("fs");
5028
- var import_node_os2 = __toESM(require("os"), 1);
5029
- var import_node_path33 = __toESM(require("path"), 1);
5448
+ var import_node_fs22 = require("fs");
5449
+ var import_node_os3 = __toESM(require("os"), 1);
5450
+ var import_node_path35 = __toESM(require("path"), 1);
5030
5451
  var import_types22 = require("@neat.is/types");
5031
5452
  function neatHome() {
5032
5453
  const override = process.env.NEAT_HOME;
5033
- if (override && override.length > 0) return import_node_path33.default.resolve(override);
5034
- return import_node_path33.default.join(import_node_os2.default.homedir(), ".neat");
5454
+ if (override && override.length > 0) return import_node_path35.default.resolve(override);
5455
+ return import_node_path35.default.join(import_node_os3.default.homedir(), ".neat");
5035
5456
  }
5036
5457
  function registryPath() {
5037
- return import_node_path33.default.join(neatHome(), "projects.json");
5458
+ return import_node_path35.default.join(neatHome(), "projects.json");
5038
5459
  }
5039
5460
  async function readRegistry() {
5040
5461
  const file = registryPath();
5041
5462
  let raw;
5042
5463
  try {
5043
- raw = await import_node_fs20.promises.readFile(file, "utf8");
5464
+ raw = await import_node_fs22.promises.readFile(file, "utf8");
5044
5465
  } catch (err) {
5045
5466
  if (err.code === "ENOENT") {
5046
5467
  return { version: 1, projects: [] };
@@ -5517,6 +5938,105 @@ function registerRoutes(scope, ctx) {
5517
5938
  violations
5518
5939
  };
5519
5940
  });
5941
+ scope.get("/extend/list-uninstrumented", async (req, reply) => {
5942
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap);
5943
+ if (!proj) return;
5944
+ if (!proj.scanPath) {
5945
+ return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
5946
+ }
5947
+ try {
5948
+ const results = await listUninstrumented({ project: proj.name, scanPath: proj.scanPath });
5949
+ return { libraries: results };
5950
+ } catch (err) {
5951
+ return reply.code(500).send({ error: err.message });
5952
+ }
5953
+ });
5954
+ scope.get("/extend/lookup", async (req, reply) => {
5955
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap);
5956
+ if (!proj) return;
5957
+ const { library, version } = req.query;
5958
+ if (!library) {
5959
+ return reply.code(400).send({ error: "query parameter `library` is required" });
5960
+ }
5961
+ const result = lookupInstrumentation(library, version);
5962
+ if (!result) {
5963
+ return reply.code(404).send({ error: "library not found in registry", library });
5964
+ }
5965
+ return result;
5966
+ });
5967
+ scope.get("/extend/describe", async (req, reply) => {
5968
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap);
5969
+ if (!proj) return;
5970
+ if (!proj.scanPath) {
5971
+ return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
5972
+ }
5973
+ try {
5974
+ const state = await describeProjectInstrumentation({ project: proj.name, scanPath: proj.scanPath });
5975
+ return state;
5976
+ } catch (err) {
5977
+ return reply.code(500).send({ error: err.message });
5978
+ }
5979
+ });
5980
+ scope.post("/extend/apply", async (req, reply) => {
5981
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap);
5982
+ if (!proj) return;
5983
+ if (!proj.scanPath) {
5984
+ return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
5985
+ }
5986
+ const { library, instrumentation_package, version, registration_snippet } = req.body ?? {};
5987
+ if (!library || !instrumentation_package || !version || !registration_snippet) {
5988
+ return reply.code(400).send({ error: "body must include library, instrumentation_package, version, registration_snippet" });
5989
+ }
5990
+ try {
5991
+ const result = await applyExtension(
5992
+ { project: proj.name, scanPath: proj.scanPath },
5993
+ { library, instrumentation_package, version, registration_snippet }
5994
+ );
5995
+ return result;
5996
+ } catch (err) {
5997
+ return reply.code(500).send({ error: err.message });
5998
+ }
5999
+ });
6000
+ scope.post("/extend/dry-run", async (req, reply) => {
6001
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap);
6002
+ if (!proj) return;
6003
+ if (!proj.scanPath) {
6004
+ return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
6005
+ }
6006
+ const { library, instrumentation_package, version, registration_snippet } = req.body ?? {};
6007
+ if (!library || !instrumentation_package || !version || !registration_snippet) {
6008
+ return reply.code(400).send({ error: "body must include library, instrumentation_package, version, registration_snippet" });
6009
+ }
6010
+ try {
6011
+ const result = await dryRunExtension(
6012
+ { project: proj.name, scanPath: proj.scanPath },
6013
+ { library, instrumentation_package, version, registration_snippet }
6014
+ );
6015
+ return result;
6016
+ } catch (err) {
6017
+ return reply.code(500).send({ error: err.message });
6018
+ }
6019
+ });
6020
+ scope.post("/extend/rollback", async (req, reply) => {
6021
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap);
6022
+ if (!proj) return;
6023
+ if (!proj.scanPath) {
6024
+ return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
6025
+ }
6026
+ const { library } = req.body ?? {};
6027
+ if (!library) {
6028
+ return reply.code(400).send({ error: "body must include library" });
6029
+ }
6030
+ try {
6031
+ const result = await rollbackExtension(
6032
+ { project: proj.name, scanPath: proj.scanPath },
6033
+ { library }
6034
+ );
6035
+ return result;
6036
+ } catch (err) {
6037
+ return reply.code(500).send({ error: err.message });
6038
+ }
6039
+ });
5520
6040
  }
5521
6041
  async function buildApi(opts) {
5522
6042
  const app = (0, import_fastify.default)({ logger: false });
@@ -5627,8 +6147,8 @@ init_otel_grpc();
5627
6147
 
5628
6148
  // src/search.ts
5629
6149
  init_cjs_shims();
5630
- var import_node_fs21 = require("fs");
5631
- var import_node_path36 = __toESM(require("path"), 1);
6150
+ var import_node_fs23 = require("fs");
6151
+ var import_node_path38 = __toESM(require("path"), 1);
5632
6152
  var import_node_crypto3 = require("crypto");
5633
6153
  var DEFAULT_LIMIT = 10;
5634
6154
  var NOMIC_DIM = 768;
@@ -5758,7 +6278,7 @@ async function pickEmbedder() {
5758
6278
  }
5759
6279
  async function readCache(cachePath) {
5760
6280
  try {
5761
- const raw = await import_node_fs21.promises.readFile(cachePath, "utf8");
6281
+ const raw = await import_node_fs23.promises.readFile(cachePath, "utf8");
5762
6282
  const parsed = JSON.parse(raw);
5763
6283
  if (parsed.version !== 1) return null;
5764
6284
  return parsed;
@@ -5767,8 +6287,8 @@ async function readCache(cachePath) {
5767
6287
  }
5768
6288
  }
5769
6289
  async function writeCache(cachePath, cache) {
5770
- await import_node_fs21.promises.mkdir(import_node_path36.default.dirname(cachePath), { recursive: true });
5771
- await import_node_fs21.promises.writeFile(cachePath, JSON.stringify(cache));
6290
+ await import_node_fs23.promises.mkdir(import_node_path38.default.dirname(cachePath), { recursive: true });
6291
+ await import_node_fs23.promises.writeFile(cachePath, JSON.stringify(cache));
5772
6292
  }
5773
6293
  var VectorIndex = class {
5774
6294
  constructor(embedder, cachePath) {
@@ -5948,14 +6468,14 @@ async function bootProject(registry, name, scanPath, baseDir) {
5948
6468
  async function main() {
5949
6469
  const baseDirEnv = process.env.NEAT_OUT_DIR;
5950
6470
  const legacyOutPath = process.env.NEAT_OUT_PATH;
5951
- const baseDir = baseDirEnv ? import_node_path37.default.resolve(baseDirEnv) : legacyOutPath ? import_node_path37.default.resolve(import_node_path37.default.dirname(legacyOutPath)) : import_node_path37.default.resolve("./neat-out");
5952
- const defaultScanPath = import_node_path37.default.resolve(process.env.NEAT_SCAN_PATH ?? "./demo");
6471
+ const baseDir = baseDirEnv ? import_node_path39.default.resolve(baseDirEnv) : legacyOutPath ? import_node_path39.default.resolve(import_node_path39.default.dirname(legacyOutPath)) : import_node_path39.default.resolve("./neat-out");
6472
+ const defaultScanPath = import_node_path39.default.resolve(process.env.NEAT_SCAN_PATH ?? "./demo");
5953
6473
  const registry = new Projects();
5954
6474
  await bootProject(registry, DEFAULT_PROJECT, defaultScanPath, baseDir);
5955
6475
  for (const name of parseExtraProjects(process.env.NEAT_PROJECTS)) {
5956
6476
  const envKey = `NEAT_PROJECT_SCAN_PATH_${name.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
5957
6477
  const projectScan = process.env[envKey];
5958
- await bootProject(registry, name, projectScan ? import_node_path37.default.resolve(projectScan) : void 0, baseDir);
6478
+ await bootProject(registry, name, projectScan ? import_node_path39.default.resolve(projectScan) : void 0, baseDir);
5959
6479
  }
5960
6480
  const host = process.env.HOST ?? "0.0.0.0";
5961
6481
  const port = Number(process.env.PORT ?? 8080);