@neat.is/core 0.4.12 → 0.4.14

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/neatd.cjs CHANGED
@@ -57,9 +57,9 @@ function mountBearerAuth(app, opts) {
57
57
  const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
58
58
  const publicRead = opts.publicRead === true;
59
59
  app.addHook("preHandler", (req2, reply, done) => {
60
- const path42 = (req2.url.split("?")[0] ?? "").replace(/\/+$/, "");
60
+ const path44 = (req2.url.split("?")[0] ?? "").replace(/\/+$/, "");
61
61
  for (const suffix of suffixes) {
62
- if (path42 === suffix || path42.endsWith(suffix)) {
62
+ if (path44 === suffix || path44.endsWith(suffix)) {
63
63
  done();
64
64
  return;
65
65
  }
@@ -133,7 +133,9 @@ __export(otel_grpc_exports, {
133
133
  });
134
134
  function bytesToHex(buf) {
135
135
  if (!buf) return "";
136
- return Buffer.isBuffer(buf) ? buf.toString("hex") : "";
136
+ if (Buffer.isBuffer(buf)) return buf.toString("hex");
137
+ if (buf instanceof Uint8Array) return Buffer.from(buf).toString("hex");
138
+ return "";
137
139
  }
138
140
  function nanosToString(n) {
139
141
  if (n === void 0 || n === null) return "0";
@@ -185,8 +187,8 @@ function reshapeGrpcRequest(req2) {
185
187
  };
186
188
  }
187
189
  function resolveProtoRoot() {
188
- const here = import_node_path36.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
189
- return import_node_path36.default.resolve(here, "..", "proto");
190
+ const here = import_node_path38.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
191
+ return import_node_path38.default.resolve(here, "..", "proto");
190
192
  }
191
193
  function loadTraceService() {
192
194
  const protoRoot = resolveProtoRoot();
@@ -254,13 +256,13 @@ async function startOtelGrpcReceiver(opts) {
254
256
  })
255
257
  };
256
258
  }
257
- var import_node_url, import_node_path36, import_node_crypto2, grpc, protoLoader;
259
+ var import_node_url, import_node_path38, import_node_crypto2, grpc, protoLoader;
258
260
  var init_otel_grpc = __esm({
259
261
  "src/otel-grpc.ts"() {
260
262
  "use strict";
261
263
  init_cjs_shims();
262
264
  import_node_url = require("url");
263
- import_node_path36 = __toESM(require("path"), 1);
265
+ import_node_path38 = __toESM(require("path"), 1);
264
266
  import_node_crypto2 = require("crypto");
265
267
  grpc = __toESM(require("@grpc/grpc-js"), 1);
266
268
  protoLoader = __toESM(require("@grpc/proto-loader"), 1);
@@ -370,10 +372,10 @@ function parseOtlpRequest(body) {
370
372
  return out;
371
373
  }
372
374
  function loadProtoRoot() {
373
- const here = import_node_path37.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
374
- const protoRoot = import_node_path37.default.resolve(here, "..", "proto");
375
+ const here = import_node_path39.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
376
+ const protoRoot = import_node_path39.default.resolve(here, "..", "proto");
375
377
  const root = new import_protobufjs.default.Root();
376
- root.resolvePath = (_origin, target) => import_node_path37.default.resolve(protoRoot, target);
378
+ root.resolvePath = (_origin, target) => import_node_path39.default.resolve(protoRoot, target);
377
379
  root.loadSync(
378
380
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
379
381
  { keepCase: true }
@@ -406,7 +408,10 @@ function encodeProtobufResponseBody() {
406
408
  }
407
409
  async function decodeProtobufBody(buf) {
408
410
  const Type = loadProtobufDecoder();
409
- const decoded = Type.decode(buf).toJSON();
411
+ const decoded = Type.toObject(Type.decode(buf), {
412
+ longs: String,
413
+ enums: Number
414
+ });
410
415
  const { reshapeGrpcRequest: reshapeGrpcRequest2 } = await Promise.resolve().then(() => (init_otel_grpc(), otel_grpc_exports));
411
416
  return reshapeGrpcRequest2(decoded);
412
417
  }
@@ -566,12 +571,12 @@ async function buildOtelReceiver(opts) {
566
571
  };
567
572
  return decorated;
568
573
  }
569
- var import_node_path37, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
574
+ var import_node_path39, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
570
575
  var init_otel = __esm({
571
576
  "src/otel.ts"() {
572
577
  "use strict";
573
578
  init_cjs_shims();
574
- import_node_path37 = __toESM(require("path"), 1);
579
+ import_node_path39 = __toESM(require("path"), 1);
575
580
  import_node_url2 = require("url");
576
581
  import_fastify2 = __toESM(require("fastify"), 1);
577
582
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -587,14 +592,14 @@ var init_otel = __esm({
587
592
 
588
593
  // src/neatd.ts
589
594
  init_cjs_shims();
590
- var import_node_fs25 = require("fs");
591
- var import_node_path41 = __toESM(require("path"), 1);
595
+ var import_node_fs26 = require("fs");
596
+ var import_node_path43 = __toESM(require("path"), 1);
592
597
  var import_node_module = require("module");
593
598
 
594
599
  // src/daemon.ts
595
600
  init_cjs_shims();
596
- var import_node_fs24 = require("fs");
597
- var import_node_path39 = __toESM(require("path"), 1);
601
+ var import_node_fs25 = require("fs");
602
+ var import_node_path41 = __toESM(require("path"), 1);
598
603
 
599
604
  // src/graph.ts
600
605
  init_cjs_shims();
@@ -1073,19 +1078,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1073
1078
  function longestIncomingWalk(graph, start, maxDepth) {
1074
1079
  let best = { path: [start], edges: [] };
1075
1080
  const visited = /* @__PURE__ */ new Set([start]);
1076
- function step(node, path42, edges) {
1077
- if (path42.length > best.path.length) {
1078
- best = { path: [...path42], edges: [...edges] };
1081
+ function step(node, path44, edges) {
1082
+ if (path44.length > best.path.length) {
1083
+ best = { path: [...path44], edges: [...edges] };
1079
1084
  }
1080
- if (path42.length - 1 >= maxDepth) return;
1085
+ if (path44.length - 1 >= maxDepth) return;
1081
1086
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1082
1087
  for (const [srcId, edge] of incoming) {
1083
1088
  if (visited.has(srcId)) continue;
1084
1089
  visited.add(srcId);
1085
- path42.push(srcId);
1090
+ path44.push(srcId);
1086
1091
  edges.push(edge);
1087
- step(srcId, path42, edges);
1088
- path42.pop();
1092
+ step(srcId, path44, edges);
1093
+ path44.pop();
1089
1094
  edges.pop();
1090
1095
  visited.delete(srcId);
1091
1096
  }
@@ -2950,10 +2955,8 @@ async function addServiceAliases(graph, scanPath, services) {
2950
2955
  await collectK8sAliases(graph, scanPath, byName);
2951
2956
  }
2952
2957
 
2953
- // src/extract/databases/index.ts
2958
+ // src/extract/files.ts
2954
2959
  init_cjs_shims();
2955
- var import_node_path18 = __toESM(require("path"), 1);
2956
- var import_types8 = require("@neat.is/types");
2957
2960
 
2958
2961
  // src/extract/calls/shared.ts
2959
2962
  init_cjs_shims();
@@ -3052,11 +3055,333 @@ function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
3052
3055
  return { fileNodeId, nodesAdded, edgesAdded };
3053
3056
  }
3054
3057
 
3058
+ // src/extract/files.ts
3059
+ var import_node_path11 = __toESM(require("path"), 1);
3060
+ async function addFiles(graph, services) {
3061
+ let nodesAdded = 0;
3062
+ let edgesAdded = 0;
3063
+ for (const service of services) {
3064
+ const filePaths = await walkSourceFiles(service.dir);
3065
+ for (const filePath of filePaths) {
3066
+ const relPath = toPosix2(import_node_path11.default.relative(service.dir, filePath));
3067
+ const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
3068
+ graph,
3069
+ service.pkg.name,
3070
+ service.node.id,
3071
+ relPath
3072
+ );
3073
+ nodesAdded += n;
3074
+ edgesAdded += e;
3075
+ }
3076
+ }
3077
+ return { nodesAdded, edgesAdded };
3078
+ }
3079
+
3080
+ // src/extract/imports.ts
3081
+ init_cjs_shims();
3082
+ var import_node_path12 = __toESM(require("path"), 1);
3083
+ var import_node_fs11 = require("fs");
3084
+ var import_tree_sitter = __toESM(require("tree-sitter"), 1);
3085
+ var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
3086
+ var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
3087
+ var import_types8 = require("@neat.is/types");
3088
+ var PARSE_CHUNK = 16384;
3089
+ function parseSource(parser, source) {
3090
+ return parser.parse(
3091
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK)
3092
+ );
3093
+ }
3094
+ function makeJsParser() {
3095
+ const p = new import_tree_sitter.default();
3096
+ p.setLanguage(import_tree_sitter_javascript.default);
3097
+ return p;
3098
+ }
3099
+ function makePyParser() {
3100
+ const p = new import_tree_sitter.default();
3101
+ p.setLanguage(import_tree_sitter_python.default);
3102
+ return p;
3103
+ }
3104
+ function stringLiteralText(node) {
3105
+ for (let i = 0; i < node.childCount; i++) {
3106
+ const child = node.child(i);
3107
+ if (child?.type === "string_fragment") return child.text;
3108
+ }
3109
+ const raw = node.text;
3110
+ if (raw.length >= 2) return raw.slice(1, -1);
3111
+ return raw.length === 0 ? null : "";
3112
+ }
3113
+ function clipSnippet(text) {
3114
+ const oneLine = text.split("\n")[0] ?? text;
3115
+ return oneLine.length > 120 ? oneLine.slice(0, 120) : oneLine;
3116
+ }
3117
+ function collectJsImports(node, out) {
3118
+ if (node.type === "import_statement") {
3119
+ const source = node.childForFieldName("source");
3120
+ if (source) {
3121
+ const specifier = stringLiteralText(source);
3122
+ if (specifier) {
3123
+ out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
3124
+ }
3125
+ }
3126
+ return;
3127
+ }
3128
+ if (node.type === "call_expression") {
3129
+ const fn = node.childForFieldName("function");
3130
+ if (fn?.type === "identifier" && fn.text === "require") {
3131
+ const args = node.childForFieldName("arguments");
3132
+ const firstArg = args?.namedChild(0);
3133
+ if (firstArg?.type === "string") {
3134
+ const specifier = stringLiteralText(firstArg);
3135
+ if (specifier) {
3136
+ out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
3137
+ }
3138
+ }
3139
+ }
3140
+ }
3141
+ for (let i = 0; i < node.namedChildCount; i++) {
3142
+ const child = node.namedChild(i);
3143
+ if (child) collectJsImports(child, out);
3144
+ }
3145
+ }
3146
+ function collectPyImports(node, out) {
3147
+ if (node.type === "import_from_statement") {
3148
+ let level = 0;
3149
+ let modulePath = "";
3150
+ let pastFrom = false;
3151
+ for (let i = 0; i < node.childCount; i++) {
3152
+ const child = node.child(i);
3153
+ if (!child) continue;
3154
+ if (!pastFrom) {
3155
+ if (child.type === "from") pastFrom = true;
3156
+ continue;
3157
+ }
3158
+ if (child.type === "import") break;
3159
+ if (child.type === "relative_import") {
3160
+ for (let j = 0; j < child.childCount; j++) {
3161
+ const rc = child.child(j);
3162
+ if (!rc) continue;
3163
+ if (rc.type === "import_prefix") {
3164
+ for (let k = 0; k < rc.childCount; k++) {
3165
+ if (rc.child(k)?.type === ".") level++;
3166
+ }
3167
+ } else if (rc.type === "dotted_name") modulePath = rc.text;
3168
+ }
3169
+ break;
3170
+ }
3171
+ if (child.type === "dotted_name") {
3172
+ modulePath = child.text;
3173
+ break;
3174
+ }
3175
+ }
3176
+ if (level > 0 || modulePath) {
3177
+ out.push({ modulePath, level, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
3178
+ }
3179
+ }
3180
+ for (let i = 0; i < node.namedChildCount; i++) {
3181
+ const child = node.namedChild(i);
3182
+ if (child) collectPyImports(child, out);
3183
+ }
3184
+ }
3185
+ async function fileExists(p) {
3186
+ try {
3187
+ await import_node_fs11.promises.access(p);
3188
+ return true;
3189
+ } catch {
3190
+ return false;
3191
+ }
3192
+ }
3193
+ function isWithinServiceDir(candidate, serviceDir) {
3194
+ const rel = import_node_path12.default.relative(serviceDir, candidate);
3195
+ return rel !== "" && !rel.startsWith("..") && !import_node_path12.default.isAbsolute(rel);
3196
+ }
3197
+ var JS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
3198
+ var JS_INDEX_FILES = JS_EXTENSIONS.map((ext) => `index${ext}`);
3199
+ async function firstExistingCandidate(base, serviceDir) {
3200
+ for (const ext of JS_EXTENSIONS) {
3201
+ const candidate = base + ext;
3202
+ if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
3203
+ return toPosix2(import_node_path12.default.relative(serviceDir, candidate));
3204
+ }
3205
+ }
3206
+ for (const indexFile of JS_INDEX_FILES) {
3207
+ const candidate = import_node_path12.default.join(base, indexFile);
3208
+ if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
3209
+ return toPosix2(import_node_path12.default.relative(serviceDir, candidate));
3210
+ }
3211
+ }
3212
+ return null;
3213
+ }
3214
+ async function loadTsPathConfig(serviceDir) {
3215
+ const tsconfigPath = import_node_path12.default.join(serviceDir, "tsconfig.json");
3216
+ let raw;
3217
+ try {
3218
+ raw = await import_node_fs11.promises.readFile(tsconfigPath, "utf8");
3219
+ } catch {
3220
+ return null;
3221
+ }
3222
+ try {
3223
+ const parsed = JSON.parse(raw);
3224
+ const paths = parsed.compilerOptions?.paths;
3225
+ if (!paths || Object.keys(paths).length === 0) return null;
3226
+ const baseUrl = parsed.compilerOptions?.baseUrl;
3227
+ return { paths, baseDir: baseUrl ? import_node_path12.default.resolve(serviceDir, baseUrl) : serviceDir };
3228
+ } catch (err) {
3229
+ recordExtractionError("import alias resolution", tsconfigPath, err);
3230
+ return null;
3231
+ }
3232
+ }
3233
+ async function resolveTsAlias(specifier, config, serviceDir) {
3234
+ for (const [pattern, targets] of Object.entries(config.paths)) {
3235
+ let suffix = null;
3236
+ if (pattern === specifier) {
3237
+ suffix = "";
3238
+ } else if (pattern.endsWith("/*")) {
3239
+ const prefix = pattern.slice(0, -1);
3240
+ if (specifier.startsWith(prefix)) suffix = specifier.slice(prefix.length);
3241
+ }
3242
+ if (suffix === null) continue;
3243
+ for (const target of targets) {
3244
+ const targetBase = target.endsWith("/*") ? target.slice(0, -2) : target.replace(/\*$/, "");
3245
+ const resolvedBase = import_node_path12.default.resolve(config.baseDir, targetBase, suffix);
3246
+ const hit = await firstExistingCandidate(resolvedBase, serviceDir);
3247
+ if (hit) return hit;
3248
+ if (isWithinServiceDir(resolvedBase, serviceDir) && await fileExists(resolvedBase)) {
3249
+ return toPosix2(import_node_path12.default.relative(serviceDir, resolvedBase));
3250
+ }
3251
+ }
3252
+ }
3253
+ return null;
3254
+ }
3255
+ async function resolveJsImport(specifier, importerDir, serviceDir, tsPaths) {
3256
+ if (!specifier) return null;
3257
+ if (specifier.startsWith("./") || specifier.startsWith("../")) {
3258
+ const base = import_node_path12.default.resolve(importerDir, specifier);
3259
+ const ext = import_node_path12.default.extname(specifier);
3260
+ if (ext) {
3261
+ if (ext === ".js" || ext === ".jsx") {
3262
+ const tsExt = ext === ".jsx" ? ".tsx" : ".ts";
3263
+ const tsSibling = base.slice(0, -ext.length) + tsExt;
3264
+ if (isWithinServiceDir(tsSibling, serviceDir) && await fileExists(tsSibling)) {
3265
+ return toPosix2(import_node_path12.default.relative(serviceDir, tsSibling));
3266
+ }
3267
+ }
3268
+ if (isWithinServiceDir(base, serviceDir) && await fileExists(base)) {
3269
+ return toPosix2(import_node_path12.default.relative(serviceDir, base));
3270
+ }
3271
+ return null;
3272
+ }
3273
+ return firstExistingCandidate(base, serviceDir);
3274
+ }
3275
+ if (tsPaths) return resolveTsAlias(specifier, tsPaths, serviceDir);
3276
+ return null;
3277
+ }
3278
+ async function resolvePyImport(imp, importerPath, serviceDir) {
3279
+ if (!imp.modulePath) return null;
3280
+ const relPath = imp.modulePath.split(".").join("/");
3281
+ let baseDir;
3282
+ if (imp.level > 0) {
3283
+ baseDir = import_node_path12.default.dirname(importerPath);
3284
+ for (let i = 1; i < imp.level; i++) baseDir = import_node_path12.default.dirname(baseDir);
3285
+ } else {
3286
+ baseDir = serviceDir;
3287
+ }
3288
+ const candidates = [import_node_path12.default.join(baseDir, `${relPath}.py`), import_node_path12.default.join(baseDir, relPath, "__init__.py")];
3289
+ for (const candidate of candidates) {
3290
+ if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
3291
+ return toPosix2(import_node_path12.default.relative(serviceDir, candidate));
3292
+ }
3293
+ }
3294
+ return null;
3295
+ }
3296
+ function emitImportEdge(graph, serviceName, importerFileId, importerRelPath, importeeRelPath, line, snippet2) {
3297
+ const importeeFileId = (0, import_types8.fileId)(serviceName, importeeRelPath);
3298
+ if (!graph.hasNode(importeeFileId)) return 0;
3299
+ const edgeId = (0, import_types8.extractedEdgeId)(importerFileId, importeeFileId, import_types8.EdgeType.IMPORTS);
3300
+ if (graph.hasEdge(edgeId)) return 0;
3301
+ const edge = {
3302
+ id: edgeId,
3303
+ source: importerFileId,
3304
+ target: importeeFileId,
3305
+ type: import_types8.EdgeType.IMPORTS,
3306
+ provenance: import_types8.Provenance.EXTRACTED,
3307
+ confidence: (0, import_types8.confidenceForExtracted)("structural"),
3308
+ evidence: { file: importerRelPath, line, snippet: snippet2 }
3309
+ };
3310
+ graph.addEdgeWithKey(edgeId, importerFileId, importeeFileId, edge);
3311
+ return 1;
3312
+ }
3313
+ async function addImports(graph, services) {
3314
+ const jsParser = makeJsParser();
3315
+ const pyParser = makePyParser();
3316
+ let edgesAdded = 0;
3317
+ for (const service of services) {
3318
+ const tsPaths = await loadTsPathConfig(service.dir);
3319
+ const files = await loadSourceFiles(service.dir);
3320
+ for (const file of files) {
3321
+ if (isTestPath(file.path)) continue;
3322
+ const relFile = toPosix2(import_node_path12.default.relative(service.dir, file.path));
3323
+ const importerFileId = (0, import_types8.fileId)(service.pkg.name, relFile);
3324
+ const isPython = import_node_path12.default.extname(file.path) === ".py";
3325
+ if (isPython) {
3326
+ let pyImports = [];
3327
+ try {
3328
+ const tree = parseSource(pyParser, file.content);
3329
+ collectPyImports(tree.rootNode, pyImports);
3330
+ } catch (err) {
3331
+ recordExtractionError("import extraction", file.path, err);
3332
+ continue;
3333
+ }
3334
+ for (const imp of pyImports) {
3335
+ const resolved = await resolvePyImport(imp, file.path, service.dir);
3336
+ if (!resolved) continue;
3337
+ edgesAdded += emitImportEdge(
3338
+ graph,
3339
+ service.pkg.name,
3340
+ importerFileId,
3341
+ relFile,
3342
+ resolved,
3343
+ imp.line,
3344
+ imp.snippet
3345
+ );
3346
+ }
3347
+ continue;
3348
+ }
3349
+ let jsImports = [];
3350
+ try {
3351
+ const tree = parseSource(jsParser, file.content);
3352
+ collectJsImports(tree.rootNode, jsImports);
3353
+ } catch (err) {
3354
+ recordExtractionError("import extraction", file.path, err);
3355
+ continue;
3356
+ }
3357
+ for (const imp of jsImports) {
3358
+ const resolved = await resolveJsImport(imp.specifier, import_node_path12.default.dirname(file.path), service.dir, tsPaths);
3359
+ if (!resolved) continue;
3360
+ edgesAdded += emitImportEdge(
3361
+ graph,
3362
+ service.pkg.name,
3363
+ importerFileId,
3364
+ relFile,
3365
+ resolved,
3366
+ imp.line,
3367
+ imp.snippet
3368
+ );
3369
+ }
3370
+ }
3371
+ }
3372
+ return { nodesAdded: 0, edgesAdded };
3373
+ }
3374
+
3375
+ // src/extract/databases/index.ts
3376
+ init_cjs_shims();
3377
+ var import_node_path20 = __toESM(require("path"), 1);
3378
+ var import_types9 = require("@neat.is/types");
3379
+
3055
3380
  // src/extract/databases/db-config-yaml.ts
3056
3381
  init_cjs_shims();
3057
- var import_node_path11 = __toESM(require("path"), 1);
3382
+ var import_node_path13 = __toESM(require("path"), 1);
3058
3383
  async function parse(serviceDir) {
3059
- const yamlPath = import_node_path11.default.join(serviceDir, "db-config.yaml");
3384
+ const yamlPath = import_node_path13.default.join(serviceDir, "db-config.yaml");
3060
3385
  if (!await exists(yamlPath)) return [];
3061
3386
  const raw = await readYaml(yamlPath);
3062
3387
  return [
@@ -3074,13 +3399,13 @@ var dbConfigYamlParser = { name: "db-config.yaml", parse };
3074
3399
 
3075
3400
  // src/extract/databases/dotenv.ts
3076
3401
  init_cjs_shims();
3077
- var import_node_fs12 = require("fs");
3078
- var import_node_path13 = __toESM(require("path"), 1);
3402
+ var import_node_fs13 = require("fs");
3403
+ var import_node_path15 = __toESM(require("path"), 1);
3079
3404
 
3080
3405
  // src/extract/databases/shared.ts
3081
3406
  init_cjs_shims();
3082
- var import_node_fs11 = require("fs");
3083
- var import_node_path12 = __toESM(require("path"), 1);
3407
+ var import_node_fs12 = require("fs");
3408
+ var import_node_path14 = __toESM(require("path"), 1);
3084
3409
  function schemeToEngine(scheme) {
3085
3410
  const s = scheme.toLowerCase().split("+")[0];
3086
3411
  switch (s) {
@@ -3119,14 +3444,14 @@ function parseConnectionString(url) {
3119
3444
  }
3120
3445
  async function readIfExists(filePath) {
3121
3446
  try {
3122
- return await import_node_fs11.promises.readFile(filePath, "utf8");
3447
+ return await import_node_fs12.promises.readFile(filePath, "utf8");
3123
3448
  } catch {
3124
3449
  return null;
3125
3450
  }
3126
3451
  }
3127
3452
  async function findFirst(serviceDir, candidates) {
3128
3453
  for (const rel of candidates) {
3129
- const abs = import_node_path12.default.join(serviceDir, rel);
3454
+ const abs = import_node_path14.default.join(serviceDir, rel);
3130
3455
  const content = await readIfExists(abs);
3131
3456
  if (content !== null) return abs;
3132
3457
  }
@@ -3177,15 +3502,15 @@ function parseDotenvLine(line) {
3177
3502
  return { key, value };
3178
3503
  }
3179
3504
  async function parse2(serviceDir) {
3180
- const entries = await import_node_fs12.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
3505
+ const entries = await import_node_fs13.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
3181
3506
  const configs = [];
3182
3507
  const seen = /* @__PURE__ */ new Set();
3183
3508
  for (const entry2 of entries) {
3184
3509
  if (!entry2.isFile()) continue;
3185
3510
  const match = isConfigFile(entry2.name);
3186
3511
  if (!match.match || match.fileType !== "env") continue;
3187
- const filePath = import_node_path13.default.join(serviceDir, entry2.name);
3188
- const content = await import_node_fs12.promises.readFile(filePath, "utf8");
3512
+ const filePath = import_node_path15.default.join(serviceDir, entry2.name);
3513
+ const content = await import_node_fs13.promises.readFile(filePath, "utf8");
3189
3514
  for (const line of content.split("\n")) {
3190
3515
  const parsed = parseDotenvLine(line);
3191
3516
  if (!parsed) continue;
@@ -3204,9 +3529,9 @@ var dotenvParser = { name: ".env", parse: parse2 };
3204
3529
 
3205
3530
  // src/extract/databases/prisma.ts
3206
3531
  init_cjs_shims();
3207
- var import_node_path14 = __toESM(require("path"), 1);
3532
+ var import_node_path16 = __toESM(require("path"), 1);
3208
3533
  async function parse3(serviceDir) {
3209
- const schemaPath = import_node_path14.default.join(serviceDir, "prisma", "schema.prisma");
3534
+ const schemaPath = import_node_path16.default.join(serviceDir, "prisma", "schema.prisma");
3210
3535
  const content = await readIfExists(schemaPath);
3211
3536
  if (!content) return [];
3212
3537
  const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
@@ -3338,10 +3663,10 @@ var knexParser = { name: "knex", parse: parse5 };
3338
3663
 
3339
3664
  // src/extract/databases/ormconfig.ts
3340
3665
  init_cjs_shims();
3341
- var import_node_path15 = __toESM(require("path"), 1);
3666
+ var import_node_path17 = __toESM(require("path"), 1);
3342
3667
  async function parse6(serviceDir) {
3343
3668
  for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
3344
- const abs = import_node_path15.default.join(serviceDir, candidate);
3669
+ const abs = import_node_path17.default.join(serviceDir, candidate);
3345
3670
  if (!await exists(abs)) continue;
3346
3671
  const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
3347
3672
  const entries = Array.isArray(raw) ? raw : [raw];
@@ -3401,9 +3726,9 @@ var typeormParser = { name: "typeorm", parse: parse7 };
3401
3726
 
3402
3727
  // src/extract/databases/sequelize.ts
3403
3728
  init_cjs_shims();
3404
- var import_node_path16 = __toESM(require("path"), 1);
3729
+ var import_node_path18 = __toESM(require("path"), 1);
3405
3730
  async function parse8(serviceDir) {
3406
- const configPath = import_node_path16.default.join(serviceDir, "config", "config.json");
3731
+ const configPath = import_node_path18.default.join(serviceDir, "config", "config.json");
3407
3732
  if (!await exists(configPath)) return [];
3408
3733
  const raw = await readJson(configPath);
3409
3734
  const out = [];
@@ -3430,7 +3755,7 @@ var sequelizeParser = { name: "sequelize", parse: parse8 };
3430
3755
 
3431
3756
  // src/extract/databases/docker-compose.ts
3432
3757
  init_cjs_shims();
3433
- var import_node_path17 = __toESM(require("path"), 1);
3758
+ var import_node_path19 = __toESM(require("path"), 1);
3434
3759
  function portFromService(svc) {
3435
3760
  for (const raw of svc.ports ?? []) {
3436
3761
  const str = String(raw);
@@ -3457,7 +3782,7 @@ function databaseFromEnv(svc) {
3457
3782
  }
3458
3783
  async function parse9(serviceDir) {
3459
3784
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
3460
- const abs = import_node_path17.default.join(serviceDir, name);
3785
+ const abs = import_node_path19.default.join(serviceDir, name);
3461
3786
  if (!await exists(abs)) continue;
3462
3787
  const raw = await readYaml(abs);
3463
3788
  if (!raw?.services) return [];
@@ -3498,8 +3823,8 @@ function compatibleDriversFor(engine) {
3498
3823
  }
3499
3824
  function toDatabaseNode(config) {
3500
3825
  return {
3501
- id: (0, import_types8.databaseId)(config.host),
3502
- type: import_types8.NodeType.DatabaseNode,
3826
+ id: (0, import_types9.databaseId)(config.host),
3827
+ type: import_types9.NodeType.DatabaseNode,
3503
3828
  name: config.database || config.host,
3504
3829
  engine: config.engine,
3505
3830
  engineVersion: config.engineVersion,
@@ -3629,7 +3954,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
3629
3954
  discoveredVia: mergedDiscoveredVia
3630
3955
  });
3631
3956
  }
3632
- const relConfigFile = toPosix2(import_node_path18.default.relative(service.dir, config.sourceFile));
3957
+ const relConfigFile = toPosix2(import_node_path20.default.relative(service.dir, config.sourceFile));
3633
3958
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
3634
3959
  graph,
3635
3960
  service.pkg.name,
@@ -3638,14 +3963,14 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
3638
3963
  );
3639
3964
  nodesAdded += fn;
3640
3965
  edgesAdded += fe;
3641
- const evidenceFile = toPosix2(import_node_path18.default.relative(scanPath, config.sourceFile));
3966
+ const evidenceFile = toPosix2(import_node_path20.default.relative(scanPath, config.sourceFile));
3642
3967
  const edge = {
3643
- id: (0, import_types4.extractedEdgeId)(fileNodeId, dbNode.id, import_types8.EdgeType.CONNECTS_TO),
3968
+ id: (0, import_types4.extractedEdgeId)(fileNodeId, dbNode.id, import_types9.EdgeType.CONNECTS_TO),
3644
3969
  source: fileNodeId,
3645
3970
  target: dbNode.id,
3646
- type: import_types8.EdgeType.CONNECTS_TO,
3647
- provenance: import_types8.Provenance.EXTRACTED,
3648
- confidence: (0, import_types8.confidenceForExtracted)("structural"),
3971
+ type: import_types9.EdgeType.CONNECTS_TO,
3972
+ provenance: import_types9.Provenance.EXTRACTED,
3973
+ confidence: (0, import_types9.confidenceForExtracted)("structural"),
3649
3974
  evidence: { file: evidenceFile }
3650
3975
  };
3651
3976
  if (!graph.hasEdge(edge.id)) {
@@ -3672,15 +3997,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
3672
3997
 
3673
3998
  // src/extract/configs.ts
3674
3999
  init_cjs_shims();
3675
- var import_node_fs13 = require("fs");
3676
- var import_node_path19 = __toESM(require("path"), 1);
3677
- var import_types9 = require("@neat.is/types");
4000
+ var import_node_fs14 = require("fs");
4001
+ var import_node_path21 = __toESM(require("path"), 1);
4002
+ var import_types10 = require("@neat.is/types");
3678
4003
  async function walkConfigFiles(dir) {
3679
4004
  const out = [];
3680
4005
  async function walk(current) {
3681
- const entries = await import_node_fs13.promises.readdir(current, { withFileTypes: true });
4006
+ const entries = await import_node_fs14.promises.readdir(current, { withFileTypes: true });
3682
4007
  for (const entry2 of entries) {
3683
- const full = import_node_path19.default.join(current, entry2.name);
4008
+ const full = import_node_path21.default.join(current, entry2.name);
3684
4009
  if (entry2.isDirectory()) {
3685
4010
  if (IGNORED_DIRS.has(entry2.name)) continue;
3686
4011
  if (await isPythonVenvDir(full)) continue;
@@ -3699,19 +4024,19 @@ async function addConfigNodes(graph, services, scanPath) {
3699
4024
  for (const service of services) {
3700
4025
  const configFiles = await walkConfigFiles(service.dir);
3701
4026
  for (const file of configFiles) {
3702
- const relPath = import_node_path19.default.relative(scanPath, file);
4027
+ const relPath = import_node_path21.default.relative(scanPath, file);
3703
4028
  const node = {
3704
- id: (0, import_types9.configId)(relPath),
3705
- type: import_types9.NodeType.ConfigNode,
3706
- name: import_node_path19.default.basename(file),
4029
+ id: (0, import_types10.configId)(relPath),
4030
+ type: import_types10.NodeType.ConfigNode,
4031
+ name: import_node_path21.default.basename(file),
3707
4032
  path: relPath,
3708
- fileType: isConfigFile(import_node_path19.default.basename(file)).fileType
4033
+ fileType: isConfigFile(import_node_path21.default.basename(file)).fileType
3709
4034
  };
3710
4035
  if (!graph.hasNode(node.id)) {
3711
4036
  graph.addNode(node.id, node);
3712
4037
  nodesAdded++;
3713
4038
  }
3714
- const relToService = toPosix2(import_node_path19.default.relative(service.dir, file));
4039
+ const relToService = toPosix2(import_node_path21.default.relative(service.dir, file));
3715
4040
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
3716
4041
  graph,
3717
4042
  service.pkg.name,
@@ -3721,13 +4046,13 @@ async function addConfigNodes(graph, services, scanPath) {
3721
4046
  nodesAdded += fn;
3722
4047
  edgesAdded += fe;
3723
4048
  const edge = {
3724
- id: (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types9.EdgeType.CONFIGURED_BY),
4049
+ id: (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types10.EdgeType.CONFIGURED_BY),
3725
4050
  source: fileNodeId,
3726
4051
  target: node.id,
3727
- type: import_types9.EdgeType.CONFIGURED_BY,
3728
- provenance: import_types9.Provenance.EXTRACTED,
3729
- confidence: (0, import_types9.confidenceForExtracted)("structural"),
3730
- evidence: { file: relPath.split(import_node_path19.default.sep).join("/") }
4052
+ type: import_types10.EdgeType.CONFIGURED_BY,
4053
+ provenance: import_types10.Provenance.EXTRACTED,
4054
+ confidence: (0, import_types10.confidenceForExtracted)("structural"),
4055
+ evidence: { file: relPath.split(import_node_path21.default.sep).join("/") }
3731
4056
  };
3732
4057
  if (!graph.hasEdge(edge.id)) {
3733
4058
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -3740,15 +4065,15 @@ async function addConfigNodes(graph, services, scanPath) {
3740
4065
 
3741
4066
  // src/extract/calls/index.ts
3742
4067
  init_cjs_shims();
3743
- var import_types15 = require("@neat.is/types");
4068
+ var import_types16 = require("@neat.is/types");
3744
4069
 
3745
4070
  // src/extract/calls/http.ts
3746
4071
  init_cjs_shims();
3747
- var import_node_path20 = __toESM(require("path"), 1);
3748
- var import_tree_sitter = __toESM(require("tree-sitter"), 1);
3749
- var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
3750
- var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
3751
- var import_types10 = require("@neat.is/types");
4072
+ var import_node_path22 = __toESM(require("path"), 1);
4073
+ var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
4074
+ var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
4075
+ var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
4076
+ var import_types11 = require("@neat.is/types");
3752
4077
  var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
3753
4078
  var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
3754
4079
  function isInsideJsxExternalLink(node) {
@@ -3776,14 +4101,14 @@ function collectStringLiterals(node, out) {
3776
4101
  if (child) collectStringLiterals(child, out);
3777
4102
  }
3778
4103
  }
3779
- var PARSE_CHUNK = 16384;
3780
- function parseSource(parser, source) {
4104
+ var PARSE_CHUNK2 = 16384;
4105
+ function parseSource2(parser, source) {
3781
4106
  return parser.parse(
3782
- (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK)
4107
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
3783
4108
  );
3784
4109
  }
3785
4110
  function callsFromSource(source, parser, knownHosts) {
3786
- const tree = parseSource(parser, source);
4111
+ const tree = parseSource2(parser, source);
3787
4112
  const literals = [];
3788
4113
  collectStringLiterals(tree.rootNode, literals);
3789
4114
  const out = [];
@@ -3797,25 +4122,25 @@ function callsFromSource(source, parser, knownHosts) {
3797
4122
  }
3798
4123
  return out;
3799
4124
  }
3800
- function makeJsParser() {
3801
- const p = new import_tree_sitter.default();
3802
- p.setLanguage(import_tree_sitter_javascript.default);
4125
+ function makeJsParser2() {
4126
+ const p = new import_tree_sitter2.default();
4127
+ p.setLanguage(import_tree_sitter_javascript2.default);
3803
4128
  return p;
3804
4129
  }
3805
- function makePyParser() {
3806
- const p = new import_tree_sitter.default();
3807
- p.setLanguage(import_tree_sitter_python.default);
4130
+ function makePyParser2() {
4131
+ const p = new import_tree_sitter2.default();
4132
+ p.setLanguage(import_tree_sitter_python2.default);
3808
4133
  return p;
3809
4134
  }
3810
4135
  async function addHttpCallEdges(graph, services) {
3811
- const jsParser = makeJsParser();
3812
- const pyParser = makePyParser();
4136
+ const jsParser = makeJsParser2();
4137
+ const pyParser = makePyParser2();
3813
4138
  const knownHosts = /* @__PURE__ */ new Set();
3814
4139
  const hostToNodeId = /* @__PURE__ */ new Map();
3815
4140
  for (const service of services) {
3816
- knownHosts.add(import_node_path20.default.basename(service.dir));
4141
+ knownHosts.add(import_node_path22.default.basename(service.dir));
3817
4142
  knownHosts.add(service.pkg.name);
3818
- hostToNodeId.set(import_node_path20.default.basename(service.dir), service.node.id);
4143
+ hostToNodeId.set(import_node_path22.default.basename(service.dir), service.node.id);
3819
4144
  hostToNodeId.set(service.pkg.name, service.node.id);
3820
4145
  }
3821
4146
  let nodesAdded = 0;
@@ -3825,7 +4150,7 @@ async function addHttpCallEdges(graph, services) {
3825
4150
  const seen = /* @__PURE__ */ new Set();
3826
4151
  for (const file of files) {
3827
4152
  if (isTestPath(file.path)) continue;
3828
- const parser = import_node_path20.default.extname(file.path) === ".py" ? pyParser : jsParser;
4153
+ const parser = import_node_path22.default.extname(file.path) === ".py" ? pyParser : jsParser;
3829
4154
  let sites;
3830
4155
  try {
3831
4156
  sites = callsFromSource(file.content, parser, knownHosts);
@@ -3834,14 +4159,14 @@ async function addHttpCallEdges(graph, services) {
3834
4159
  continue;
3835
4160
  }
3836
4161
  if (sites.length === 0) continue;
3837
- const relFile = toPosix2(import_node_path20.default.relative(service.dir, file.path));
4162
+ const relFile = toPosix2(import_node_path22.default.relative(service.dir, file.path));
3838
4163
  for (const site of sites) {
3839
4164
  const targetId = hostToNodeId.get(site.host);
3840
4165
  if (!targetId || targetId === service.node.id) continue;
3841
4166
  const dedupKey = `${relFile}|${targetId}`;
3842
4167
  if (seen.has(dedupKey)) continue;
3843
4168
  seen.add(dedupKey);
3844
- const confidence = (0, import_types10.confidenceForExtracted)("hostname-shape-match");
4169
+ const confidence = (0, import_types11.confidenceForExtracted)("hostname-shape-match");
3845
4170
  const ev = {
3846
4171
  file: relFile,
3847
4172
  line: site.line,
@@ -3855,25 +4180,25 @@ async function addHttpCallEdges(graph, services) {
3855
4180
  );
3856
4181
  nodesAdded += n;
3857
4182
  edgesAdded += e;
3858
- if (!(0, import_types10.passesExtractedFloor)(confidence)) {
4183
+ if (!(0, import_types11.passesExtractedFloor)(confidence)) {
3859
4184
  noteExtractedDropped({
3860
4185
  source: fileNodeId,
3861
4186
  target: targetId,
3862
- type: import_types10.EdgeType.CALLS,
4187
+ type: import_types11.EdgeType.CALLS,
3863
4188
  confidence,
3864
4189
  confidenceKind: "hostname-shape-match",
3865
4190
  evidence: ev
3866
4191
  });
3867
4192
  continue;
3868
4193
  }
3869
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types10.EdgeType.CALLS);
4194
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types11.EdgeType.CALLS);
3870
4195
  if (!graph.hasEdge(edgeId)) {
3871
4196
  const edge = {
3872
4197
  id: edgeId,
3873
4198
  source: fileNodeId,
3874
4199
  target: targetId,
3875
- type: import_types10.EdgeType.CALLS,
3876
- provenance: import_types10.Provenance.EXTRACTED,
4200
+ type: import_types11.EdgeType.CALLS,
4201
+ provenance: import_types11.Provenance.EXTRACTED,
3877
4202
  confidence,
3878
4203
  evidence: ev
3879
4204
  };
@@ -3888,8 +4213,8 @@ async function addHttpCallEdges(graph, services) {
3888
4213
 
3889
4214
  // src/extract/calls/kafka.ts
3890
4215
  init_cjs_shims();
3891
- var import_node_path21 = __toESM(require("path"), 1);
3892
- var import_types11 = require("@neat.is/types");
4216
+ var import_node_path23 = __toESM(require("path"), 1);
4217
+ var import_types12 = require("@neat.is/types");
3893
4218
  var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
3894
4219
  var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
3895
4220
  function findAll(re, text) {
@@ -3910,7 +4235,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
3910
4235
  seen.add(key);
3911
4236
  const line = lineOf(file.content, topic);
3912
4237
  out.push({
3913
- infraId: (0, import_types11.infraId)("kafka-topic", topic),
4238
+ infraId: (0, import_types12.infraId)("kafka-topic", topic),
3914
4239
  name: topic,
3915
4240
  kind: "kafka-topic",
3916
4241
  edgeType,
@@ -3919,7 +4244,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
3919
4244
  // tier (ADR-066).
3920
4245
  confidenceKind: "verified-call-site",
3921
4246
  evidence: {
3922
- file: import_node_path21.default.relative(serviceDir, file.path),
4247
+ file: import_node_path23.default.relative(serviceDir, file.path),
3923
4248
  line,
3924
4249
  snippet: snippet(file.content, line)
3925
4250
  }
@@ -3932,8 +4257,8 @@ function kafkaEndpointsFromFile(file, serviceDir) {
3932
4257
 
3933
4258
  // src/extract/calls/redis.ts
3934
4259
  init_cjs_shims();
3935
- var import_node_path22 = __toESM(require("path"), 1);
3936
- var import_types12 = require("@neat.is/types");
4260
+ var import_node_path24 = __toESM(require("path"), 1);
4261
+ var import_types13 = require("@neat.is/types");
3937
4262
  var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
3938
4263
  function redisEndpointsFromFile(file, serviceDir) {
3939
4264
  const out = [];
@@ -3946,7 +4271,7 @@ function redisEndpointsFromFile(file, serviceDir) {
3946
4271
  seen.add(host);
3947
4272
  const line = lineOf(file.content, host);
3948
4273
  out.push({
3949
- infraId: (0, import_types12.infraId)("redis", host),
4274
+ infraId: (0, import_types13.infraId)("redis", host),
3950
4275
  name: host,
3951
4276
  kind: "redis",
3952
4277
  edgeType: "CALLS",
@@ -3955,7 +4280,7 @@ function redisEndpointsFromFile(file, serviceDir) {
3955
4280
  // support tier (ADR-066).
3956
4281
  confidenceKind: "url-with-structural-support",
3957
4282
  evidence: {
3958
- file: import_node_path22.default.relative(serviceDir, file.path),
4283
+ file: import_node_path24.default.relative(serviceDir, file.path),
3959
4284
  line,
3960
4285
  snippet: snippet(file.content, line)
3961
4286
  }
@@ -3966,8 +4291,8 @@ function redisEndpointsFromFile(file, serviceDir) {
3966
4291
 
3967
4292
  // src/extract/calls/aws.ts
3968
4293
  init_cjs_shims();
3969
- var import_node_path23 = __toESM(require("path"), 1);
3970
- var import_types13 = require("@neat.is/types");
4294
+ var import_node_path25 = __toESM(require("path"), 1);
4295
+ var import_types14 = require("@neat.is/types");
3971
4296
  var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
3972
4297
  var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
3973
4298
  function hasMarker(text, markers) {
@@ -3991,7 +4316,7 @@ function awsEndpointsFromFile(file, serviceDir) {
3991
4316
  seen.add(key);
3992
4317
  const line = lineOf(file.content, name);
3993
4318
  out.push({
3994
- infraId: (0, import_types13.infraId)(kind, name),
4319
+ infraId: (0, import_types14.infraId)(kind, name),
3995
4320
  name,
3996
4321
  kind,
3997
4322
  edgeType: "CALLS",
@@ -4000,7 +4325,7 @@ function awsEndpointsFromFile(file, serviceDir) {
4000
4325
  // (ADR-066).
4001
4326
  confidenceKind: "verified-call-site",
4002
4327
  evidence: {
4003
- file: import_node_path23.default.relative(serviceDir, file.path),
4328
+ file: import_node_path25.default.relative(serviceDir, file.path),
4004
4329
  line,
4005
4330
  snippet: snippet(file.content, line)
4006
4331
  }
@@ -4025,8 +4350,8 @@ function awsEndpointsFromFile(file, serviceDir) {
4025
4350
 
4026
4351
  // src/extract/calls/grpc.ts
4027
4352
  init_cjs_shims();
4028
- var import_node_path24 = __toESM(require("path"), 1);
4029
- var import_types14 = require("@neat.is/types");
4353
+ var import_node_path26 = __toESM(require("path"), 1);
4354
+ var import_types15 = require("@neat.is/types");
4030
4355
  var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
4031
4356
  var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
4032
4357
  var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
@@ -4075,7 +4400,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
4075
4400
  const { kind } = classified;
4076
4401
  const line = lineOf(file.content, m[0]);
4077
4402
  out.push({
4078
- infraId: (0, import_types14.infraId)(kind, name),
4403
+ infraId: (0, import_types15.infraId)(kind, name),
4079
4404
  name,
4080
4405
  kind,
4081
4406
  edgeType: "CALLS",
@@ -4084,7 +4409,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
4084
4409
  // tier (ADR-066).
4085
4410
  confidenceKind: "verified-call-site",
4086
4411
  evidence: {
4087
- file: import_node_path24.default.relative(serviceDir, file.path),
4412
+ file: import_node_path26.default.relative(serviceDir, file.path),
4088
4413
  line,
4089
4414
  snippet: snippet(file.content, line)
4090
4415
  }
@@ -4097,11 +4422,11 @@ function grpcEndpointsFromFile(file, serviceDir) {
4097
4422
  function edgeTypeFromEndpoint(ep) {
4098
4423
  switch (ep.edgeType) {
4099
4424
  case "PUBLISHES_TO":
4100
- return import_types15.EdgeType.PUBLISHES_TO;
4425
+ return import_types16.EdgeType.PUBLISHES_TO;
4101
4426
  case "CONSUMES_FROM":
4102
- return import_types15.EdgeType.CONSUMES_FROM;
4427
+ return import_types16.EdgeType.CONSUMES_FROM;
4103
4428
  default:
4104
- return import_types15.EdgeType.CALLS;
4429
+ return import_types16.EdgeType.CALLS;
4105
4430
  }
4106
4431
  }
4107
4432
  function isAwsKind(kind) {
@@ -4128,7 +4453,7 @@ async function addExternalEndpointEdges(graph, services) {
4128
4453
  if (!graph.hasNode(ep.infraId)) {
4129
4454
  const node = {
4130
4455
  id: ep.infraId,
4131
- type: import_types15.NodeType.InfraNode,
4456
+ type: import_types16.NodeType.InfraNode,
4132
4457
  name: ep.name,
4133
4458
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
4134
4459
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -4140,7 +4465,7 @@ async function addExternalEndpointEdges(graph, services) {
4140
4465
  nodesAdded++;
4141
4466
  }
4142
4467
  const edgeType = edgeTypeFromEndpoint(ep);
4143
- const confidence = (0, import_types15.confidenceForExtracted)(ep.confidenceKind);
4468
+ const confidence = (0, import_types16.confidenceForExtracted)(ep.confidenceKind);
4144
4469
  const relFile = toPosix2(ep.evidence.file);
4145
4470
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
4146
4471
  graph,
@@ -4150,7 +4475,7 @@ async function addExternalEndpointEdges(graph, services) {
4150
4475
  );
4151
4476
  nodesAdded += n;
4152
4477
  edgesAdded += e;
4153
- if (!(0, import_types15.passesExtractedFloor)(confidence)) {
4478
+ if (!(0, import_types16.passesExtractedFloor)(confidence)) {
4154
4479
  noteExtractedDropped({
4155
4480
  source: fileNodeId,
4156
4481
  target: ep.infraId,
@@ -4170,7 +4495,7 @@ async function addExternalEndpointEdges(graph, services) {
4170
4495
  source: fileNodeId,
4171
4496
  target: ep.infraId,
4172
4497
  type: edgeType,
4173
- provenance: import_types15.Provenance.EXTRACTED,
4498
+ provenance: import_types16.Provenance.EXTRACTED,
4174
4499
  confidence,
4175
4500
  evidence: ep.evidence
4176
4501
  };
@@ -4195,16 +4520,16 @@ init_cjs_shims();
4195
4520
 
4196
4521
  // src/extract/infra/docker-compose.ts
4197
4522
  init_cjs_shims();
4198
- var import_node_path25 = __toESM(require("path"), 1);
4199
- var import_types17 = require("@neat.is/types");
4523
+ var import_node_path27 = __toESM(require("path"), 1);
4524
+ var import_types18 = require("@neat.is/types");
4200
4525
 
4201
4526
  // src/extract/infra/shared.ts
4202
4527
  init_cjs_shims();
4203
- var import_types16 = require("@neat.is/types");
4528
+ var import_types17 = require("@neat.is/types");
4204
4529
  function makeInfraNode(kind, name, provider = "self", extras) {
4205
4530
  return {
4206
- id: (0, import_types16.infraId)(kind, name),
4207
- type: import_types16.NodeType.InfraNode,
4531
+ id: (0, import_types17.infraId)(kind, name),
4532
+ type: import_types17.NodeType.InfraNode,
4208
4533
  name,
4209
4534
  provider,
4210
4535
  kind,
@@ -4233,7 +4558,7 @@ function dependsOnList(value) {
4233
4558
  }
4234
4559
  function serviceNameToServiceNode(name, services) {
4235
4560
  for (const s of services) {
4236
- if (s.node.name === name || import_node_path25.default.basename(s.dir) === name) return s.node.id;
4561
+ if (s.node.name === name || import_node_path27.default.basename(s.dir) === name) return s.node.id;
4237
4562
  }
4238
4563
  return null;
4239
4564
  }
@@ -4242,7 +4567,7 @@ async function addComposeInfra(graph, scanPath, services) {
4242
4567
  let edgesAdded = 0;
4243
4568
  let composePath = null;
4244
4569
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
4245
- const abs = import_node_path25.default.join(scanPath, name);
4570
+ const abs = import_node_path27.default.join(scanPath, name);
4246
4571
  if (await exists(abs)) {
4247
4572
  composePath = abs;
4248
4573
  break;
@@ -4255,13 +4580,13 @@ async function addComposeInfra(graph, scanPath, services) {
4255
4580
  } catch (err) {
4256
4581
  recordExtractionError(
4257
4582
  "infra docker-compose",
4258
- import_node_path25.default.relative(scanPath, composePath),
4583
+ import_node_path27.default.relative(scanPath, composePath),
4259
4584
  err
4260
4585
  );
4261
4586
  return { nodesAdded, edgesAdded };
4262
4587
  }
4263
4588
  if (!compose?.services) return { nodesAdded, edgesAdded };
4264
- const evidenceFile = import_node_path25.default.relative(scanPath, composePath).split(import_node_path25.default.sep).join("/");
4589
+ const evidenceFile = import_node_path27.default.relative(scanPath, composePath).split(import_node_path27.default.sep).join("/");
4265
4590
  const composeNameToNodeId = /* @__PURE__ */ new Map();
4266
4591
  for (const [composeName, svc] of Object.entries(compose.services)) {
4267
4592
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -4283,15 +4608,15 @@ async function addComposeInfra(graph, scanPath, services) {
4283
4608
  for (const dep of dependsOnList(svc.depends_on)) {
4284
4609
  const targetId = composeNameToNodeId.get(dep);
4285
4610
  if (!targetId) continue;
4286
- const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types17.EdgeType.DEPENDS_ON);
4611
+ const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types18.EdgeType.DEPENDS_ON);
4287
4612
  if (graph.hasEdge(edgeId)) continue;
4288
4613
  const edge = {
4289
4614
  id: edgeId,
4290
4615
  source: sourceId,
4291
4616
  target: targetId,
4292
- type: import_types17.EdgeType.DEPENDS_ON,
4293
- provenance: import_types17.Provenance.EXTRACTED,
4294
- confidence: (0, import_types17.confidenceForExtracted)("structural"),
4617
+ type: import_types18.EdgeType.DEPENDS_ON,
4618
+ provenance: import_types18.Provenance.EXTRACTED,
4619
+ confidence: (0, import_types18.confidenceForExtracted)("structural"),
4295
4620
  evidence: { file: evidenceFile }
4296
4621
  };
4297
4622
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -4303,9 +4628,9 @@ async function addComposeInfra(graph, scanPath, services) {
4303
4628
 
4304
4629
  // src/extract/infra/dockerfile.ts
4305
4630
  init_cjs_shims();
4306
- var import_node_path26 = __toESM(require("path"), 1);
4307
- var import_node_fs14 = require("fs");
4308
- var import_types18 = require("@neat.is/types");
4631
+ var import_node_path28 = __toESM(require("path"), 1);
4632
+ var import_node_fs15 = require("fs");
4633
+ var import_types19 = require("@neat.is/types");
4309
4634
  function runtimeImage(content) {
4310
4635
  const lines = content.split("\n");
4311
4636
  let last = null;
@@ -4324,15 +4649,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4324
4649
  let nodesAdded = 0;
4325
4650
  let edgesAdded = 0;
4326
4651
  for (const service of services) {
4327
- const dockerfilePath = import_node_path26.default.join(service.dir, "Dockerfile");
4652
+ const dockerfilePath = import_node_path28.default.join(service.dir, "Dockerfile");
4328
4653
  if (!await exists(dockerfilePath)) continue;
4329
4654
  let content;
4330
4655
  try {
4331
- content = await import_node_fs14.promises.readFile(dockerfilePath, "utf8");
4656
+ content = await import_node_fs15.promises.readFile(dockerfilePath, "utf8");
4332
4657
  } catch (err) {
4333
4658
  recordExtractionError(
4334
4659
  "infra dockerfile",
4335
- import_node_path26.default.relative(scanPath, dockerfilePath),
4660
+ import_node_path28.default.relative(scanPath, dockerfilePath),
4336
4661
  err
4337
4662
  );
4338
4663
  continue;
@@ -4344,7 +4669,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4344
4669
  graph.addNode(node.id, node);
4345
4670
  nodesAdded++;
4346
4671
  }
4347
- const relDockerfile = toPosix2(import_node_path26.default.relative(service.dir, dockerfilePath));
4672
+ const relDockerfile = toPosix2(import_node_path28.default.relative(service.dir, dockerfilePath));
4348
4673
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
4349
4674
  graph,
4350
4675
  service.pkg.name,
@@ -4353,17 +4678,17 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4353
4678
  );
4354
4679
  nodesAdded += fn;
4355
4680
  edgesAdded += fe;
4356
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types18.EdgeType.RUNS_ON);
4681
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types19.EdgeType.RUNS_ON);
4357
4682
  if (!graph.hasEdge(edgeId)) {
4358
4683
  const edge = {
4359
4684
  id: edgeId,
4360
4685
  source: fileNodeId,
4361
4686
  target: node.id,
4362
- type: import_types18.EdgeType.RUNS_ON,
4363
- provenance: import_types18.Provenance.EXTRACTED,
4364
- confidence: (0, import_types18.confidenceForExtracted)("structural"),
4687
+ type: import_types19.EdgeType.RUNS_ON,
4688
+ provenance: import_types19.Provenance.EXTRACTED,
4689
+ confidence: (0, import_types19.confidenceForExtracted)("structural"),
4365
4690
  evidence: {
4366
- file: toPosix2(import_node_path26.default.relative(scanPath, dockerfilePath))
4691
+ file: toPosix2(import_node_path28.default.relative(scanPath, dockerfilePath))
4367
4692
  }
4368
4693
  };
4369
4694
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -4375,21 +4700,21 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4375
4700
 
4376
4701
  // src/extract/infra/terraform.ts
4377
4702
  init_cjs_shims();
4378
- var import_node_fs15 = require("fs");
4379
- var import_node_path27 = __toESM(require("path"), 1);
4703
+ var import_node_fs16 = require("fs");
4704
+ var import_node_path29 = __toESM(require("path"), 1);
4380
4705
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
4381
4706
  async function walkTfFiles(start, depth = 0, max = 5) {
4382
4707
  if (depth > max) return [];
4383
4708
  const out = [];
4384
- const entries = await import_node_fs15.promises.readdir(start, { withFileTypes: true }).catch(() => []);
4709
+ const entries = await import_node_fs16.promises.readdir(start, { withFileTypes: true }).catch(() => []);
4385
4710
  for (const entry2 of entries) {
4386
4711
  if (entry2.isDirectory()) {
4387
4712
  if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
4388
- const child = import_node_path27.default.join(start, entry2.name);
4713
+ const child = import_node_path29.default.join(start, entry2.name);
4389
4714
  if (await isPythonVenvDir(child)) continue;
4390
4715
  out.push(...await walkTfFiles(child, depth + 1, max));
4391
4716
  } else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
4392
- out.push(import_node_path27.default.join(start, entry2.name));
4717
+ out.push(import_node_path29.default.join(start, entry2.name));
4393
4718
  }
4394
4719
  }
4395
4720
  return out;
@@ -4398,7 +4723,7 @@ async function addTerraformResources(graph, scanPath) {
4398
4723
  let nodesAdded = 0;
4399
4724
  const files = await walkTfFiles(scanPath);
4400
4725
  for (const file of files) {
4401
- const content = await import_node_fs15.promises.readFile(file, "utf8");
4726
+ const content = await import_node_fs16.promises.readFile(file, "utf8");
4402
4727
  RESOURCE_RE.lastIndex = 0;
4403
4728
  let m;
4404
4729
  while ((m = RESOURCE_RE.exec(content)) !== null) {
@@ -4416,8 +4741,8 @@ async function addTerraformResources(graph, scanPath) {
4416
4741
 
4417
4742
  // src/extract/infra/k8s.ts
4418
4743
  init_cjs_shims();
4419
- var import_node_fs16 = require("fs");
4420
- var import_node_path28 = __toESM(require("path"), 1);
4744
+ var import_node_fs17 = require("fs");
4745
+ var import_node_path30 = __toESM(require("path"), 1);
4421
4746
  var import_yaml3 = require("yaml");
4422
4747
  var K8S_KIND_TO_INFRA_KIND = {
4423
4748
  Service: "k8s-service",
@@ -4431,15 +4756,15 @@ var K8S_KIND_TO_INFRA_KIND = {
4431
4756
  async function walkYamlFiles2(start, depth = 0, max = 5) {
4432
4757
  if (depth > max) return [];
4433
4758
  const out = [];
4434
- const entries = await import_node_fs16.promises.readdir(start, { withFileTypes: true }).catch(() => []);
4759
+ const entries = await import_node_fs17.promises.readdir(start, { withFileTypes: true }).catch(() => []);
4435
4760
  for (const entry2 of entries) {
4436
4761
  if (entry2.isDirectory()) {
4437
4762
  if (IGNORED_DIRS.has(entry2.name)) continue;
4438
- const child = import_node_path28.default.join(start, entry2.name);
4763
+ const child = import_node_path30.default.join(start, entry2.name);
4439
4764
  if (await isPythonVenvDir(child)) continue;
4440
4765
  out.push(...await walkYamlFiles2(child, depth + 1, max));
4441
- } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path28.default.extname(entry2.name))) {
4442
- out.push(import_node_path28.default.join(start, entry2.name));
4766
+ } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path30.default.extname(entry2.name))) {
4767
+ out.push(import_node_path30.default.join(start, entry2.name));
4443
4768
  }
4444
4769
  }
4445
4770
  return out;
@@ -4448,7 +4773,7 @@ async function addK8sResources(graph, scanPath) {
4448
4773
  let nodesAdded = 0;
4449
4774
  const files = await walkYamlFiles2(scanPath);
4450
4775
  for (const file of files) {
4451
- const content = await import_node_fs16.promises.readFile(file, "utf8");
4776
+ const content = await import_node_fs17.promises.readFile(file, "utf8");
4452
4777
  let docs;
4453
4778
  try {
4454
4779
  docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
@@ -4483,17 +4808,17 @@ async function addInfra(graph, scanPath, services) {
4483
4808
  }
4484
4809
 
4485
4810
  // src/extract/index.ts
4486
- var import_node_path30 = __toESM(require("path"), 1);
4811
+ var import_node_path32 = __toESM(require("path"), 1);
4487
4812
 
4488
4813
  // src/extract/retire.ts
4489
4814
  init_cjs_shims();
4490
- var import_node_fs17 = require("fs");
4491
- var import_node_path29 = __toESM(require("path"), 1);
4492
- var import_types19 = require("@neat.is/types");
4815
+ var import_node_fs18 = require("fs");
4816
+ var import_node_path31 = __toESM(require("path"), 1);
4817
+ var import_types20 = require("@neat.is/types");
4493
4818
  function dropOrphanedFileNodes(graph) {
4494
4819
  const orphans = [];
4495
4820
  graph.forEachNode((id, attrs) => {
4496
- if (attrs.type !== import_types19.NodeType.FileNode) return;
4821
+ if (attrs.type !== import_types20.NodeType.FileNode) return;
4497
4822
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
4498
4823
  orphans.push(id);
4499
4824
  }
@@ -4506,14 +4831,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
4506
4831
  const bases = [scanPath, ...serviceDirs];
4507
4832
  graph.forEachEdge((id, attrs) => {
4508
4833
  const edge = attrs;
4509
- if (edge.provenance !== import_types19.Provenance.EXTRACTED) return;
4834
+ if (edge.provenance !== import_types20.Provenance.EXTRACTED) return;
4510
4835
  const evidenceFile = edge.evidence?.file;
4511
4836
  if (!evidenceFile) return;
4512
- if (import_node_path29.default.isAbsolute(evidenceFile)) {
4513
- if (!(0, import_node_fs17.existsSync)(evidenceFile)) toDrop.push(id);
4837
+ if (import_node_path31.default.isAbsolute(evidenceFile)) {
4838
+ if (!(0, import_node_fs18.existsSync)(evidenceFile)) toDrop.push(id);
4514
4839
  return;
4515
4840
  }
4516
- const found = bases.some((base) => (0, import_node_fs17.existsSync)(import_node_path29.default.join(base, evidenceFile)));
4841
+ const found = bases.some((base) => (0, import_node_fs18.existsSync)(import_node_path31.default.join(base, evidenceFile)));
4517
4842
  if (!found) toDrop.push(id);
4518
4843
  });
4519
4844
  for (const id of toDrop) graph.dropEdge(id);
@@ -4528,6 +4853,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
4528
4853
  const services = await discoverServices(scanPath);
4529
4854
  const phase1Nodes = addServiceNodes(graph, services);
4530
4855
  await addServiceAliases(graph, scanPath, services);
4856
+ const fileEnum = await addFiles(graph, services);
4857
+ const importGraph = await addImports(graph, services);
4531
4858
  const phase2 = await addDatabasesAndCompat(graph, services, scanPath);
4532
4859
  const phase3 = await addConfigNodes(graph, services, scanPath);
4533
4860
  const phase4 = await addCallEdges(graph, services);
@@ -4551,7 +4878,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
4551
4878
  }
4552
4879
  const droppedEntries = drainDroppedExtracted();
4553
4880
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
4554
- const rejectedPath = import_node_path30.default.join(import_node_path30.default.dirname(opts.errorsPath), "rejected.ndjson");
4881
+ const rejectedPath = import_node_path32.default.join(import_node_path32.default.dirname(opts.errorsPath), "rejected.ndjson");
4555
4882
  try {
4556
4883
  await writeRejectedExtracted(droppedEntries, rejectedPath);
4557
4884
  } catch (err) {
@@ -4561,8 +4888,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
4561
4888
  }
4562
4889
  }
4563
4890
  const result = {
4564
- nodesAdded: phase1Nodes + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
4565
- edgesAdded: phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
4891
+ nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
4892
+ edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
4566
4893
  frontiersPromoted,
4567
4894
  extractionErrors: errorEntries.length,
4568
4895
  errorEntries,
@@ -4585,9 +4912,9 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
4585
4912
 
4586
4913
  // src/persist.ts
4587
4914
  init_cjs_shims();
4588
- var import_node_fs18 = require("fs");
4589
- var import_node_path31 = __toESM(require("path"), 1);
4590
- var import_types20 = require("@neat.is/types");
4915
+ var import_node_fs19 = require("fs");
4916
+ var import_node_path33 = __toESM(require("path"), 1);
4917
+ var import_types21 = require("@neat.is/types");
4591
4918
  var SCHEMA_VERSION = 4;
4592
4919
  function migrateV1ToV2(payload) {
4593
4920
  const nodes = payload.graph.nodes;
@@ -4609,12 +4936,12 @@ function migrateV2ToV3(payload) {
4609
4936
  for (const edge of edges) {
4610
4937
  const attrs = edge.attributes;
4611
4938
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
4612
- attrs.provenance = import_types20.Provenance.OBSERVED;
4939
+ attrs.provenance = import_types21.Provenance.OBSERVED;
4613
4940
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
4614
4941
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
4615
4942
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
4616
4943
  if (type && source && target) {
4617
- const newId = (0, import_types20.observedEdgeId)(source, target, type);
4944
+ const newId = (0, import_types21.observedEdgeId)(source, target, type);
4618
4945
  attrs.id = newId;
4619
4946
  if (edge.key) edge.key = newId;
4620
4947
  }
@@ -4623,7 +4950,7 @@ function migrateV2ToV3(payload) {
4623
4950
  return { ...payload, schemaVersion: 3 };
4624
4951
  }
4625
4952
  async function ensureDir(filePath) {
4626
- await import_node_fs18.promises.mkdir(import_node_path31.default.dirname(filePath), { recursive: true });
4953
+ await import_node_fs19.promises.mkdir(import_node_path33.default.dirname(filePath), { recursive: true });
4627
4954
  }
4628
4955
  async function saveGraphToDisk(graph, outPath) {
4629
4956
  await ensureDir(outPath);
@@ -4633,13 +4960,13 @@ async function saveGraphToDisk(graph, outPath) {
4633
4960
  graph: graph.export()
4634
4961
  };
4635
4962
  const tmp = `${outPath}.tmp`;
4636
- await import_node_fs18.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
4637
- await import_node_fs18.promises.rename(tmp, outPath);
4963
+ await import_node_fs19.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
4964
+ await import_node_fs19.promises.rename(tmp, outPath);
4638
4965
  }
4639
4966
  async function loadGraphFromDisk(graph, outPath) {
4640
4967
  let raw;
4641
4968
  try {
4642
- raw = await import_node_fs18.promises.readFile(outPath, "utf8");
4969
+ raw = await import_node_fs19.promises.readFile(outPath, "utf8");
4643
4970
  } catch (err) {
4644
4971
  if (err.code === "ENOENT") return;
4645
4972
  throw err;
@@ -4698,23 +5025,23 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
4698
5025
 
4699
5026
  // src/projects.ts
4700
5027
  init_cjs_shims();
4701
- var import_node_path32 = __toESM(require("path"), 1);
5028
+ var import_node_path34 = __toESM(require("path"), 1);
4702
5029
  function pathsForProject(project, baseDir) {
4703
5030
  if (project === DEFAULT_PROJECT) {
4704
5031
  return {
4705
- snapshotPath: import_node_path32.default.join(baseDir, "graph.json"),
4706
- errorsPath: import_node_path32.default.join(baseDir, "errors.ndjson"),
4707
- staleEventsPath: import_node_path32.default.join(baseDir, "stale-events.ndjson"),
4708
- embeddingsCachePath: import_node_path32.default.join(baseDir, "embeddings.json"),
4709
- policyViolationsPath: import_node_path32.default.join(baseDir, "policy-violations.ndjson")
5032
+ snapshotPath: import_node_path34.default.join(baseDir, "graph.json"),
5033
+ errorsPath: import_node_path34.default.join(baseDir, "errors.ndjson"),
5034
+ staleEventsPath: import_node_path34.default.join(baseDir, "stale-events.ndjson"),
5035
+ embeddingsCachePath: import_node_path34.default.join(baseDir, "embeddings.json"),
5036
+ policyViolationsPath: import_node_path34.default.join(baseDir, "policy-violations.ndjson")
4710
5037
  };
4711
5038
  }
4712
5039
  return {
4713
- snapshotPath: import_node_path32.default.join(baseDir, `${project}.json`),
4714
- errorsPath: import_node_path32.default.join(baseDir, `errors.${project}.ndjson`),
4715
- staleEventsPath: import_node_path32.default.join(baseDir, `stale-events.${project}.ndjson`),
4716
- embeddingsCachePath: import_node_path32.default.join(baseDir, `embeddings.${project}.json`),
4717
- policyViolationsPath: import_node_path32.default.join(baseDir, `policy-violations.${project}.ndjson`)
5040
+ snapshotPath: import_node_path34.default.join(baseDir, `${project}.json`),
5041
+ errorsPath: import_node_path34.default.join(baseDir, `errors.${project}.ndjson`),
5042
+ staleEventsPath: import_node_path34.default.join(baseDir, `stale-events.${project}.ndjson`),
5043
+ embeddingsCachePath: import_node_path34.default.join(baseDir, `embeddings.${project}.json`),
5044
+ policyViolationsPath: import_node_path34.default.join(baseDir, `policy-violations.${project}.ndjson`)
4718
5045
  };
4719
5046
  }
4720
5047
  var Projects = class {
@@ -4752,19 +5079,19 @@ var Projects = class {
4752
5079
  init_cjs_shims();
4753
5080
  var import_fastify = __toESM(require("fastify"), 1);
4754
5081
  var import_cors = __toESM(require("@fastify/cors"), 1);
4755
- var import_types23 = require("@neat.is/types");
5082
+ var import_types24 = require("@neat.is/types");
4756
5083
 
4757
5084
  // src/extend/index.ts
4758
5085
  init_cjs_shims();
4759
- var import_node_fs20 = require("fs");
4760
- var import_node_path34 = __toESM(require("path"), 1);
5086
+ var import_node_fs21 = require("fs");
5087
+ var import_node_path36 = __toESM(require("path"), 1);
4761
5088
  var import_node_os2 = __toESM(require("os"), 1);
4762
5089
  var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
4763
5090
 
4764
5091
  // src/installers/package-manager.ts
4765
5092
  init_cjs_shims();
4766
- var import_node_fs19 = require("fs");
4767
- var import_node_path33 = __toESM(require("path"), 1);
5093
+ var import_node_fs20 = require("fs");
5094
+ var import_node_path35 = __toESM(require("path"), 1);
4768
5095
  var import_node_child_process = require("child_process");
4769
5096
  var LOCKFILE_PRIORITY = [
4770
5097
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -4779,29 +5106,29 @@ var LOCKFILE_PRIORITY = [
4779
5106
  var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
4780
5107
  async function exists2(p) {
4781
5108
  try {
4782
- await import_node_fs19.promises.access(p);
5109
+ await import_node_fs20.promises.access(p);
4783
5110
  return true;
4784
5111
  } catch {
4785
5112
  return false;
4786
5113
  }
4787
5114
  }
4788
5115
  async function detectPackageManager(serviceDir) {
4789
- let dir = import_node_path33.default.resolve(serviceDir);
5116
+ let dir = import_node_path35.default.resolve(serviceDir);
4790
5117
  const stops = /* @__PURE__ */ new Set();
4791
5118
  for (let i = 0; i < 64; i++) {
4792
5119
  if (stops.has(dir)) break;
4793
5120
  stops.add(dir);
4794
5121
  for (const candidate of LOCKFILE_PRIORITY) {
4795
- const lockPath = import_node_path33.default.join(dir, candidate.lockfile);
5122
+ const lockPath = import_node_path35.default.join(dir, candidate.lockfile);
4796
5123
  if (await exists2(lockPath)) {
4797
5124
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
4798
5125
  }
4799
5126
  }
4800
- const parent = import_node_path33.default.dirname(dir);
5127
+ const parent = import_node_path35.default.dirname(dir);
4801
5128
  if (parent === dir) break;
4802
5129
  dir = parent;
4803
5130
  }
4804
- return { pm: "npm", cwd: import_node_path33.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
5131
+ return { pm: "npm", cwd: import_node_path35.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
4805
5132
  }
4806
5133
  async function runPackageManagerInstall(cmd) {
4807
5134
  return new Promise((resolve) => {
@@ -4841,32 +5168,32 @@ ${err.message}`
4841
5168
  }
4842
5169
 
4843
5170
  // src/extend/index.ts
4844
- async function fileExists(p) {
5171
+ async function fileExists2(p) {
4845
5172
  try {
4846
- await import_node_fs20.promises.access(p);
5173
+ await import_node_fs21.promises.access(p);
4847
5174
  return true;
4848
5175
  } catch {
4849
5176
  return false;
4850
5177
  }
4851
5178
  }
4852
5179
  async function readPackageJson(scanPath) {
4853
- const pkgPath = import_node_path34.default.join(scanPath, "package.json");
4854
- const raw = await import_node_fs20.promises.readFile(pkgPath, "utf8");
5180
+ const pkgPath = import_node_path36.default.join(scanPath, "package.json");
5181
+ const raw = await import_node_fs21.promises.readFile(pkgPath, "utf8");
4855
5182
  return JSON.parse(raw);
4856
5183
  }
4857
5184
  async function findHookFiles(scanPath) {
4858
- const entries = await import_node_fs20.promises.readdir(scanPath);
5185
+ const entries = await import_node_fs21.promises.readdir(scanPath);
4859
5186
  return entries.filter(
4860
5187
  (e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
4861
5188
  ).sort();
4862
5189
  }
4863
5190
  function extendLogPath() {
4864
- return process.env.NEAT_EXTEND_LOG ?? import_node_path34.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
5191
+ return process.env.NEAT_EXTEND_LOG ?? import_node_path36.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
4865
5192
  }
4866
5193
  async function appendExtendLog(entry2) {
4867
5194
  const logPath = extendLogPath();
4868
- await import_node_fs20.promises.mkdir(import_node_path34.default.dirname(logPath), { recursive: true });
4869
- await import_node_fs20.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
5195
+ await import_node_fs21.promises.mkdir(import_node_path36.default.dirname(logPath), { recursive: true });
5196
+ await import_node_fs21.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
4870
5197
  }
4871
5198
  function splicedContent(fileContent, snippet2) {
4872
5199
  if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
@@ -4924,7 +5251,7 @@ function lookupInstrumentation(library, installedVersion) {
4924
5251
  }
4925
5252
  async function describeProjectInstrumentation(ctx) {
4926
5253
  const hookFiles = await findHookFiles(ctx.scanPath);
4927
- const envNeat = await fileExists(import_node_path34.default.join(ctx.scanPath, ".env.neat"));
5254
+ const envNeat = await fileExists2(import_node_path36.default.join(ctx.scanPath, ".env.neat"));
4928
5255
  const registryInstrPackages = new Set(
4929
5256
  (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
4930
5257
  );
@@ -4946,31 +5273,31 @@ async function applyExtension(ctx, args, options) {
4946
5273
  );
4947
5274
  }
4948
5275
  for (const file of hookFiles) {
4949
- const content = await import_node_fs20.promises.readFile(import_node_path34.default.join(ctx.scanPath, file), "utf8");
5276
+ const content = await import_node_fs21.promises.readFile(import_node_path36.default.join(ctx.scanPath, file), "utf8");
4950
5277
  if (content.includes(args.registration_snippet)) {
4951
5278
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
4952
5279
  }
4953
5280
  }
4954
5281
  const primaryFile = hookFiles[0];
4955
- const primaryPath = import_node_path34.default.join(ctx.scanPath, primaryFile);
5282
+ const primaryPath = import_node_path36.default.join(ctx.scanPath, primaryFile);
4956
5283
  const filesTouched = [];
4957
5284
  const depsAdded = [];
4958
- const pkgPath = import_node_path34.default.join(ctx.scanPath, "package.json");
5285
+ const pkgPath = import_node_path36.default.join(ctx.scanPath, "package.json");
4959
5286
  const pkg = await readPackageJson(ctx.scanPath);
4960
5287
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
4961
5288
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
4962
- await import_node_fs20.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
5289
+ await import_node_fs21.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
4963
5290
  filesTouched.push("package.json");
4964
5291
  depsAdded.push(`${args.instrumentation_package}@${args.version}`);
4965
5292
  }
4966
- const hookContent = await import_node_fs20.promises.readFile(primaryPath, "utf8");
5293
+ const hookContent = await import_node_fs21.promises.readFile(primaryPath, "utf8");
4967
5294
  const patched = splicedContent(hookContent, args.registration_snippet);
4968
5295
  if (!patched) {
4969
5296
  throw new Error(
4970
5297
  `Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
4971
5298
  );
4972
5299
  }
4973
- await import_node_fs20.promises.writeFile(primaryPath, patched, "utf8");
5300
+ await import_node_fs21.promises.writeFile(primaryPath, patched, "utf8");
4974
5301
  filesTouched.push(primaryFile);
4975
5302
  const cmd = await detectPackageManager(ctx.scanPath);
4976
5303
  const installer = options?.runInstall ?? runPackageManagerInstall;
@@ -5001,7 +5328,7 @@ async function dryRunExtension(ctx, args) {
5001
5328
  };
5002
5329
  }
5003
5330
  for (const file of hookFiles) {
5004
- const content = await import_node_fs20.promises.readFile(import_node_path34.default.join(ctx.scanPath, file), "utf8");
5331
+ const content = await import_node_fs21.promises.readFile(import_node_path36.default.join(ctx.scanPath, file), "utf8");
5005
5332
  if (content.includes(args.registration_snippet)) {
5006
5333
  return {
5007
5334
  library: args.library,
@@ -5023,7 +5350,7 @@ async function dryRunExtension(ctx, args) {
5023
5350
  depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
5024
5351
  filesTouched.push("package.json");
5025
5352
  }
5026
- const hookContent = await import_node_fs20.promises.readFile(import_node_path34.default.join(ctx.scanPath, primaryFile), "utf8");
5353
+ const hookContent = await import_node_fs21.promises.readFile(import_node_path36.default.join(ctx.scanPath, primaryFile), "utf8");
5027
5354
  const patched = splicedContent(hookContent, args.registration_snippet);
5028
5355
  if (patched) {
5029
5356
  filesTouched.push(primaryFile);
@@ -5035,31 +5362,31 @@ async function dryRunExtension(ctx, args) {
5035
5362
  }
5036
5363
  async function rollbackExtension(ctx, args) {
5037
5364
  const logPath = extendLogPath();
5038
- if (!await fileExists(logPath)) {
5365
+ if (!await fileExists2(logPath)) {
5039
5366
  return { undone: false, message: "no apply found for library" };
5040
5367
  }
5041
- const raw = await import_node_fs20.promises.readFile(logPath, "utf8");
5368
+ const raw = await import_node_fs21.promises.readFile(logPath, "utf8");
5042
5369
  const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
5043
5370
  const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
5044
5371
  if (!match) {
5045
5372
  return { undone: false, message: "no apply found for library" };
5046
5373
  }
5047
- const pkgPath = import_node_path34.default.join(ctx.scanPath, "package.json");
5048
- if (await fileExists(pkgPath)) {
5374
+ const pkgPath = import_node_path36.default.join(ctx.scanPath, "package.json");
5375
+ if (await fileExists2(pkgPath)) {
5049
5376
  const pkg = await readPackageJson(ctx.scanPath);
5050
5377
  if (pkg.dependencies?.[match.instrumentation_package]) {
5051
5378
  const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
5052
5379
  pkg.dependencies = rest;
5053
- await import_node_fs20.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
5380
+ await import_node_fs21.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
5054
5381
  }
5055
5382
  }
5056
5383
  const hookFiles = await findHookFiles(ctx.scanPath);
5057
5384
  for (const file of hookFiles) {
5058
- const filePath = import_node_path34.default.join(ctx.scanPath, file);
5059
- const content = await import_node_fs20.promises.readFile(filePath, "utf8");
5385
+ const filePath = import_node_path36.default.join(ctx.scanPath, file);
5386
+ const content = await import_node_fs21.promises.readFile(filePath, "utf8");
5060
5387
  if (content.includes(match.registration_snippet)) {
5061
5388
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
5062
- await import_node_fs20.promises.writeFile(filePath, filtered, "utf8");
5389
+ await import_node_fs21.promises.writeFile(filePath, filtered, "utf8");
5063
5390
  break;
5064
5391
  }
5065
5392
  }
@@ -5071,7 +5398,7 @@ async function rollbackExtension(ctx, args) {
5071
5398
 
5072
5399
  // src/divergences.ts
5073
5400
  init_cjs_shims();
5074
- var import_types21 = require("@neat.is/types");
5401
+ var import_types22 = require("@neat.is/types");
5075
5402
  function bucketKey(source, target, type) {
5076
5403
  return `${type}|${source}|${target}`;
5077
5404
  }
@@ -5079,22 +5406,22 @@ function bucketEdges(graph) {
5079
5406
  const buckets = /* @__PURE__ */ new Map();
5080
5407
  graph.forEachEdge((id, attrs) => {
5081
5408
  const e = attrs;
5082
- const parsed = (0, import_types21.parseEdgeId)(id);
5409
+ const parsed = (0, import_types22.parseEdgeId)(id);
5083
5410
  const provenance = parsed?.provenance ?? e.provenance;
5084
5411
  const key = bucketKey(e.source, e.target, e.type);
5085
5412
  const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
5086
5413
  switch (provenance) {
5087
- case import_types21.Provenance.EXTRACTED:
5414
+ case import_types22.Provenance.EXTRACTED:
5088
5415
  cur.extracted = e;
5089
5416
  break;
5090
- case import_types21.Provenance.OBSERVED:
5417
+ case import_types22.Provenance.OBSERVED:
5091
5418
  cur.observed = e;
5092
5419
  break;
5093
- case import_types21.Provenance.INFERRED:
5420
+ case import_types22.Provenance.INFERRED:
5094
5421
  cur.inferred = e;
5095
5422
  break;
5096
5423
  default:
5097
- if (e.provenance === import_types21.Provenance.STALE) cur.stale = e;
5424
+ if (e.provenance === import_types22.Provenance.STALE) cur.stale = e;
5098
5425
  }
5099
5426
  buckets.set(key, cur);
5100
5427
  });
@@ -5103,7 +5430,7 @@ function bucketEdges(graph) {
5103
5430
  function nodeIsFrontier(graph, nodeId) {
5104
5431
  if (!graph.hasNode(nodeId)) return false;
5105
5432
  const attrs = graph.getNodeAttributes(nodeId);
5106
- return attrs.type === import_types21.NodeType.FrontierNode;
5433
+ return attrs.type === import_types22.NodeType.FrontierNode;
5107
5434
  }
5108
5435
  function clampConfidence(n) {
5109
5436
  if (!Number.isFinite(n)) return 0;
@@ -5124,7 +5451,7 @@ function gradedConfidence(edge) {
5124
5451
  }
5125
5452
  function detectMissingDivergences(graph, bucket) {
5126
5453
  const out = [];
5127
- if (bucket.type === import_types21.EdgeType.CONTAINS) return out;
5454
+ if (bucket.type === import_types22.EdgeType.CONTAINS) return out;
5128
5455
  if (bucket.extracted && !bucket.observed) {
5129
5456
  if (!nodeIsFrontier(graph, bucket.target)) {
5130
5457
  out.push({
@@ -5165,7 +5492,7 @@ function declaredHostFor(svc) {
5165
5492
  function hasExtractedConfiguredBy(graph, svcId) {
5166
5493
  for (const edgeId of graph.outboundEdges(svcId)) {
5167
5494
  const e = graph.getEdgeAttributes(edgeId);
5168
- if (e.type === import_types21.EdgeType.CONFIGURED_BY && e.provenance === import_types21.Provenance.EXTRACTED) {
5495
+ if (e.type === import_types22.EdgeType.CONFIGURED_BY && e.provenance === import_types22.Provenance.EXTRACTED) {
5169
5496
  return true;
5170
5497
  }
5171
5498
  }
@@ -5178,10 +5505,10 @@ function detectHostMismatch(graph, svcId, svc) {
5178
5505
  const out = [];
5179
5506
  for (const edgeId of graph.outboundEdges(svcId)) {
5180
5507
  const edge = graph.getEdgeAttributes(edgeId);
5181
- if (edge.type !== import_types21.EdgeType.CONNECTS_TO) continue;
5182
- if (edge.provenance !== import_types21.Provenance.OBSERVED) continue;
5508
+ if (edge.type !== import_types22.EdgeType.CONNECTS_TO) continue;
5509
+ if (edge.provenance !== import_types22.Provenance.OBSERVED) continue;
5183
5510
  const target = graph.getNodeAttributes(edge.target);
5184
- if (target.type !== import_types21.NodeType.DatabaseNode) continue;
5511
+ if (target.type !== import_types22.NodeType.DatabaseNode) continue;
5185
5512
  const observedHost = target.host?.trim();
5186
5513
  if (!observedHost) continue;
5187
5514
  if (observedHost === declaredHost) continue;
@@ -5203,10 +5530,10 @@ function detectCompatDivergences(graph, svcId, svc) {
5203
5530
  const deps = svc.dependencies ?? {};
5204
5531
  for (const edgeId of graph.outboundEdges(svcId)) {
5205
5532
  const edge = graph.getEdgeAttributes(edgeId);
5206
- if (edge.type !== import_types21.EdgeType.CONNECTS_TO) continue;
5207
- if (edge.provenance !== import_types21.Provenance.OBSERVED) continue;
5533
+ if (edge.type !== import_types22.EdgeType.CONNECTS_TO) continue;
5534
+ if (edge.provenance !== import_types22.Provenance.OBSERVED) continue;
5208
5535
  const target = graph.getNodeAttributes(edge.target);
5209
- if (target.type !== import_types21.NodeType.DatabaseNode) continue;
5536
+ if (target.type !== import_types22.NodeType.DatabaseNode) continue;
5210
5537
  for (const pair of compatPairs()) {
5211
5538
  if (pair.engine !== target.engine) continue;
5212
5539
  const declared = deps[pair.driver];
@@ -5267,7 +5594,7 @@ function computeDivergences(graph, opts = {}) {
5267
5594
  }
5268
5595
  graph.forEachNode((nodeId, attrs) => {
5269
5596
  const n = attrs;
5270
- if (n.type !== import_types21.NodeType.ServiceNode) return;
5597
+ if (n.type !== import_types22.NodeType.ServiceNode) return;
5271
5598
  const svc = n;
5272
5599
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
5273
5600
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
@@ -5300,7 +5627,7 @@ function computeDivergences(graph, opts = {}) {
5300
5627
  if (a.source !== b.source) return a.source.localeCompare(b.source);
5301
5628
  return a.target.localeCompare(b.target);
5302
5629
  });
5303
- return import_types21.DivergenceResultSchema.parse({
5630
+ return import_types22.DivergenceResultSchema.parse({
5304
5631
  divergences: filtered,
5305
5632
  totalAffected: filtered.length,
5306
5633
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -5309,7 +5636,7 @@ function computeDivergences(graph, opts = {}) {
5309
5636
 
5310
5637
  // src/diff.ts
5311
5638
  init_cjs_shims();
5312
- var import_node_fs21 = require("fs");
5639
+ var import_node_fs22 = require("fs");
5313
5640
  async function loadSnapshotForDiff(target) {
5314
5641
  if (/^https?:\/\//i.test(target)) {
5315
5642
  const res = await fetch(target);
@@ -5318,7 +5645,7 @@ async function loadSnapshotForDiff(target) {
5318
5645
  }
5319
5646
  return await res.json();
5320
5647
  }
5321
- const raw = await import_node_fs21.promises.readFile(target, "utf8");
5648
+ const raw = await import_node_fs22.promises.readFile(target, "utf8");
5322
5649
  return JSON.parse(raw);
5323
5650
  }
5324
5651
  function indexEntries(entries) {
@@ -5386,25 +5713,25 @@ function canonicalJson(value) {
5386
5713
 
5387
5714
  // src/registry.ts
5388
5715
  init_cjs_shims();
5389
- var import_node_fs22 = require("fs");
5716
+ var import_node_fs23 = require("fs");
5390
5717
  var import_node_os3 = __toESM(require("os"), 1);
5391
- var import_node_path35 = __toESM(require("path"), 1);
5392
- var import_types22 = require("@neat.is/types");
5718
+ var import_node_path37 = __toESM(require("path"), 1);
5719
+ var import_types23 = require("@neat.is/types");
5393
5720
  var LOCK_TIMEOUT_MS = 5e3;
5394
5721
  var LOCK_RETRY_MS = 50;
5395
5722
  function neatHome() {
5396
5723
  const override = process.env.NEAT_HOME;
5397
- if (override && override.length > 0) return import_node_path35.default.resolve(override);
5398
- return import_node_path35.default.join(import_node_os3.default.homedir(), ".neat");
5724
+ if (override && override.length > 0) return import_node_path37.default.resolve(override);
5725
+ return import_node_path37.default.join(import_node_os3.default.homedir(), ".neat");
5399
5726
  }
5400
5727
  function registryPath() {
5401
- return import_node_path35.default.join(neatHome(), "projects.json");
5728
+ return import_node_path37.default.join(neatHome(), "projects.json");
5402
5729
  }
5403
5730
  function registryLockPath() {
5404
- return import_node_path35.default.join(neatHome(), "projects.json.lock");
5731
+ return import_node_path37.default.join(neatHome(), "projects.json.lock");
5405
5732
  }
5406
5733
  function daemonPidPath() {
5407
- return import_node_path35.default.join(neatHome(), "neatd.pid");
5734
+ return import_node_path37.default.join(neatHome(), "neatd.pid");
5408
5735
  }
5409
5736
  function isPidAliveDefault(pid) {
5410
5737
  try {
@@ -5416,7 +5743,7 @@ function isPidAliveDefault(pid) {
5416
5743
  }
5417
5744
  async function readPidFile(file) {
5418
5745
  try {
5419
- const raw = await import_node_fs22.promises.readFile(file, "utf8");
5746
+ const raw = await import_node_fs23.promises.readFile(file, "utf8");
5420
5747
  const pid = Number.parseInt(raw.trim(), 10);
5421
5748
  return Number.isInteger(pid) && pid > 0 ? pid : void 0;
5422
5749
  } catch {
@@ -5464,24 +5791,24 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
5464
5791
  }
5465
5792
  }
5466
5793
  async function writeAtomically(target, contents) {
5467
- await import_node_fs22.promises.mkdir(import_node_path35.default.dirname(target), { recursive: true });
5794
+ await import_node_fs23.promises.mkdir(import_node_path37.default.dirname(target), { recursive: true });
5468
5795
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
5469
- const fd = await import_node_fs22.promises.open(tmp, "w");
5796
+ const fd = await import_node_fs23.promises.open(tmp, "w");
5470
5797
  try {
5471
5798
  await fd.writeFile(contents, "utf8");
5472
5799
  await fd.sync();
5473
5800
  } finally {
5474
5801
  await fd.close();
5475
5802
  }
5476
- await import_node_fs22.promises.rename(tmp, target);
5803
+ await import_node_fs23.promises.rename(tmp, target);
5477
5804
  }
5478
5805
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
5479
5806
  const deadline = Date.now() + timeoutMs;
5480
- await import_node_fs22.promises.mkdir(import_node_path35.default.dirname(lockPath), { recursive: true });
5807
+ await import_node_fs23.promises.mkdir(import_node_path37.default.dirname(lockPath), { recursive: true });
5481
5808
  let probedHolder = false;
5482
5809
  while (true) {
5483
5810
  try {
5484
- const fd = await import_node_fs22.promises.open(lockPath, "wx");
5811
+ const fd = await import_node_fs23.promises.open(lockPath, "wx");
5485
5812
  try {
5486
5813
  await fd.writeFile(`${process.pid}
5487
5814
  `, "utf8");
@@ -5506,7 +5833,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
5506
5833
  }
5507
5834
  }
5508
5835
  async function releaseLock(lockPath) {
5509
- await import_node_fs22.promises.unlink(lockPath).catch(() => {
5836
+ await import_node_fs23.promises.unlink(lockPath).catch(() => {
5510
5837
  });
5511
5838
  }
5512
5839
  async function withLock(fn) {
@@ -5522,7 +5849,7 @@ async function readRegistry() {
5522
5849
  const file = registryPath();
5523
5850
  let raw;
5524
5851
  try {
5525
- raw = await import_node_fs22.promises.readFile(file, "utf8");
5852
+ raw = await import_node_fs23.promises.readFile(file, "utf8");
5526
5853
  } catch (err) {
5527
5854
  if (err.code === "ENOENT") {
5528
5855
  return { version: 1, projects: [] };
@@ -5530,10 +5857,10 @@ async function readRegistry() {
5530
5857
  throw err;
5531
5858
  }
5532
5859
  const parsed = JSON.parse(raw);
5533
- return import_types22.RegistryFileSchema.parse(parsed);
5860
+ return import_types23.RegistryFileSchema.parse(parsed);
5534
5861
  }
5535
5862
  async function writeRegistry(reg) {
5536
- const validated = import_types22.RegistryFileSchema.parse(reg);
5863
+ const validated = import_types23.RegistryFileSchema.parse(reg);
5537
5864
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
5538
5865
  }
5539
5866
  async function getProject(name) {
@@ -5755,11 +6082,11 @@ function registerRoutes(scope, ctx) {
5755
6082
  const candidates = req2.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
5756
6083
  const parsed = [];
5757
6084
  for (const c of candidates) {
5758
- const r = import_types23.DivergenceTypeSchema.safeParse(c);
6085
+ const r = import_types24.DivergenceTypeSchema.safeParse(c);
5759
6086
  if (!r.success) {
5760
6087
  return reply.code(400).send({
5761
6088
  error: `unknown divergence type "${c}"`,
5762
- allowed: import_types23.DivergenceTypeSchema.options
6089
+ allowed: import_types24.DivergenceTypeSchema.options
5763
6090
  });
5764
6091
  }
5765
6092
  parsed.push(r.data);
@@ -5972,7 +6299,7 @@ function registerRoutes(scope, ctx) {
5972
6299
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
5973
6300
  let violations = await log.readAll();
5974
6301
  if (req2.query.severity) {
5975
- const sev = import_types23.PolicySeveritySchema.safeParse(req2.query.severity);
6302
+ const sev = import_types24.PolicySeveritySchema.safeParse(req2.query.severity);
5976
6303
  if (!sev.success) {
5977
6304
  return reply.code(400).send({
5978
6305
  error: "invalid severity",
@@ -5989,7 +6316,7 @@ function registerRoutes(scope, ctx) {
5989
6316
  scope.post("/policies/check", async (req2, reply) => {
5990
6317
  const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
5991
6318
  if (!proj) return;
5992
- const parsed = import_types23.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
6319
+ const parsed = import_types24.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
5993
6320
  if (!parsed.success) {
5994
6321
  return reply.code(400).send({
5995
6322
  error: "invalid /policies/check body",
@@ -6231,8 +6558,8 @@ init_auth();
6231
6558
 
6232
6559
  // src/unrouted.ts
6233
6560
  init_cjs_shims();
6234
- var import_node_fs23 = require("fs");
6235
- var import_node_path38 = __toESM(require("path"), 1);
6561
+ var import_node_fs24 = require("fs");
6562
+ var import_node_path40 = __toESM(require("path"), 1);
6236
6563
  function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
6237
6564
  return {
6238
6565
  timestamp: now.toISOString(),
@@ -6242,21 +6569,21 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
6242
6569
  };
6243
6570
  }
6244
6571
  async function appendUnroutedSpan(neatHome3, record) {
6245
- const target = import_node_path38.default.join(neatHome3, "errors.ndjson");
6246
- await import_node_fs23.promises.mkdir(neatHome3, { recursive: true });
6247
- await import_node_fs23.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
6572
+ const target = import_node_path40.default.join(neatHome3, "errors.ndjson");
6573
+ await import_node_fs24.promises.mkdir(neatHome3, { recursive: true });
6574
+ await import_node_fs24.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
6248
6575
  }
6249
6576
  function unroutedErrorsPath(neatHome3) {
6250
- return import_node_path38.default.join(neatHome3, "errors.ndjson");
6577
+ return import_node_path40.default.join(neatHome3, "errors.ndjson");
6251
6578
  }
6252
6579
 
6253
6580
  // src/daemon.ts
6254
6581
  function neatHomeFor(opts) {
6255
- if (opts.neatHome && opts.neatHome.length > 0) return import_node_path39.default.resolve(opts.neatHome);
6582
+ if (opts.neatHome && opts.neatHome.length > 0) return import_node_path41.default.resolve(opts.neatHome);
6256
6583
  const env = process.env.NEAT_HOME;
6257
- if (env && env.length > 0) return import_node_path39.default.resolve(env);
6584
+ if (env && env.length > 0) return import_node_path41.default.resolve(env);
6258
6585
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
6259
- return import_node_path39.default.join(home, ".neat");
6586
+ return import_node_path41.default.join(home, ".neat");
6260
6587
  }
6261
6588
  function routeSpanToProject(serviceName, projects) {
6262
6589
  if (!serviceName) return DEFAULT_PROJECT;
@@ -6291,9 +6618,9 @@ function isTokenContained(needle, haystack) {
6291
6618
  return tokens.includes(needle);
6292
6619
  }
6293
6620
  async function bootstrapProject(entry2) {
6294
- const paths = pathsForProject(entry2.name, import_node_path39.default.join(entry2.path, "neat-out"));
6621
+ const paths = pathsForProject(entry2.name, import_node_path41.default.join(entry2.path, "neat-out"));
6295
6622
  try {
6296
- const stat = await import_node_fs24.promises.stat(entry2.path);
6623
+ const stat = await import_node_fs25.promises.stat(entry2.path);
6297
6624
  if (!stat.isDirectory()) {
6298
6625
  throw new Error(`registered path ${entry2.path} is not a directory`);
6299
6626
  }
@@ -6359,13 +6686,13 @@ async function startDaemon(opts = {}) {
6359
6686
  const home = neatHomeFor(opts);
6360
6687
  const regPath = registryPath();
6361
6688
  try {
6362
- await import_node_fs24.promises.access(regPath);
6689
+ await import_node_fs25.promises.access(regPath);
6363
6690
  } catch {
6364
6691
  throw new Error(
6365
6692
  `neatd: registry not found at ${regPath}. Run \`neat init <path>\` to register a project before starting the daemon.`
6366
6693
  );
6367
6694
  }
6368
- const pidPath = import_node_path39.default.join(home, "neatd.pid");
6695
+ const pidPath = import_node_path41.default.join(home, "neatd.pid");
6369
6696
  await writeAtomically(pidPath, `${process.pid}
6370
6697
  `);
6371
6698
  const slots = /* @__PURE__ */ new Map();
@@ -6523,7 +6850,7 @@ async function startDaemon(opts = {}) {
6523
6850
  }
6524
6851
  if (restApp) await restApp.close().catch(() => {
6525
6852
  });
6526
- await import_node_fs24.promises.unlink(pidPath).catch(() => {
6853
+ await import_node_fs25.promises.unlink(pidPath).catch(() => {
6527
6854
  });
6528
6855
  throw new Error(
6529
6856
  `neatd: failed to bind REST on port ${restPort} \u2014 ${err.message}`
@@ -6629,7 +6956,7 @@ async function startDaemon(opts = {}) {
6629
6956
  });
6630
6957
  if (otlpApp) await otlpApp.close().catch(() => {
6631
6958
  });
6632
- await import_node_fs24.promises.unlink(pidPath).catch(() => {
6959
+ await import_node_fs25.promises.unlink(pidPath).catch(() => {
6633
6960
  });
6634
6961
  throw new Error(
6635
6962
  `neatd: failed to bind OTLP on port ${otlpPort} \u2014 ${err.message}`
@@ -6675,9 +7002,9 @@ async function startDaemon(opts = {}) {
6675
7002
  let registryWatcher = null;
6676
7003
  let reloadTimer = null;
6677
7004
  try {
6678
- const regDir = import_node_path39.default.dirname(regPath);
6679
- const regBase = import_node_path39.default.basename(regPath);
6680
- registryWatcher = (0, import_node_fs24.watch)(regDir, (_eventType, filename) => {
7005
+ const regDir = import_node_path41.default.dirname(regPath);
7006
+ const regBase = import_node_path41.default.basename(regPath);
7007
+ registryWatcher = (0, import_node_fs25.watch)(regDir, (_eventType, filename) => {
6681
7008
  if (filename !== null && filename !== regBase) return;
6682
7009
  if (reloadTimer) clearTimeout(reloadTimer);
6683
7010
  reloadTimer = setTimeout(() => {
@@ -6720,7 +7047,7 @@ async function startDaemon(opts = {}) {
6720
7047
  } catch {
6721
7048
  }
6722
7049
  }
6723
- await import_node_fs24.promises.unlink(pidPath).catch(() => {
7050
+ await import_node_fs25.promises.unlink(pidPath).catch(() => {
6724
7051
  });
6725
7052
  };
6726
7053
  return {
@@ -6742,7 +7069,7 @@ init_auth();
6742
7069
  init_cjs_shims();
6743
7070
  var import_node_child_process2 = require("child_process");
6744
7071
  var import_node_net = __toESM(require("net"), 1);
6745
- var import_node_path40 = __toESM(require("path"), 1);
7072
+ var import_node_path42 = __toESM(require("path"), 1);
6746
7073
  var DEFAULT_WEB_PORT = 6328;
6747
7074
  async function assertPortFree(port) {
6748
7075
  await new Promise((resolve, reject) => {
@@ -6770,10 +7097,10 @@ function resolveWebPackageDir() {
6770
7097
  eval("require")
6771
7098
  );
6772
7099
  const pkgJsonPath = req.resolve("@neat.is/web/package.json");
6773
- return import_node_path40.default.dirname(pkgJsonPath);
7100
+ return import_node_path42.default.dirname(pkgJsonPath);
6774
7101
  }
6775
7102
  function resolveStandaloneServerEntry(webDir) {
6776
- return import_node_path40.default.join(webDir, ".next/standalone/packages/web/server.js");
7103
+ return import_node_path42.default.join(webDir, ".next/standalone/packages/web/server.js");
6777
7104
  }
6778
7105
  async function spawnWebUI(restPort) {
6779
7106
  const portRaw = process.env.NEAT_WEB_PORT;
@@ -6798,7 +7125,7 @@ async function spawnWebUI(restPort) {
6798
7125
  NEAT_API_URL: process.env.NEAT_API_URL ?? `http://localhost:${restPort}`
6799
7126
  };
6800
7127
  const child = (0, import_node_child_process2.spawn)(process.execPath, [serverEntry], {
6801
- cwd: import_node_path40.default.dirname(serverEntry),
7128
+ cwd: import_node_path42.default.dirname(serverEntry),
6802
7129
  env,
6803
7130
  stdio: ["ignore", "inherit", "inherit"],
6804
7131
  detached: false
@@ -6911,14 +7238,14 @@ function localVersion() {
6911
7238
  }
6912
7239
  function neatHome2() {
6913
7240
  if (process.env.NEAT_HOME && process.env.NEAT_HOME.length > 0) {
6914
- return import_node_path41.default.resolve(process.env.NEAT_HOME);
7241
+ return import_node_path43.default.resolve(process.env.NEAT_HOME);
6915
7242
  }
6916
7243
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
6917
- return import_node_path41.default.join(home, ".neat");
7244
+ return import_node_path43.default.join(home, ".neat");
6918
7245
  }
6919
7246
  async function readPid() {
6920
7247
  try {
6921
- const raw = await import_node_fs25.promises.readFile(import_node_path41.default.join(neatHome2(), "neatd.pid"), "utf8");
7248
+ const raw = await import_node_fs26.promises.readFile(import_node_path43.default.join(neatHome2(), "neatd.pid"), "utf8");
6922
7249
  const n = Number.parseInt(raw.trim(), 10);
6923
7250
  return Number.isFinite(n) ? n : null;
6924
7251
  } catch {