@neat.is/core 0.4.12 → 0.4.13

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 path40 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
59
+ const path42 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
60
60
  for (const suffix of suffixes) {
61
- if (path40 === suffix || path40.endsWith(suffix)) {
61
+ if (path42 === suffix || path42.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_path36.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
188
- return import_node_path36.default.resolve(here, "..", "proto");
187
+ const here = import_node_path38.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
188
+ return import_node_path38.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_path36, import_node_crypto2, grpc, protoLoader;
256
+ var import_node_url, import_node_path38, 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_path36 = __toESM(require("path"), 1);
262
+ import_node_path38 = __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_path37.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
373
- const protoRoot = import_node_path37.default.resolve(here, "..", "proto");
372
+ const here = import_node_path39.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
373
+ const protoRoot = import_node_path39.default.resolve(here, "..", "proto");
374
374
  const root = new import_protobufjs.default.Root();
375
- root.resolvePath = (_origin, target) => import_node_path37.default.resolve(protoRoot, target);
375
+ root.resolvePath = (_origin, target) => import_node_path39.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_path37, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
568
+ var import_node_path39, 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_path37 = __toESM(require("path"), 1);
573
+ import_node_path39 = __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_path39 = __toESM(require("path"), 1);
589
+ var import_node_path41 = __toESM(require("path"), 1);
590
590
 
591
591
  // src/graph.ts
592
592
  init_cjs_shims();
@@ -610,7 +610,7 @@ function getGraph(project = DEFAULT_PROJECT) {
610
610
  init_cjs_shims();
611
611
  var import_fastify = __toESM(require("fastify"), 1);
612
612
  var import_cors = __toESM(require("@fastify/cors"), 1);
613
- var import_types23 = require("@neat.is/types");
613
+ var import_types24 = require("@neat.is/types");
614
614
 
615
615
  // src/extend/index.ts
616
616
  init_cjs_shims();
@@ -1353,19 +1353,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1353
1353
  function longestIncomingWalk(graph, start, maxDepth) {
1354
1354
  let best = { path: [start], edges: [] };
1355
1355
  const visited = /* @__PURE__ */ new Set([start]);
1356
- function step(node, path40, edges) {
1357
- if (path40.length > best.path.length) {
1358
- best = { path: [...path40], edges: [...edges] };
1356
+ function step(node, path42, edges) {
1357
+ if (path42.length > best.path.length) {
1358
+ best = { path: [...path42], edges: [...edges] };
1359
1359
  }
1360
- if (path40.length - 1 >= maxDepth) return;
1360
+ if (path42.length - 1 >= maxDepth) return;
1361
1361
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1362
1362
  for (const [srcId, edge] of incoming) {
1363
1363
  if (visited.has(srcId)) continue;
1364
1364
  visited.add(srcId);
1365
- path40.push(srcId);
1365
+ path42.push(srcId);
1366
1366
  edges.push(edge);
1367
- step(srcId, path40, edges);
1368
- path40.pop();
1367
+ step(srcId, path42, edges);
1368
+ path42.pop();
1369
1369
  edges.pop();
1370
1370
  visited.delete(srcId);
1371
1371
  }
@@ -3564,10 +3564,8 @@ async function addServiceAliases(graph, scanPath, services) {
3564
3564
  await collectK8sAliases(graph, scanPath, byName);
3565
3565
  }
3566
3566
 
3567
- // src/extract/databases/index.ts
3567
+ // src/extract/files.ts
3568
3568
  init_cjs_shims();
3569
- var import_node_path20 = __toESM(require("path"), 1);
3570
- var import_types9 = require("@neat.is/types");
3571
3569
 
3572
3570
  // src/extract/calls/shared.ts
3573
3571
  init_cjs_shims();
@@ -3666,11 +3664,333 @@ function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
3666
3664
  return { fileNodeId, nodesAdded, edgesAdded };
3667
3665
  }
3668
3666
 
3667
+ // src/extract/files.ts
3668
+ var import_node_path13 = __toESM(require("path"), 1);
3669
+ async function addFiles(graph, services) {
3670
+ let nodesAdded = 0;
3671
+ let edgesAdded = 0;
3672
+ for (const service of services) {
3673
+ const filePaths = await walkSourceFiles(service.dir);
3674
+ for (const filePath of filePaths) {
3675
+ const relPath = toPosix2(import_node_path13.default.relative(service.dir, filePath));
3676
+ const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
3677
+ graph,
3678
+ service.pkg.name,
3679
+ service.node.id,
3680
+ relPath
3681
+ );
3682
+ nodesAdded += n;
3683
+ edgesAdded += e;
3684
+ }
3685
+ }
3686
+ return { nodesAdded, edgesAdded };
3687
+ }
3688
+
3689
+ // src/extract/imports.ts
3690
+ init_cjs_shims();
3691
+ var import_node_path14 = __toESM(require("path"), 1);
3692
+ var import_node_fs13 = require("fs");
3693
+ var import_tree_sitter = __toESM(require("tree-sitter"), 1);
3694
+ var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
3695
+ var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
3696
+ var import_types9 = require("@neat.is/types");
3697
+ var PARSE_CHUNK = 16384;
3698
+ function parseSource(parser, source) {
3699
+ return parser.parse(
3700
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK)
3701
+ );
3702
+ }
3703
+ function makeJsParser() {
3704
+ const p = new import_tree_sitter.default();
3705
+ p.setLanguage(import_tree_sitter_javascript.default);
3706
+ return p;
3707
+ }
3708
+ function makePyParser() {
3709
+ const p = new import_tree_sitter.default();
3710
+ p.setLanguage(import_tree_sitter_python.default);
3711
+ return p;
3712
+ }
3713
+ function stringLiteralText(node) {
3714
+ for (let i = 0; i < node.childCount; i++) {
3715
+ const child = node.child(i);
3716
+ if (child?.type === "string_fragment") return child.text;
3717
+ }
3718
+ const raw = node.text;
3719
+ if (raw.length >= 2) return raw.slice(1, -1);
3720
+ return raw.length === 0 ? null : "";
3721
+ }
3722
+ function clipSnippet(text) {
3723
+ const oneLine = text.split("\n")[0] ?? text;
3724
+ return oneLine.length > 120 ? oneLine.slice(0, 120) : oneLine;
3725
+ }
3726
+ function collectJsImports(node, out) {
3727
+ if (node.type === "import_statement") {
3728
+ const source = node.childForFieldName("source");
3729
+ if (source) {
3730
+ const specifier = stringLiteralText(source);
3731
+ if (specifier) {
3732
+ out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
3733
+ }
3734
+ }
3735
+ return;
3736
+ }
3737
+ if (node.type === "call_expression") {
3738
+ const fn = node.childForFieldName("function");
3739
+ if (fn?.type === "identifier" && fn.text === "require") {
3740
+ const args = node.childForFieldName("arguments");
3741
+ const firstArg = args?.namedChild(0);
3742
+ if (firstArg?.type === "string") {
3743
+ const specifier = stringLiteralText(firstArg);
3744
+ if (specifier) {
3745
+ out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
3746
+ }
3747
+ }
3748
+ }
3749
+ }
3750
+ for (let i = 0; i < node.namedChildCount; i++) {
3751
+ const child = node.namedChild(i);
3752
+ if (child) collectJsImports(child, out);
3753
+ }
3754
+ }
3755
+ function collectPyImports(node, out) {
3756
+ if (node.type === "import_from_statement") {
3757
+ let level = 0;
3758
+ let modulePath = "";
3759
+ let pastFrom = false;
3760
+ for (let i = 0; i < node.childCount; i++) {
3761
+ const child = node.child(i);
3762
+ if (!child) continue;
3763
+ if (!pastFrom) {
3764
+ if (child.type === "from") pastFrom = true;
3765
+ continue;
3766
+ }
3767
+ if (child.type === "import") break;
3768
+ if (child.type === "relative_import") {
3769
+ for (let j = 0; j < child.childCount; j++) {
3770
+ const rc = child.child(j);
3771
+ if (!rc) continue;
3772
+ if (rc.type === "import_prefix") {
3773
+ for (let k = 0; k < rc.childCount; k++) {
3774
+ if (rc.child(k)?.type === ".") level++;
3775
+ }
3776
+ } else if (rc.type === "dotted_name") modulePath = rc.text;
3777
+ }
3778
+ break;
3779
+ }
3780
+ if (child.type === "dotted_name") {
3781
+ modulePath = child.text;
3782
+ break;
3783
+ }
3784
+ }
3785
+ if (level > 0 || modulePath) {
3786
+ out.push({ modulePath, level, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
3787
+ }
3788
+ }
3789
+ for (let i = 0; i < node.namedChildCount; i++) {
3790
+ const child = node.namedChild(i);
3791
+ if (child) collectPyImports(child, out);
3792
+ }
3793
+ }
3794
+ async function fileExists2(p) {
3795
+ try {
3796
+ await import_node_fs13.promises.access(p);
3797
+ return true;
3798
+ } catch {
3799
+ return false;
3800
+ }
3801
+ }
3802
+ function isWithinServiceDir(candidate, serviceDir) {
3803
+ const rel = import_node_path14.default.relative(serviceDir, candidate);
3804
+ return rel !== "" && !rel.startsWith("..") && !import_node_path14.default.isAbsolute(rel);
3805
+ }
3806
+ var JS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
3807
+ var JS_INDEX_FILES = JS_EXTENSIONS.map((ext) => `index${ext}`);
3808
+ async function firstExistingCandidate(base, serviceDir) {
3809
+ for (const ext of JS_EXTENSIONS) {
3810
+ const candidate = base + ext;
3811
+ if (isWithinServiceDir(candidate, serviceDir) && await fileExists2(candidate)) {
3812
+ return toPosix2(import_node_path14.default.relative(serviceDir, candidate));
3813
+ }
3814
+ }
3815
+ for (const indexFile of JS_INDEX_FILES) {
3816
+ const candidate = import_node_path14.default.join(base, indexFile);
3817
+ if (isWithinServiceDir(candidate, serviceDir) && await fileExists2(candidate)) {
3818
+ return toPosix2(import_node_path14.default.relative(serviceDir, candidate));
3819
+ }
3820
+ }
3821
+ return null;
3822
+ }
3823
+ async function loadTsPathConfig(serviceDir) {
3824
+ const tsconfigPath = import_node_path14.default.join(serviceDir, "tsconfig.json");
3825
+ let raw;
3826
+ try {
3827
+ raw = await import_node_fs13.promises.readFile(tsconfigPath, "utf8");
3828
+ } catch {
3829
+ return null;
3830
+ }
3831
+ try {
3832
+ const parsed = JSON.parse(raw);
3833
+ const paths = parsed.compilerOptions?.paths;
3834
+ if (!paths || Object.keys(paths).length === 0) return null;
3835
+ const baseUrl = parsed.compilerOptions?.baseUrl;
3836
+ return { paths, baseDir: baseUrl ? import_node_path14.default.resolve(serviceDir, baseUrl) : serviceDir };
3837
+ } catch (err) {
3838
+ recordExtractionError("import alias resolution", tsconfigPath, err);
3839
+ return null;
3840
+ }
3841
+ }
3842
+ async function resolveTsAlias(specifier, config, serviceDir) {
3843
+ for (const [pattern, targets] of Object.entries(config.paths)) {
3844
+ let suffix = null;
3845
+ if (pattern === specifier) {
3846
+ suffix = "";
3847
+ } else if (pattern.endsWith("/*")) {
3848
+ const prefix = pattern.slice(0, -1);
3849
+ if (specifier.startsWith(prefix)) suffix = specifier.slice(prefix.length);
3850
+ }
3851
+ if (suffix === null) continue;
3852
+ for (const target of targets) {
3853
+ const targetBase = target.endsWith("/*") ? target.slice(0, -2) : target.replace(/\*$/, "");
3854
+ const resolvedBase = import_node_path14.default.resolve(config.baseDir, targetBase, suffix);
3855
+ const hit = await firstExistingCandidate(resolvedBase, serviceDir);
3856
+ if (hit) return hit;
3857
+ if (isWithinServiceDir(resolvedBase, serviceDir) && await fileExists2(resolvedBase)) {
3858
+ return toPosix2(import_node_path14.default.relative(serviceDir, resolvedBase));
3859
+ }
3860
+ }
3861
+ }
3862
+ return null;
3863
+ }
3864
+ async function resolveJsImport(specifier, importerDir, serviceDir, tsPaths) {
3865
+ if (!specifier) return null;
3866
+ if (specifier.startsWith("./") || specifier.startsWith("../")) {
3867
+ const base = import_node_path14.default.resolve(importerDir, specifier);
3868
+ const ext = import_node_path14.default.extname(specifier);
3869
+ if (ext) {
3870
+ if (ext === ".js" || ext === ".jsx") {
3871
+ const tsExt = ext === ".jsx" ? ".tsx" : ".ts";
3872
+ const tsSibling = base.slice(0, -ext.length) + tsExt;
3873
+ if (isWithinServiceDir(tsSibling, serviceDir) && await fileExists2(tsSibling)) {
3874
+ return toPosix2(import_node_path14.default.relative(serviceDir, tsSibling));
3875
+ }
3876
+ }
3877
+ if (isWithinServiceDir(base, serviceDir) && await fileExists2(base)) {
3878
+ return toPosix2(import_node_path14.default.relative(serviceDir, base));
3879
+ }
3880
+ return null;
3881
+ }
3882
+ return firstExistingCandidate(base, serviceDir);
3883
+ }
3884
+ if (tsPaths) return resolveTsAlias(specifier, tsPaths, serviceDir);
3885
+ return null;
3886
+ }
3887
+ async function resolvePyImport(imp, importerPath, serviceDir) {
3888
+ if (!imp.modulePath) return null;
3889
+ const relPath = imp.modulePath.split(".").join("/");
3890
+ let baseDir;
3891
+ if (imp.level > 0) {
3892
+ baseDir = import_node_path14.default.dirname(importerPath);
3893
+ for (let i = 1; i < imp.level; i++) baseDir = import_node_path14.default.dirname(baseDir);
3894
+ } else {
3895
+ baseDir = serviceDir;
3896
+ }
3897
+ const candidates = [import_node_path14.default.join(baseDir, `${relPath}.py`), import_node_path14.default.join(baseDir, relPath, "__init__.py")];
3898
+ for (const candidate of candidates) {
3899
+ if (isWithinServiceDir(candidate, serviceDir) && await fileExists2(candidate)) {
3900
+ return toPosix2(import_node_path14.default.relative(serviceDir, candidate));
3901
+ }
3902
+ }
3903
+ return null;
3904
+ }
3905
+ function emitImportEdge(graph, serviceName, importerFileId, importerRelPath, importeeRelPath, line, snippet2) {
3906
+ const importeeFileId = (0, import_types9.fileId)(serviceName, importeeRelPath);
3907
+ if (!graph.hasNode(importeeFileId)) return 0;
3908
+ const edgeId = (0, import_types9.extractedEdgeId)(importerFileId, importeeFileId, import_types9.EdgeType.IMPORTS);
3909
+ if (graph.hasEdge(edgeId)) return 0;
3910
+ const edge = {
3911
+ id: edgeId,
3912
+ source: importerFileId,
3913
+ target: importeeFileId,
3914
+ type: import_types9.EdgeType.IMPORTS,
3915
+ provenance: import_types9.Provenance.EXTRACTED,
3916
+ confidence: (0, import_types9.confidenceForExtracted)("structural"),
3917
+ evidence: { file: importerRelPath, line, snippet: snippet2 }
3918
+ };
3919
+ graph.addEdgeWithKey(edgeId, importerFileId, importeeFileId, edge);
3920
+ return 1;
3921
+ }
3922
+ async function addImports(graph, services) {
3923
+ const jsParser = makeJsParser();
3924
+ const pyParser = makePyParser();
3925
+ let edgesAdded = 0;
3926
+ for (const service of services) {
3927
+ const tsPaths = await loadTsPathConfig(service.dir);
3928
+ const files = await loadSourceFiles(service.dir);
3929
+ for (const file of files) {
3930
+ if (isTestPath(file.path)) continue;
3931
+ const relFile = toPosix2(import_node_path14.default.relative(service.dir, file.path));
3932
+ const importerFileId = (0, import_types9.fileId)(service.pkg.name, relFile);
3933
+ const isPython = import_node_path14.default.extname(file.path) === ".py";
3934
+ if (isPython) {
3935
+ let pyImports = [];
3936
+ try {
3937
+ const tree = parseSource(pyParser, file.content);
3938
+ collectPyImports(tree.rootNode, pyImports);
3939
+ } catch (err) {
3940
+ recordExtractionError("import extraction", file.path, err);
3941
+ continue;
3942
+ }
3943
+ for (const imp of pyImports) {
3944
+ const resolved = await resolvePyImport(imp, file.path, service.dir);
3945
+ if (!resolved) continue;
3946
+ edgesAdded += emitImportEdge(
3947
+ graph,
3948
+ service.pkg.name,
3949
+ importerFileId,
3950
+ relFile,
3951
+ resolved,
3952
+ imp.line,
3953
+ imp.snippet
3954
+ );
3955
+ }
3956
+ continue;
3957
+ }
3958
+ let jsImports = [];
3959
+ try {
3960
+ const tree = parseSource(jsParser, file.content);
3961
+ collectJsImports(tree.rootNode, jsImports);
3962
+ } catch (err) {
3963
+ recordExtractionError("import extraction", file.path, err);
3964
+ continue;
3965
+ }
3966
+ for (const imp of jsImports) {
3967
+ const resolved = await resolveJsImport(imp.specifier, import_node_path14.default.dirname(file.path), service.dir, tsPaths);
3968
+ if (!resolved) continue;
3969
+ edgesAdded += emitImportEdge(
3970
+ graph,
3971
+ service.pkg.name,
3972
+ importerFileId,
3973
+ relFile,
3974
+ resolved,
3975
+ imp.line,
3976
+ imp.snippet
3977
+ );
3978
+ }
3979
+ }
3980
+ }
3981
+ return { nodesAdded: 0, edgesAdded };
3982
+ }
3983
+
3984
+ // src/extract/databases/index.ts
3985
+ init_cjs_shims();
3986
+ var import_node_path22 = __toESM(require("path"), 1);
3987
+ var import_types10 = require("@neat.is/types");
3988
+
3669
3989
  // src/extract/databases/db-config-yaml.ts
3670
3990
  init_cjs_shims();
3671
- var import_node_path13 = __toESM(require("path"), 1);
3991
+ var import_node_path15 = __toESM(require("path"), 1);
3672
3992
  async function parse(serviceDir) {
3673
- const yamlPath = import_node_path13.default.join(serviceDir, "db-config.yaml");
3993
+ const yamlPath = import_node_path15.default.join(serviceDir, "db-config.yaml");
3674
3994
  if (!await exists2(yamlPath)) return [];
3675
3995
  const raw = await readYaml(yamlPath);
3676
3996
  return [
@@ -3688,13 +4008,13 @@ var dbConfigYamlParser = { name: "db-config.yaml", parse };
3688
4008
 
3689
4009
  // src/extract/databases/dotenv.ts
3690
4010
  init_cjs_shims();
3691
- var import_node_fs14 = require("fs");
3692
- var import_node_path15 = __toESM(require("path"), 1);
4011
+ var import_node_fs15 = require("fs");
4012
+ var import_node_path17 = __toESM(require("path"), 1);
3693
4013
 
3694
4014
  // src/extract/databases/shared.ts
3695
4015
  init_cjs_shims();
3696
- var import_node_fs13 = require("fs");
3697
- var import_node_path14 = __toESM(require("path"), 1);
4016
+ var import_node_fs14 = require("fs");
4017
+ var import_node_path16 = __toESM(require("path"), 1);
3698
4018
  function schemeToEngine(scheme) {
3699
4019
  const s = scheme.toLowerCase().split("+")[0];
3700
4020
  switch (s) {
@@ -3733,14 +4053,14 @@ function parseConnectionString(url) {
3733
4053
  }
3734
4054
  async function readIfExists(filePath) {
3735
4055
  try {
3736
- return await import_node_fs13.promises.readFile(filePath, "utf8");
4056
+ return await import_node_fs14.promises.readFile(filePath, "utf8");
3737
4057
  } catch {
3738
4058
  return null;
3739
4059
  }
3740
4060
  }
3741
4061
  async function findFirst(serviceDir, candidates) {
3742
4062
  for (const rel of candidates) {
3743
- const abs = import_node_path14.default.join(serviceDir, rel);
4063
+ const abs = import_node_path16.default.join(serviceDir, rel);
3744
4064
  const content = await readIfExists(abs);
3745
4065
  if (content !== null) return abs;
3746
4066
  }
@@ -3791,15 +4111,15 @@ function parseDotenvLine(line) {
3791
4111
  return { key, value };
3792
4112
  }
3793
4113
  async function parse2(serviceDir) {
3794
- const entries = await import_node_fs14.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
4114
+ const entries = await import_node_fs15.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
3795
4115
  const configs = [];
3796
4116
  const seen = /* @__PURE__ */ new Set();
3797
4117
  for (const entry of entries) {
3798
4118
  if (!entry.isFile()) continue;
3799
4119
  const match = isConfigFile(entry.name);
3800
4120
  if (!match.match || match.fileType !== "env") continue;
3801
- const filePath = import_node_path15.default.join(serviceDir, entry.name);
3802
- const content = await import_node_fs14.promises.readFile(filePath, "utf8");
4121
+ const filePath = import_node_path17.default.join(serviceDir, entry.name);
4122
+ const content = await import_node_fs15.promises.readFile(filePath, "utf8");
3803
4123
  for (const line of content.split("\n")) {
3804
4124
  const parsed = parseDotenvLine(line);
3805
4125
  if (!parsed) continue;
@@ -3818,9 +4138,9 @@ var dotenvParser = { name: ".env", parse: parse2 };
3818
4138
 
3819
4139
  // src/extract/databases/prisma.ts
3820
4140
  init_cjs_shims();
3821
- var import_node_path16 = __toESM(require("path"), 1);
4141
+ var import_node_path18 = __toESM(require("path"), 1);
3822
4142
  async function parse3(serviceDir) {
3823
- const schemaPath = import_node_path16.default.join(serviceDir, "prisma", "schema.prisma");
4143
+ const schemaPath = import_node_path18.default.join(serviceDir, "prisma", "schema.prisma");
3824
4144
  const content = await readIfExists(schemaPath);
3825
4145
  if (!content) return [];
3826
4146
  const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
@@ -3952,10 +4272,10 @@ var knexParser = { name: "knex", parse: parse5 };
3952
4272
 
3953
4273
  // src/extract/databases/ormconfig.ts
3954
4274
  init_cjs_shims();
3955
- var import_node_path17 = __toESM(require("path"), 1);
4275
+ var import_node_path19 = __toESM(require("path"), 1);
3956
4276
  async function parse6(serviceDir) {
3957
4277
  for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
3958
- const abs = import_node_path17.default.join(serviceDir, candidate);
4278
+ const abs = import_node_path19.default.join(serviceDir, candidate);
3959
4279
  if (!await exists2(abs)) continue;
3960
4280
  const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
3961
4281
  const entries = Array.isArray(raw) ? raw : [raw];
@@ -4015,9 +4335,9 @@ var typeormParser = { name: "typeorm", parse: parse7 };
4015
4335
 
4016
4336
  // src/extract/databases/sequelize.ts
4017
4337
  init_cjs_shims();
4018
- var import_node_path18 = __toESM(require("path"), 1);
4338
+ var import_node_path20 = __toESM(require("path"), 1);
4019
4339
  async function parse8(serviceDir) {
4020
- const configPath = import_node_path18.default.join(serviceDir, "config", "config.json");
4340
+ const configPath = import_node_path20.default.join(serviceDir, "config", "config.json");
4021
4341
  if (!await exists2(configPath)) return [];
4022
4342
  const raw = await readJson(configPath);
4023
4343
  const out = [];
@@ -4044,7 +4364,7 @@ var sequelizeParser = { name: "sequelize", parse: parse8 };
4044
4364
 
4045
4365
  // src/extract/databases/docker-compose.ts
4046
4366
  init_cjs_shims();
4047
- var import_node_path19 = __toESM(require("path"), 1);
4367
+ var import_node_path21 = __toESM(require("path"), 1);
4048
4368
  function portFromService(svc) {
4049
4369
  for (const raw of svc.ports ?? []) {
4050
4370
  const str = String(raw);
@@ -4071,7 +4391,7 @@ function databaseFromEnv(svc) {
4071
4391
  }
4072
4392
  async function parse9(serviceDir) {
4073
4393
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
4074
- const abs = import_node_path19.default.join(serviceDir, name);
4394
+ const abs = import_node_path21.default.join(serviceDir, name);
4075
4395
  if (!await exists2(abs)) continue;
4076
4396
  const raw = await readYaml(abs);
4077
4397
  if (!raw?.services) return [];
@@ -4112,8 +4432,8 @@ function compatibleDriversFor(engine) {
4112
4432
  }
4113
4433
  function toDatabaseNode(config) {
4114
4434
  return {
4115
- id: (0, import_types9.databaseId)(config.host),
4116
- type: import_types9.NodeType.DatabaseNode,
4435
+ id: (0, import_types10.databaseId)(config.host),
4436
+ type: import_types10.NodeType.DatabaseNode,
4117
4437
  name: config.database || config.host,
4118
4438
  engine: config.engine,
4119
4439
  engineVersion: config.engineVersion,
@@ -4243,7 +4563,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
4243
4563
  discoveredVia: mergedDiscoveredVia
4244
4564
  });
4245
4565
  }
4246
- const relConfigFile = toPosix2(import_node_path20.default.relative(service.dir, config.sourceFile));
4566
+ const relConfigFile = toPosix2(import_node_path22.default.relative(service.dir, config.sourceFile));
4247
4567
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
4248
4568
  graph,
4249
4569
  service.pkg.name,
@@ -4252,14 +4572,14 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
4252
4572
  );
4253
4573
  nodesAdded += fn;
4254
4574
  edgesAdded += fe;
4255
- const evidenceFile = toPosix2(import_node_path20.default.relative(scanPath, config.sourceFile));
4575
+ const evidenceFile = toPosix2(import_node_path22.default.relative(scanPath, config.sourceFile));
4256
4576
  const edge = {
4257
- id: (0, import_types5.extractedEdgeId)(fileNodeId, dbNode.id, import_types9.EdgeType.CONNECTS_TO),
4577
+ id: (0, import_types5.extractedEdgeId)(fileNodeId, dbNode.id, import_types10.EdgeType.CONNECTS_TO),
4258
4578
  source: fileNodeId,
4259
4579
  target: dbNode.id,
4260
- type: import_types9.EdgeType.CONNECTS_TO,
4261
- provenance: import_types9.Provenance.EXTRACTED,
4262
- confidence: (0, import_types9.confidenceForExtracted)("structural"),
4580
+ type: import_types10.EdgeType.CONNECTS_TO,
4581
+ provenance: import_types10.Provenance.EXTRACTED,
4582
+ confidence: (0, import_types10.confidenceForExtracted)("structural"),
4263
4583
  evidence: { file: evidenceFile }
4264
4584
  };
4265
4585
  if (!graph.hasEdge(edge.id)) {
@@ -4286,15 +4606,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
4286
4606
 
4287
4607
  // src/extract/configs.ts
4288
4608
  init_cjs_shims();
4289
- var import_node_fs15 = require("fs");
4290
- var import_node_path21 = __toESM(require("path"), 1);
4291
- var import_types10 = require("@neat.is/types");
4609
+ var import_node_fs16 = require("fs");
4610
+ var import_node_path23 = __toESM(require("path"), 1);
4611
+ var import_types11 = require("@neat.is/types");
4292
4612
  async function walkConfigFiles(dir) {
4293
4613
  const out = [];
4294
4614
  async function walk(current) {
4295
- const entries = await import_node_fs15.promises.readdir(current, { withFileTypes: true });
4615
+ const entries = await import_node_fs16.promises.readdir(current, { withFileTypes: true });
4296
4616
  for (const entry of entries) {
4297
- const full = import_node_path21.default.join(current, entry.name);
4617
+ const full = import_node_path23.default.join(current, entry.name);
4298
4618
  if (entry.isDirectory()) {
4299
4619
  if (IGNORED_DIRS.has(entry.name)) continue;
4300
4620
  if (await isPythonVenvDir(full)) continue;
@@ -4313,19 +4633,19 @@ async function addConfigNodes(graph, services, scanPath) {
4313
4633
  for (const service of services) {
4314
4634
  const configFiles = await walkConfigFiles(service.dir);
4315
4635
  for (const file of configFiles) {
4316
- const relPath = import_node_path21.default.relative(scanPath, file);
4636
+ const relPath = import_node_path23.default.relative(scanPath, file);
4317
4637
  const node = {
4318
- id: (0, import_types10.configId)(relPath),
4319
- type: import_types10.NodeType.ConfigNode,
4320
- name: import_node_path21.default.basename(file),
4638
+ id: (0, import_types11.configId)(relPath),
4639
+ type: import_types11.NodeType.ConfigNode,
4640
+ name: import_node_path23.default.basename(file),
4321
4641
  path: relPath,
4322
- fileType: isConfigFile(import_node_path21.default.basename(file)).fileType
4642
+ fileType: isConfigFile(import_node_path23.default.basename(file)).fileType
4323
4643
  };
4324
4644
  if (!graph.hasNode(node.id)) {
4325
4645
  graph.addNode(node.id, node);
4326
4646
  nodesAdded++;
4327
4647
  }
4328
- const relToService = toPosix2(import_node_path21.default.relative(service.dir, file));
4648
+ const relToService = toPosix2(import_node_path23.default.relative(service.dir, file));
4329
4649
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
4330
4650
  graph,
4331
4651
  service.pkg.name,
@@ -4335,13 +4655,13 @@ async function addConfigNodes(graph, services, scanPath) {
4335
4655
  nodesAdded += fn;
4336
4656
  edgesAdded += fe;
4337
4657
  const edge = {
4338
- id: (0, import_types5.extractedEdgeId)(fileNodeId, node.id, import_types10.EdgeType.CONFIGURED_BY),
4658
+ id: (0, import_types5.extractedEdgeId)(fileNodeId, node.id, import_types11.EdgeType.CONFIGURED_BY),
4339
4659
  source: fileNodeId,
4340
4660
  target: node.id,
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("/") }
4661
+ type: import_types11.EdgeType.CONFIGURED_BY,
4662
+ provenance: import_types11.Provenance.EXTRACTED,
4663
+ confidence: (0, import_types11.confidenceForExtracted)("structural"),
4664
+ evidence: { file: relPath.split(import_node_path23.default.sep).join("/") }
4345
4665
  };
4346
4666
  if (!graph.hasEdge(edge.id)) {
4347
4667
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -4354,15 +4674,15 @@ async function addConfigNodes(graph, services, scanPath) {
4354
4674
 
4355
4675
  // src/extract/calls/index.ts
4356
4676
  init_cjs_shims();
4357
- var import_types16 = require("@neat.is/types");
4677
+ var import_types17 = require("@neat.is/types");
4358
4678
 
4359
4679
  // src/extract/calls/http.ts
4360
4680
  init_cjs_shims();
4361
- var import_node_path22 = __toESM(require("path"), 1);
4362
- var import_tree_sitter = __toESM(require("tree-sitter"), 1);
4363
- var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
4364
- var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
4365
- var import_types11 = require("@neat.is/types");
4681
+ var import_node_path24 = __toESM(require("path"), 1);
4682
+ var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
4683
+ var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
4684
+ var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
4685
+ var import_types12 = require("@neat.is/types");
4366
4686
  var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
4367
4687
  var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
4368
4688
  function isInsideJsxExternalLink(node) {
@@ -4390,14 +4710,14 @@ function collectStringLiterals(node, out) {
4390
4710
  if (child) collectStringLiterals(child, out);
4391
4711
  }
4392
4712
  }
4393
- var PARSE_CHUNK = 16384;
4394
- function parseSource(parser, source) {
4713
+ var PARSE_CHUNK2 = 16384;
4714
+ function parseSource2(parser, source) {
4395
4715
  return parser.parse(
4396
- (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK)
4716
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
4397
4717
  );
4398
4718
  }
4399
4719
  function callsFromSource(source, parser, knownHosts) {
4400
- const tree = parseSource(parser, source);
4720
+ const tree = parseSource2(parser, source);
4401
4721
  const literals = [];
4402
4722
  collectStringLiterals(tree.rootNode, literals);
4403
4723
  const out = [];
@@ -4411,25 +4731,25 @@ function callsFromSource(source, parser, knownHosts) {
4411
4731
  }
4412
4732
  return out;
4413
4733
  }
4414
- function makeJsParser() {
4415
- const p = new import_tree_sitter.default();
4416
- p.setLanguage(import_tree_sitter_javascript.default);
4734
+ function makeJsParser2() {
4735
+ const p = new import_tree_sitter2.default();
4736
+ p.setLanguage(import_tree_sitter_javascript2.default);
4417
4737
  return p;
4418
4738
  }
4419
- function makePyParser() {
4420
- const p = new import_tree_sitter.default();
4421
- p.setLanguage(import_tree_sitter_python.default);
4739
+ function makePyParser2() {
4740
+ const p = new import_tree_sitter2.default();
4741
+ p.setLanguage(import_tree_sitter_python2.default);
4422
4742
  return p;
4423
4743
  }
4424
4744
  async function addHttpCallEdges(graph, services) {
4425
- const jsParser = makeJsParser();
4426
- const pyParser = makePyParser();
4745
+ const jsParser = makeJsParser2();
4746
+ const pyParser = makePyParser2();
4427
4747
  const knownHosts = /* @__PURE__ */ new Set();
4428
4748
  const hostToNodeId = /* @__PURE__ */ new Map();
4429
4749
  for (const service of services) {
4430
- knownHosts.add(import_node_path22.default.basename(service.dir));
4750
+ knownHosts.add(import_node_path24.default.basename(service.dir));
4431
4751
  knownHosts.add(service.pkg.name);
4432
- hostToNodeId.set(import_node_path22.default.basename(service.dir), service.node.id);
4752
+ hostToNodeId.set(import_node_path24.default.basename(service.dir), service.node.id);
4433
4753
  hostToNodeId.set(service.pkg.name, service.node.id);
4434
4754
  }
4435
4755
  let nodesAdded = 0;
@@ -4439,7 +4759,7 @@ async function addHttpCallEdges(graph, services) {
4439
4759
  const seen = /* @__PURE__ */ new Set();
4440
4760
  for (const file of files) {
4441
4761
  if (isTestPath(file.path)) continue;
4442
- const parser = import_node_path22.default.extname(file.path) === ".py" ? pyParser : jsParser;
4762
+ const parser = import_node_path24.default.extname(file.path) === ".py" ? pyParser : jsParser;
4443
4763
  let sites;
4444
4764
  try {
4445
4765
  sites = callsFromSource(file.content, parser, knownHosts);
@@ -4448,14 +4768,14 @@ async function addHttpCallEdges(graph, services) {
4448
4768
  continue;
4449
4769
  }
4450
4770
  if (sites.length === 0) continue;
4451
- const relFile = toPosix2(import_node_path22.default.relative(service.dir, file.path));
4771
+ const relFile = toPosix2(import_node_path24.default.relative(service.dir, file.path));
4452
4772
  for (const site of sites) {
4453
4773
  const targetId = hostToNodeId.get(site.host);
4454
4774
  if (!targetId || targetId === service.node.id) continue;
4455
4775
  const dedupKey = `${relFile}|${targetId}`;
4456
4776
  if (seen.has(dedupKey)) continue;
4457
4777
  seen.add(dedupKey);
4458
- const confidence = (0, import_types11.confidenceForExtracted)("hostname-shape-match");
4778
+ const confidence = (0, import_types12.confidenceForExtracted)("hostname-shape-match");
4459
4779
  const ev = {
4460
4780
  file: relFile,
4461
4781
  line: site.line,
@@ -4469,25 +4789,25 @@ async function addHttpCallEdges(graph, services) {
4469
4789
  );
4470
4790
  nodesAdded += n;
4471
4791
  edgesAdded += e;
4472
- if (!(0, import_types11.passesExtractedFloor)(confidence)) {
4792
+ if (!(0, import_types12.passesExtractedFloor)(confidence)) {
4473
4793
  noteExtractedDropped({
4474
4794
  source: fileNodeId,
4475
4795
  target: targetId,
4476
- type: import_types11.EdgeType.CALLS,
4796
+ type: import_types12.EdgeType.CALLS,
4477
4797
  confidence,
4478
4798
  confidenceKind: "hostname-shape-match",
4479
4799
  evidence: ev
4480
4800
  });
4481
4801
  continue;
4482
4802
  }
4483
- const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, targetId, import_types11.EdgeType.CALLS);
4803
+ const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, targetId, import_types12.EdgeType.CALLS);
4484
4804
  if (!graph.hasEdge(edgeId)) {
4485
4805
  const edge = {
4486
4806
  id: edgeId,
4487
4807
  source: fileNodeId,
4488
4808
  target: targetId,
4489
- type: import_types11.EdgeType.CALLS,
4490
- provenance: import_types11.Provenance.EXTRACTED,
4809
+ type: import_types12.EdgeType.CALLS,
4810
+ provenance: import_types12.Provenance.EXTRACTED,
4491
4811
  confidence,
4492
4812
  evidence: ev
4493
4813
  };
@@ -4502,8 +4822,8 @@ async function addHttpCallEdges(graph, services) {
4502
4822
 
4503
4823
  // src/extract/calls/kafka.ts
4504
4824
  init_cjs_shims();
4505
- var import_node_path23 = __toESM(require("path"), 1);
4506
- var import_types12 = require("@neat.is/types");
4825
+ var import_node_path25 = __toESM(require("path"), 1);
4826
+ var import_types13 = require("@neat.is/types");
4507
4827
  var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
4508
4828
  var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
4509
4829
  function findAll(re, text) {
@@ -4524,7 +4844,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
4524
4844
  seen.add(key);
4525
4845
  const line = lineOf(file.content, topic);
4526
4846
  out.push({
4527
- infraId: (0, import_types12.infraId)("kafka-topic", topic),
4847
+ infraId: (0, import_types13.infraId)("kafka-topic", topic),
4528
4848
  name: topic,
4529
4849
  kind: "kafka-topic",
4530
4850
  edgeType,
@@ -4533,7 +4853,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
4533
4853
  // tier (ADR-066).
4534
4854
  confidenceKind: "verified-call-site",
4535
4855
  evidence: {
4536
- file: import_node_path23.default.relative(serviceDir, file.path),
4856
+ file: import_node_path25.default.relative(serviceDir, file.path),
4537
4857
  line,
4538
4858
  snippet: snippet(file.content, line)
4539
4859
  }
@@ -4546,8 +4866,8 @@ function kafkaEndpointsFromFile(file, serviceDir) {
4546
4866
 
4547
4867
  // src/extract/calls/redis.ts
4548
4868
  init_cjs_shims();
4549
- var import_node_path24 = __toESM(require("path"), 1);
4550
- var import_types13 = require("@neat.is/types");
4869
+ var import_node_path26 = __toESM(require("path"), 1);
4870
+ var import_types14 = require("@neat.is/types");
4551
4871
  var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
4552
4872
  function redisEndpointsFromFile(file, serviceDir) {
4553
4873
  const out = [];
@@ -4560,7 +4880,7 @@ function redisEndpointsFromFile(file, serviceDir) {
4560
4880
  seen.add(host);
4561
4881
  const line = lineOf(file.content, host);
4562
4882
  out.push({
4563
- infraId: (0, import_types13.infraId)("redis", host),
4883
+ infraId: (0, import_types14.infraId)("redis", host),
4564
4884
  name: host,
4565
4885
  kind: "redis",
4566
4886
  edgeType: "CALLS",
@@ -4569,7 +4889,7 @@ function redisEndpointsFromFile(file, serviceDir) {
4569
4889
  // support tier (ADR-066).
4570
4890
  confidenceKind: "url-with-structural-support",
4571
4891
  evidence: {
4572
- file: import_node_path24.default.relative(serviceDir, file.path),
4892
+ file: import_node_path26.default.relative(serviceDir, file.path),
4573
4893
  line,
4574
4894
  snippet: snippet(file.content, line)
4575
4895
  }
@@ -4580,8 +4900,8 @@ function redisEndpointsFromFile(file, serviceDir) {
4580
4900
 
4581
4901
  // src/extract/calls/aws.ts
4582
4902
  init_cjs_shims();
4583
- var import_node_path25 = __toESM(require("path"), 1);
4584
- var import_types14 = require("@neat.is/types");
4903
+ var import_node_path27 = __toESM(require("path"), 1);
4904
+ var import_types15 = require("@neat.is/types");
4585
4905
  var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
4586
4906
  var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
4587
4907
  function hasMarker(text, markers) {
@@ -4605,7 +4925,7 @@ function awsEndpointsFromFile(file, serviceDir) {
4605
4925
  seen.add(key);
4606
4926
  const line = lineOf(file.content, name);
4607
4927
  out.push({
4608
- infraId: (0, import_types14.infraId)(kind, name),
4928
+ infraId: (0, import_types15.infraId)(kind, name),
4609
4929
  name,
4610
4930
  kind,
4611
4931
  edgeType: "CALLS",
@@ -4614,7 +4934,7 @@ function awsEndpointsFromFile(file, serviceDir) {
4614
4934
  // (ADR-066).
4615
4935
  confidenceKind: "verified-call-site",
4616
4936
  evidence: {
4617
- file: import_node_path25.default.relative(serviceDir, file.path),
4937
+ file: import_node_path27.default.relative(serviceDir, file.path),
4618
4938
  line,
4619
4939
  snippet: snippet(file.content, line)
4620
4940
  }
@@ -4639,8 +4959,8 @@ function awsEndpointsFromFile(file, serviceDir) {
4639
4959
 
4640
4960
  // src/extract/calls/grpc.ts
4641
4961
  init_cjs_shims();
4642
- var import_node_path26 = __toESM(require("path"), 1);
4643
- var import_types15 = require("@neat.is/types");
4962
+ var import_node_path28 = __toESM(require("path"), 1);
4963
+ var import_types16 = require("@neat.is/types");
4644
4964
  var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
4645
4965
  var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
4646
4966
  var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
@@ -4689,7 +5009,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
4689
5009
  const { kind } = classified;
4690
5010
  const line = lineOf(file.content, m[0]);
4691
5011
  out.push({
4692
- infraId: (0, import_types15.infraId)(kind, name),
5012
+ infraId: (0, import_types16.infraId)(kind, name),
4693
5013
  name,
4694
5014
  kind,
4695
5015
  edgeType: "CALLS",
@@ -4698,7 +5018,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
4698
5018
  // tier (ADR-066).
4699
5019
  confidenceKind: "verified-call-site",
4700
5020
  evidence: {
4701
- file: import_node_path26.default.relative(serviceDir, file.path),
5021
+ file: import_node_path28.default.relative(serviceDir, file.path),
4702
5022
  line,
4703
5023
  snippet: snippet(file.content, line)
4704
5024
  }
@@ -4711,11 +5031,11 @@ function grpcEndpointsFromFile(file, serviceDir) {
4711
5031
  function edgeTypeFromEndpoint(ep) {
4712
5032
  switch (ep.edgeType) {
4713
5033
  case "PUBLISHES_TO":
4714
- return import_types16.EdgeType.PUBLISHES_TO;
5034
+ return import_types17.EdgeType.PUBLISHES_TO;
4715
5035
  case "CONSUMES_FROM":
4716
- return import_types16.EdgeType.CONSUMES_FROM;
5036
+ return import_types17.EdgeType.CONSUMES_FROM;
4717
5037
  default:
4718
- return import_types16.EdgeType.CALLS;
5038
+ return import_types17.EdgeType.CALLS;
4719
5039
  }
4720
5040
  }
4721
5041
  function isAwsKind(kind) {
@@ -4742,7 +5062,7 @@ async function addExternalEndpointEdges(graph, services) {
4742
5062
  if (!graph.hasNode(ep.infraId)) {
4743
5063
  const node = {
4744
5064
  id: ep.infraId,
4745
- type: import_types16.NodeType.InfraNode,
5065
+ type: import_types17.NodeType.InfraNode,
4746
5066
  name: ep.name,
4747
5067
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
4748
5068
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -4754,7 +5074,7 @@ async function addExternalEndpointEdges(graph, services) {
4754
5074
  nodesAdded++;
4755
5075
  }
4756
5076
  const edgeType = edgeTypeFromEndpoint(ep);
4757
- const confidence = (0, import_types16.confidenceForExtracted)(ep.confidenceKind);
5077
+ const confidence = (0, import_types17.confidenceForExtracted)(ep.confidenceKind);
4758
5078
  const relFile = toPosix2(ep.evidence.file);
4759
5079
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
4760
5080
  graph,
@@ -4764,7 +5084,7 @@ async function addExternalEndpointEdges(graph, services) {
4764
5084
  );
4765
5085
  nodesAdded += n;
4766
5086
  edgesAdded += e;
4767
- if (!(0, import_types16.passesExtractedFloor)(confidence)) {
5087
+ if (!(0, import_types17.passesExtractedFloor)(confidence)) {
4768
5088
  noteExtractedDropped({
4769
5089
  source: fileNodeId,
4770
5090
  target: ep.infraId,
@@ -4784,7 +5104,7 @@ async function addExternalEndpointEdges(graph, services) {
4784
5104
  source: fileNodeId,
4785
5105
  target: ep.infraId,
4786
5106
  type: edgeType,
4787
- provenance: import_types16.Provenance.EXTRACTED,
5107
+ provenance: import_types17.Provenance.EXTRACTED,
4788
5108
  confidence,
4789
5109
  evidence: ep.evidence
4790
5110
  };
@@ -4809,16 +5129,16 @@ init_cjs_shims();
4809
5129
 
4810
5130
  // src/extract/infra/docker-compose.ts
4811
5131
  init_cjs_shims();
4812
- var import_node_path27 = __toESM(require("path"), 1);
4813
- var import_types18 = require("@neat.is/types");
5132
+ var import_node_path29 = __toESM(require("path"), 1);
5133
+ var import_types19 = require("@neat.is/types");
4814
5134
 
4815
5135
  // src/extract/infra/shared.ts
4816
5136
  init_cjs_shims();
4817
- var import_types17 = require("@neat.is/types");
5137
+ var import_types18 = require("@neat.is/types");
4818
5138
  function makeInfraNode(kind, name, provider = "self", extras) {
4819
5139
  return {
4820
- id: (0, import_types17.infraId)(kind, name),
4821
- type: import_types17.NodeType.InfraNode,
5140
+ id: (0, import_types18.infraId)(kind, name),
5141
+ type: import_types18.NodeType.InfraNode,
4822
5142
  name,
4823
5143
  provider,
4824
5144
  kind,
@@ -4847,7 +5167,7 @@ function dependsOnList(value) {
4847
5167
  }
4848
5168
  function serviceNameToServiceNode(name, services) {
4849
5169
  for (const s of services) {
4850
- if (s.node.name === name || import_node_path27.default.basename(s.dir) === name) return s.node.id;
5170
+ if (s.node.name === name || import_node_path29.default.basename(s.dir) === name) return s.node.id;
4851
5171
  }
4852
5172
  return null;
4853
5173
  }
@@ -4856,7 +5176,7 @@ async function addComposeInfra(graph, scanPath, services) {
4856
5176
  let edgesAdded = 0;
4857
5177
  let composePath = null;
4858
5178
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
4859
- const abs = import_node_path27.default.join(scanPath, name);
5179
+ const abs = import_node_path29.default.join(scanPath, name);
4860
5180
  if (await exists2(abs)) {
4861
5181
  composePath = abs;
4862
5182
  break;
@@ -4869,13 +5189,13 @@ async function addComposeInfra(graph, scanPath, services) {
4869
5189
  } catch (err) {
4870
5190
  recordExtractionError(
4871
5191
  "infra docker-compose",
4872
- import_node_path27.default.relative(scanPath, composePath),
5192
+ import_node_path29.default.relative(scanPath, composePath),
4873
5193
  err
4874
5194
  );
4875
5195
  return { nodesAdded, edgesAdded };
4876
5196
  }
4877
5197
  if (!compose?.services) return { nodesAdded, edgesAdded };
4878
- const evidenceFile = import_node_path27.default.relative(scanPath, composePath).split(import_node_path27.default.sep).join("/");
5198
+ const evidenceFile = import_node_path29.default.relative(scanPath, composePath).split(import_node_path29.default.sep).join("/");
4879
5199
  const composeNameToNodeId = /* @__PURE__ */ new Map();
4880
5200
  for (const [composeName, svc] of Object.entries(compose.services)) {
4881
5201
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -4897,15 +5217,15 @@ async function addComposeInfra(graph, scanPath, services) {
4897
5217
  for (const dep of dependsOnList(svc.depends_on)) {
4898
5218
  const targetId = composeNameToNodeId.get(dep);
4899
5219
  if (!targetId) continue;
4900
- const edgeId = (0, import_types5.extractedEdgeId)(sourceId, targetId, import_types18.EdgeType.DEPENDS_ON);
5220
+ const edgeId = (0, import_types5.extractedEdgeId)(sourceId, targetId, import_types19.EdgeType.DEPENDS_ON);
4901
5221
  if (graph.hasEdge(edgeId)) continue;
4902
5222
  const edge = {
4903
5223
  id: edgeId,
4904
5224
  source: sourceId,
4905
5225
  target: targetId,
4906
- type: import_types18.EdgeType.DEPENDS_ON,
4907
- provenance: import_types18.Provenance.EXTRACTED,
4908
- confidence: (0, import_types18.confidenceForExtracted)("structural"),
5226
+ type: import_types19.EdgeType.DEPENDS_ON,
5227
+ provenance: import_types19.Provenance.EXTRACTED,
5228
+ confidence: (0, import_types19.confidenceForExtracted)("structural"),
4909
5229
  evidence: { file: evidenceFile }
4910
5230
  };
4911
5231
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -4917,9 +5237,9 @@ async function addComposeInfra(graph, scanPath, services) {
4917
5237
 
4918
5238
  // src/extract/infra/dockerfile.ts
4919
5239
  init_cjs_shims();
4920
- var import_node_path28 = __toESM(require("path"), 1);
4921
- var import_node_fs16 = require("fs");
4922
- var import_types19 = require("@neat.is/types");
5240
+ var import_node_path30 = __toESM(require("path"), 1);
5241
+ var import_node_fs17 = require("fs");
5242
+ var import_types20 = require("@neat.is/types");
4923
5243
  function runtimeImage(content) {
4924
5244
  const lines = content.split("\n");
4925
5245
  let last = null;
@@ -4938,15 +5258,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4938
5258
  let nodesAdded = 0;
4939
5259
  let edgesAdded = 0;
4940
5260
  for (const service of services) {
4941
- const dockerfilePath = import_node_path28.default.join(service.dir, "Dockerfile");
5261
+ const dockerfilePath = import_node_path30.default.join(service.dir, "Dockerfile");
4942
5262
  if (!await exists2(dockerfilePath)) continue;
4943
5263
  let content;
4944
5264
  try {
4945
- content = await import_node_fs16.promises.readFile(dockerfilePath, "utf8");
5265
+ content = await import_node_fs17.promises.readFile(dockerfilePath, "utf8");
4946
5266
  } catch (err) {
4947
5267
  recordExtractionError(
4948
5268
  "infra dockerfile",
4949
- import_node_path28.default.relative(scanPath, dockerfilePath),
5269
+ import_node_path30.default.relative(scanPath, dockerfilePath),
4950
5270
  err
4951
5271
  );
4952
5272
  continue;
@@ -4958,7 +5278,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4958
5278
  graph.addNode(node.id, node);
4959
5279
  nodesAdded++;
4960
5280
  }
4961
- const relDockerfile = toPosix2(import_node_path28.default.relative(service.dir, dockerfilePath));
5281
+ const relDockerfile = toPosix2(import_node_path30.default.relative(service.dir, dockerfilePath));
4962
5282
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
4963
5283
  graph,
4964
5284
  service.pkg.name,
@@ -4967,17 +5287,17 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4967
5287
  );
4968
5288
  nodesAdded += fn;
4969
5289
  edgesAdded += fe;
4970
- const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, node.id, import_types19.EdgeType.RUNS_ON);
5290
+ const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, node.id, import_types20.EdgeType.RUNS_ON);
4971
5291
  if (!graph.hasEdge(edgeId)) {
4972
5292
  const edge = {
4973
5293
  id: edgeId,
4974
5294
  source: fileNodeId,
4975
5295
  target: node.id,
4976
- type: import_types19.EdgeType.RUNS_ON,
4977
- provenance: import_types19.Provenance.EXTRACTED,
4978
- confidence: (0, import_types19.confidenceForExtracted)("structural"),
5296
+ type: import_types20.EdgeType.RUNS_ON,
5297
+ provenance: import_types20.Provenance.EXTRACTED,
5298
+ confidence: (0, import_types20.confidenceForExtracted)("structural"),
4979
5299
  evidence: {
4980
- file: toPosix2(import_node_path28.default.relative(scanPath, dockerfilePath))
5300
+ file: toPosix2(import_node_path30.default.relative(scanPath, dockerfilePath))
4981
5301
  }
4982
5302
  };
4983
5303
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -4989,21 +5309,21 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4989
5309
 
4990
5310
  // src/extract/infra/terraform.ts
4991
5311
  init_cjs_shims();
4992
- var import_node_fs17 = require("fs");
4993
- var import_node_path29 = __toESM(require("path"), 1);
5312
+ var import_node_fs18 = require("fs");
5313
+ var import_node_path31 = __toESM(require("path"), 1);
4994
5314
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
4995
5315
  async function walkTfFiles(start, depth = 0, max = 5) {
4996
5316
  if (depth > max) return [];
4997
5317
  const out = [];
4998
- const entries = await import_node_fs17.promises.readdir(start, { withFileTypes: true }).catch(() => []);
5318
+ const entries = await import_node_fs18.promises.readdir(start, { withFileTypes: true }).catch(() => []);
4999
5319
  for (const entry of entries) {
5000
5320
  if (entry.isDirectory()) {
5001
5321
  if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
5002
- const child = import_node_path29.default.join(start, entry.name);
5322
+ const child = import_node_path31.default.join(start, entry.name);
5003
5323
  if (await isPythonVenvDir(child)) continue;
5004
5324
  out.push(...await walkTfFiles(child, depth + 1, max));
5005
5325
  } else if (entry.isFile() && entry.name.endsWith(".tf")) {
5006
- out.push(import_node_path29.default.join(start, entry.name));
5326
+ out.push(import_node_path31.default.join(start, entry.name));
5007
5327
  }
5008
5328
  }
5009
5329
  return out;
@@ -5012,7 +5332,7 @@ async function addTerraformResources(graph, scanPath) {
5012
5332
  let nodesAdded = 0;
5013
5333
  const files = await walkTfFiles(scanPath);
5014
5334
  for (const file of files) {
5015
- const content = await import_node_fs17.promises.readFile(file, "utf8");
5335
+ const content = await import_node_fs18.promises.readFile(file, "utf8");
5016
5336
  RESOURCE_RE.lastIndex = 0;
5017
5337
  let m;
5018
5338
  while ((m = RESOURCE_RE.exec(content)) !== null) {
@@ -5030,8 +5350,8 @@ async function addTerraformResources(graph, scanPath) {
5030
5350
 
5031
5351
  // src/extract/infra/k8s.ts
5032
5352
  init_cjs_shims();
5033
- var import_node_fs18 = require("fs");
5034
- var import_node_path30 = __toESM(require("path"), 1);
5353
+ var import_node_fs19 = require("fs");
5354
+ var import_node_path32 = __toESM(require("path"), 1);
5035
5355
  var import_yaml3 = require("yaml");
5036
5356
  var K8S_KIND_TO_INFRA_KIND = {
5037
5357
  Service: "k8s-service",
@@ -5045,15 +5365,15 @@ var K8S_KIND_TO_INFRA_KIND = {
5045
5365
  async function walkYamlFiles2(start, depth = 0, max = 5) {
5046
5366
  if (depth > max) return [];
5047
5367
  const out = [];
5048
- const entries = await import_node_fs18.promises.readdir(start, { withFileTypes: true }).catch(() => []);
5368
+ const entries = await import_node_fs19.promises.readdir(start, { withFileTypes: true }).catch(() => []);
5049
5369
  for (const entry of entries) {
5050
5370
  if (entry.isDirectory()) {
5051
5371
  if (IGNORED_DIRS.has(entry.name)) continue;
5052
- const child = import_node_path30.default.join(start, entry.name);
5372
+ const child = import_node_path32.default.join(start, entry.name);
5053
5373
  if (await isPythonVenvDir(child)) continue;
5054
5374
  out.push(...await walkYamlFiles2(child, depth + 1, max));
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));
5375
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path32.default.extname(entry.name))) {
5376
+ out.push(import_node_path32.default.join(start, entry.name));
5057
5377
  }
5058
5378
  }
5059
5379
  return out;
@@ -5062,7 +5382,7 @@ async function addK8sResources(graph, scanPath) {
5062
5382
  let nodesAdded = 0;
5063
5383
  const files = await walkYamlFiles2(scanPath);
5064
5384
  for (const file of files) {
5065
- const content = await import_node_fs18.promises.readFile(file, "utf8");
5385
+ const content = await import_node_fs19.promises.readFile(file, "utf8");
5066
5386
  let docs;
5067
5387
  try {
5068
5388
  docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
@@ -5097,17 +5417,17 @@ async function addInfra(graph, scanPath, services) {
5097
5417
  }
5098
5418
 
5099
5419
  // src/extract/index.ts
5100
- var import_node_path32 = __toESM(require("path"), 1);
5420
+ var import_node_path34 = __toESM(require("path"), 1);
5101
5421
 
5102
5422
  // src/extract/retire.ts
5103
5423
  init_cjs_shims();
5104
- var import_node_fs19 = require("fs");
5105
- var import_node_path31 = __toESM(require("path"), 1);
5106
- var import_types20 = require("@neat.is/types");
5424
+ var import_node_fs20 = require("fs");
5425
+ var import_node_path33 = __toESM(require("path"), 1);
5426
+ var import_types21 = require("@neat.is/types");
5107
5427
  function dropOrphanedFileNodes(graph) {
5108
5428
  const orphans = [];
5109
5429
  graph.forEachNode((id, attrs) => {
5110
- if (attrs.type !== import_types20.NodeType.FileNode) return;
5430
+ if (attrs.type !== import_types21.NodeType.FileNode) return;
5111
5431
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
5112
5432
  orphans.push(id);
5113
5433
  }
@@ -5120,14 +5440,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
5120
5440
  const bases = [scanPath, ...serviceDirs];
5121
5441
  graph.forEachEdge((id, attrs) => {
5122
5442
  const edge = attrs;
5123
- if (edge.provenance !== import_types20.Provenance.EXTRACTED) return;
5443
+ if (edge.provenance !== import_types21.Provenance.EXTRACTED) return;
5124
5444
  const evidenceFile = edge.evidence?.file;
5125
5445
  if (!evidenceFile) return;
5126
- if (import_node_path31.default.isAbsolute(evidenceFile)) {
5127
- if (!(0, import_node_fs19.existsSync)(evidenceFile)) toDrop.push(id);
5446
+ if (import_node_path33.default.isAbsolute(evidenceFile)) {
5447
+ if (!(0, import_node_fs20.existsSync)(evidenceFile)) toDrop.push(id);
5128
5448
  return;
5129
5449
  }
5130
- const found = bases.some((base) => (0, import_node_fs19.existsSync)(import_node_path31.default.join(base, evidenceFile)));
5450
+ const found = bases.some((base) => (0, import_node_fs20.existsSync)(import_node_path33.default.join(base, evidenceFile)));
5131
5451
  if (!found) toDrop.push(id);
5132
5452
  });
5133
5453
  for (const id of toDrop) graph.dropEdge(id);
@@ -5142,6 +5462,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
5142
5462
  const services = await discoverServices(scanPath);
5143
5463
  const phase1Nodes = addServiceNodes(graph, services);
5144
5464
  await addServiceAliases(graph, scanPath, services);
5465
+ const fileEnum = await addFiles(graph, services);
5466
+ const importGraph = await addImports(graph, services);
5145
5467
  const phase2 = await addDatabasesAndCompat(graph, services, scanPath);
5146
5468
  const phase3 = await addConfigNodes(graph, services, scanPath);
5147
5469
  const phase4 = await addCallEdges(graph, services);
@@ -5165,7 +5487,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
5165
5487
  }
5166
5488
  const droppedEntries = drainDroppedExtracted();
5167
5489
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
5168
- const rejectedPath = import_node_path32.default.join(import_node_path32.default.dirname(opts.errorsPath), "rejected.ndjson");
5490
+ const rejectedPath = import_node_path34.default.join(import_node_path34.default.dirname(opts.errorsPath), "rejected.ndjson");
5169
5491
  try {
5170
5492
  await writeRejectedExtracted(droppedEntries, rejectedPath);
5171
5493
  } catch (err) {
@@ -5175,8 +5497,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
5175
5497
  }
5176
5498
  }
5177
5499
  const result = {
5178
- nodesAdded: phase1Nodes + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
5179
- edgesAdded: phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
5500
+ nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
5501
+ edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
5180
5502
  frontiersPromoted,
5181
5503
  extractionErrors: errorEntries.length,
5182
5504
  errorEntries,
@@ -5199,7 +5521,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
5199
5521
 
5200
5522
  // src/diff.ts
5201
5523
  init_cjs_shims();
5202
- var import_node_fs20 = require("fs");
5524
+ var import_node_fs21 = require("fs");
5203
5525
  async function loadSnapshotForDiff(target) {
5204
5526
  if (/^https?:\/\//i.test(target)) {
5205
5527
  const res = await fetch(target);
@@ -5208,7 +5530,7 @@ async function loadSnapshotForDiff(target) {
5208
5530
  }
5209
5531
  return await res.json();
5210
5532
  }
5211
- const raw = await import_node_fs20.promises.readFile(target, "utf8");
5533
+ const raw = await import_node_fs21.promises.readFile(target, "utf8");
5212
5534
  return JSON.parse(raw);
5213
5535
  }
5214
5536
  function indexEntries(entries) {
@@ -5276,9 +5598,9 @@ function canonicalJson(value) {
5276
5598
 
5277
5599
  // src/persist.ts
5278
5600
  init_cjs_shims();
5279
- var import_node_fs21 = require("fs");
5280
- var import_node_path33 = __toESM(require("path"), 1);
5281
- var import_types21 = require("@neat.is/types");
5601
+ var import_node_fs22 = require("fs");
5602
+ var import_node_path35 = __toESM(require("path"), 1);
5603
+ var import_types22 = require("@neat.is/types");
5282
5604
  var SCHEMA_VERSION = 4;
5283
5605
  function migrateV1ToV2(payload) {
5284
5606
  const nodes = payload.graph.nodes;
@@ -5300,12 +5622,12 @@ function migrateV2ToV3(payload) {
5300
5622
  for (const edge of edges) {
5301
5623
  const attrs = edge.attributes;
5302
5624
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
5303
- attrs.provenance = import_types21.Provenance.OBSERVED;
5625
+ attrs.provenance = import_types22.Provenance.OBSERVED;
5304
5626
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
5305
5627
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
5306
5628
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
5307
5629
  if (type && source && target) {
5308
- const newId = (0, import_types21.observedEdgeId)(source, target, type);
5630
+ const newId = (0, import_types22.observedEdgeId)(source, target, type);
5309
5631
  attrs.id = newId;
5310
5632
  if (edge.key) edge.key = newId;
5311
5633
  }
@@ -5314,7 +5636,7 @@ function migrateV2ToV3(payload) {
5314
5636
  return { ...payload, schemaVersion: 3 };
5315
5637
  }
5316
5638
  async function ensureDir(filePath) {
5317
- await import_node_fs21.promises.mkdir(import_node_path33.default.dirname(filePath), { recursive: true });
5639
+ await import_node_fs22.promises.mkdir(import_node_path35.default.dirname(filePath), { recursive: true });
5318
5640
  }
5319
5641
  async function saveGraphToDisk(graph, outPath) {
5320
5642
  await ensureDir(outPath);
@@ -5324,13 +5646,13 @@ async function saveGraphToDisk(graph, outPath) {
5324
5646
  graph: graph.export()
5325
5647
  };
5326
5648
  const tmp = `${outPath}.tmp`;
5327
- await import_node_fs21.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
5328
- await import_node_fs21.promises.rename(tmp, outPath);
5649
+ await import_node_fs22.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
5650
+ await import_node_fs22.promises.rename(tmp, outPath);
5329
5651
  }
5330
5652
  async function loadGraphFromDisk(graph, outPath) {
5331
5653
  let raw;
5332
5654
  try {
5333
- raw = await import_node_fs21.promises.readFile(outPath, "utf8");
5655
+ raw = await import_node_fs22.promises.readFile(outPath, "utf8");
5334
5656
  } catch (err) {
5335
5657
  if (err.code === "ENOENT") return;
5336
5658
  throw err;
@@ -5389,23 +5711,23 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
5389
5711
 
5390
5712
  // src/projects.ts
5391
5713
  init_cjs_shims();
5392
- var import_node_path34 = __toESM(require("path"), 1);
5714
+ var import_node_path36 = __toESM(require("path"), 1);
5393
5715
  function pathsForProject(project, baseDir) {
5394
5716
  if (project === DEFAULT_PROJECT) {
5395
5717
  return {
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")
5718
+ snapshotPath: import_node_path36.default.join(baseDir, "graph.json"),
5719
+ errorsPath: import_node_path36.default.join(baseDir, "errors.ndjson"),
5720
+ staleEventsPath: import_node_path36.default.join(baseDir, "stale-events.ndjson"),
5721
+ embeddingsCachePath: import_node_path36.default.join(baseDir, "embeddings.json"),
5722
+ policyViolationsPath: import_node_path36.default.join(baseDir, "policy-violations.ndjson")
5401
5723
  };
5402
5724
  }
5403
5725
  return {
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`)
5726
+ snapshotPath: import_node_path36.default.join(baseDir, `${project}.json`),
5727
+ errorsPath: import_node_path36.default.join(baseDir, `errors.${project}.ndjson`),
5728
+ staleEventsPath: import_node_path36.default.join(baseDir, `stale-events.${project}.ndjson`),
5729
+ embeddingsCachePath: import_node_path36.default.join(baseDir, `embeddings.${project}.json`),
5730
+ policyViolationsPath: import_node_path36.default.join(baseDir, `policy-violations.${project}.ndjson`)
5409
5731
  };
5410
5732
  }
5411
5733
  var Projects = class {
@@ -5445,23 +5767,23 @@ function parseExtraProjects(raw) {
5445
5767
 
5446
5768
  // src/registry.ts
5447
5769
  init_cjs_shims();
5448
- var import_node_fs22 = require("fs");
5770
+ var import_node_fs23 = require("fs");
5449
5771
  var import_node_os3 = __toESM(require("os"), 1);
5450
- var import_node_path35 = __toESM(require("path"), 1);
5451
- var import_types22 = require("@neat.is/types");
5772
+ var import_node_path37 = __toESM(require("path"), 1);
5773
+ var import_types23 = require("@neat.is/types");
5452
5774
  function neatHome() {
5453
5775
  const override = process.env.NEAT_HOME;
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");
5776
+ if (override && override.length > 0) return import_node_path37.default.resolve(override);
5777
+ return import_node_path37.default.join(import_node_os3.default.homedir(), ".neat");
5456
5778
  }
5457
5779
  function registryPath() {
5458
- return import_node_path35.default.join(neatHome(), "projects.json");
5780
+ return import_node_path37.default.join(neatHome(), "projects.json");
5459
5781
  }
5460
5782
  async function readRegistry() {
5461
5783
  const file = registryPath();
5462
5784
  let raw;
5463
5785
  try {
5464
- raw = await import_node_fs22.promises.readFile(file, "utf8");
5786
+ raw = await import_node_fs23.promises.readFile(file, "utf8");
5465
5787
  } catch (err) {
5466
5788
  if (err.code === "ENOENT") {
5467
5789
  return { version: 1, projects: [] };
@@ -5469,7 +5791,7 @@ async function readRegistry() {
5469
5791
  throw err;
5470
5792
  }
5471
5793
  const parsed = JSON.parse(raw);
5472
- return import_types22.RegistryFileSchema.parse(parsed);
5794
+ return import_types23.RegistryFileSchema.parse(parsed);
5473
5795
  }
5474
5796
  async function getProject(name) {
5475
5797
  const reg = await readRegistry();
@@ -5671,11 +5993,11 @@ function registerRoutes(scope, ctx) {
5671
5993
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
5672
5994
  const parsed = [];
5673
5995
  for (const c of candidates) {
5674
- const r = import_types23.DivergenceTypeSchema.safeParse(c);
5996
+ const r = import_types24.DivergenceTypeSchema.safeParse(c);
5675
5997
  if (!r.success) {
5676
5998
  return reply.code(400).send({
5677
5999
  error: `unknown divergence type "${c}"`,
5678
- allowed: import_types23.DivergenceTypeSchema.options
6000
+ allowed: import_types24.DivergenceTypeSchema.options
5679
6001
  });
5680
6002
  }
5681
6003
  parsed.push(r.data);
@@ -5888,7 +6210,7 @@ function registerRoutes(scope, ctx) {
5888
6210
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
5889
6211
  let violations = await log.readAll();
5890
6212
  if (req.query.severity) {
5891
- const sev = import_types23.PolicySeveritySchema.safeParse(req.query.severity);
6213
+ const sev = import_types24.PolicySeveritySchema.safeParse(req.query.severity);
5892
6214
  if (!sev.success) {
5893
6215
  return reply.code(400).send({
5894
6216
  error: "invalid severity",
@@ -5905,7 +6227,7 @@ function registerRoutes(scope, ctx) {
5905
6227
  scope.post("/policies/check", async (req, reply) => {
5906
6228
  const proj = resolveProject(registry, req, reply, ctx.bootstrap);
5907
6229
  if (!proj) return;
5908
- const parsed = import_types23.PoliciesCheckBodySchema.safeParse(req.body ?? {});
6230
+ const parsed = import_types24.PoliciesCheckBodySchema.safeParse(req.body ?? {});
5909
6231
  if (!parsed.success) {
5910
6232
  return reply.code(400).send({
5911
6233
  error: "invalid /policies/check body",
@@ -6147,8 +6469,8 @@ init_otel_grpc();
6147
6469
 
6148
6470
  // src/search.ts
6149
6471
  init_cjs_shims();
6150
- var import_node_fs23 = require("fs");
6151
- var import_node_path38 = __toESM(require("path"), 1);
6472
+ var import_node_fs24 = require("fs");
6473
+ var import_node_path40 = __toESM(require("path"), 1);
6152
6474
  var import_node_crypto3 = require("crypto");
6153
6475
  var DEFAULT_LIMIT = 10;
6154
6476
  var NOMIC_DIM = 768;
@@ -6278,7 +6600,7 @@ async function pickEmbedder() {
6278
6600
  }
6279
6601
  async function readCache(cachePath) {
6280
6602
  try {
6281
- const raw = await import_node_fs23.promises.readFile(cachePath, "utf8");
6603
+ const raw = await import_node_fs24.promises.readFile(cachePath, "utf8");
6282
6604
  const parsed = JSON.parse(raw);
6283
6605
  if (parsed.version !== 1) return null;
6284
6606
  return parsed;
@@ -6287,8 +6609,8 @@ async function readCache(cachePath) {
6287
6609
  }
6288
6610
  }
6289
6611
  async function writeCache(cachePath, 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));
6612
+ await import_node_fs24.promises.mkdir(import_node_path40.default.dirname(cachePath), { recursive: true });
6613
+ await import_node_fs24.promises.writeFile(cachePath, JSON.stringify(cache));
6292
6614
  }
6293
6615
  var VectorIndex = class {
6294
6616
  constructor(embedder, cachePath) {
@@ -6468,14 +6790,14 @@ async function bootProject(registry, name, scanPath, baseDir) {
6468
6790
  async function main() {
6469
6791
  const baseDirEnv = process.env.NEAT_OUT_DIR;
6470
6792
  const legacyOutPath = process.env.NEAT_OUT_PATH;
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");
6793
+ const baseDir = baseDirEnv ? import_node_path41.default.resolve(baseDirEnv) : legacyOutPath ? import_node_path41.default.resolve(import_node_path41.default.dirname(legacyOutPath)) : import_node_path41.default.resolve("./neat-out");
6794
+ const defaultScanPath = import_node_path41.default.resolve(process.env.NEAT_SCAN_PATH ?? "./demo");
6473
6795
  const registry = new Projects();
6474
6796
  await bootProject(registry, DEFAULT_PROJECT, defaultScanPath, baseDir);
6475
6797
  for (const name of parseExtraProjects(process.env.NEAT_PROJECTS)) {
6476
6798
  const envKey = `NEAT_PROJECT_SCAN_PATH_${name.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
6477
6799
  const projectScan = process.env[envKey];
6478
- await bootProject(registry, name, projectScan ? import_node_path39.default.resolve(projectScan) : void 0, baseDir);
6800
+ await bootProject(registry, name, projectScan ? import_node_path41.default.resolve(projectScan) : void 0, baseDir);
6479
6801
  }
6480
6802
  const host = process.env.HOST ?? "0.0.0.0";
6481
6803
  const port = Number(process.env.PORT ?? 8080);