@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/index.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", (req, reply, done) => {
60
- const path40 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
60
+ const path42 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
61
61
  for (const suffix of suffixes) {
62
- if (path40 === suffix || path40.endsWith(suffix)) {
62
+ if (path42 === suffix || path42.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(req) {
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
  }
@@ -574,12 +579,12 @@ function logSpanHandler(span) {
574
579
  `otel: ${span.service} ${span.name} parent=${parent} status=${status2}${db}`
575
580
  );
576
581
  }
577
- var import_node_path37, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
582
+ var import_node_path39, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
578
583
  var init_otel = __esm({
579
584
  "src/otel.ts"() {
580
585
  "use strict";
581
586
  init_cjs_shims();
582
- import_node_path37 = __toESM(require("path"), 1);
587
+ import_node_path39 = __toESM(require("path"), 1);
583
588
  import_node_url2 = require("url");
584
589
  import_fastify2 = __toESM(require("fastify"), 1);
585
590
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -1117,19 +1122,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1117
1122
  function longestIncomingWalk(graph, start, maxDepth) {
1118
1123
  let best = { path: [start], edges: [] };
1119
1124
  const visited = /* @__PURE__ */ new Set([start]);
1120
- function step(node, path40, edges) {
1121
- if (path40.length > best.path.length) {
1122
- best = { path: [...path40], edges: [...edges] };
1125
+ function step(node, path42, edges) {
1126
+ if (path42.length > best.path.length) {
1127
+ best = { path: [...path42], edges: [...edges] };
1123
1128
  }
1124
- if (path40.length - 1 >= maxDepth) return;
1129
+ if (path42.length - 1 >= maxDepth) return;
1125
1130
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1126
1131
  for (const [srcId, edge] of incoming) {
1127
1132
  if (visited.has(srcId)) continue;
1128
1133
  visited.add(srcId);
1129
- path40.push(srcId);
1134
+ path42.push(srcId);
1130
1135
  edges.push(edge);
1131
- step(srcId, path40, edges);
1132
- path40.pop();
1136
+ step(srcId, path42, edges);
1137
+ path42.pop();
1133
1138
  edges.pop();
1134
1139
  visited.delete(srcId);
1135
1140
  }
@@ -3089,10 +3094,8 @@ async function addServiceAliases(graph, scanPath, services) {
3089
3094
  await collectK8sAliases(graph, scanPath, byName);
3090
3095
  }
3091
3096
 
3092
- // src/extract/databases/index.ts
3097
+ // src/extract/files.ts
3093
3098
  init_cjs_shims();
3094
- var import_node_path18 = __toESM(require("path"), 1);
3095
- var import_types8 = require("@neat.is/types");
3096
3099
 
3097
3100
  // src/extract/calls/shared.ts
3098
3101
  init_cjs_shims();
@@ -3191,11 +3194,333 @@ function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
3191
3194
  return { fileNodeId, nodesAdded, edgesAdded };
3192
3195
  }
3193
3196
 
3197
+ // src/extract/files.ts
3198
+ var import_node_path11 = __toESM(require("path"), 1);
3199
+ async function addFiles(graph, services) {
3200
+ let nodesAdded = 0;
3201
+ let edgesAdded = 0;
3202
+ for (const service of services) {
3203
+ const filePaths = await walkSourceFiles(service.dir);
3204
+ for (const filePath of filePaths) {
3205
+ const relPath = toPosix2(import_node_path11.default.relative(service.dir, filePath));
3206
+ const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
3207
+ graph,
3208
+ service.pkg.name,
3209
+ service.node.id,
3210
+ relPath
3211
+ );
3212
+ nodesAdded += n;
3213
+ edgesAdded += e;
3214
+ }
3215
+ }
3216
+ return { nodesAdded, edgesAdded };
3217
+ }
3218
+
3219
+ // src/extract/imports.ts
3220
+ init_cjs_shims();
3221
+ var import_node_path12 = __toESM(require("path"), 1);
3222
+ var import_node_fs11 = require("fs");
3223
+ var import_tree_sitter = __toESM(require("tree-sitter"), 1);
3224
+ var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
3225
+ var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
3226
+ var import_types8 = require("@neat.is/types");
3227
+ var PARSE_CHUNK = 16384;
3228
+ function parseSource(parser, source) {
3229
+ return parser.parse(
3230
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK)
3231
+ );
3232
+ }
3233
+ function makeJsParser() {
3234
+ const p = new import_tree_sitter.default();
3235
+ p.setLanguage(import_tree_sitter_javascript.default);
3236
+ return p;
3237
+ }
3238
+ function makePyParser() {
3239
+ const p = new import_tree_sitter.default();
3240
+ p.setLanguage(import_tree_sitter_python.default);
3241
+ return p;
3242
+ }
3243
+ function stringLiteralText(node) {
3244
+ for (let i = 0; i < node.childCount; i++) {
3245
+ const child = node.child(i);
3246
+ if (child?.type === "string_fragment") return child.text;
3247
+ }
3248
+ const raw = node.text;
3249
+ if (raw.length >= 2) return raw.slice(1, -1);
3250
+ return raw.length === 0 ? null : "";
3251
+ }
3252
+ function clipSnippet(text) {
3253
+ const oneLine = text.split("\n")[0] ?? text;
3254
+ return oneLine.length > 120 ? oneLine.slice(0, 120) : oneLine;
3255
+ }
3256
+ function collectJsImports(node, out) {
3257
+ if (node.type === "import_statement") {
3258
+ const source = node.childForFieldName("source");
3259
+ if (source) {
3260
+ const specifier = stringLiteralText(source);
3261
+ if (specifier) {
3262
+ out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
3263
+ }
3264
+ }
3265
+ return;
3266
+ }
3267
+ if (node.type === "call_expression") {
3268
+ const fn = node.childForFieldName("function");
3269
+ if (fn?.type === "identifier" && fn.text === "require") {
3270
+ const args = node.childForFieldName("arguments");
3271
+ const firstArg = args?.namedChild(0);
3272
+ if (firstArg?.type === "string") {
3273
+ const specifier = stringLiteralText(firstArg);
3274
+ if (specifier) {
3275
+ out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
3276
+ }
3277
+ }
3278
+ }
3279
+ }
3280
+ for (let i = 0; i < node.namedChildCount; i++) {
3281
+ const child = node.namedChild(i);
3282
+ if (child) collectJsImports(child, out);
3283
+ }
3284
+ }
3285
+ function collectPyImports(node, out) {
3286
+ if (node.type === "import_from_statement") {
3287
+ let level = 0;
3288
+ let modulePath = "";
3289
+ let pastFrom = false;
3290
+ for (let i = 0; i < node.childCount; i++) {
3291
+ const child = node.child(i);
3292
+ if (!child) continue;
3293
+ if (!pastFrom) {
3294
+ if (child.type === "from") pastFrom = true;
3295
+ continue;
3296
+ }
3297
+ if (child.type === "import") break;
3298
+ if (child.type === "relative_import") {
3299
+ for (let j = 0; j < child.childCount; j++) {
3300
+ const rc = child.child(j);
3301
+ if (!rc) continue;
3302
+ if (rc.type === "import_prefix") {
3303
+ for (let k = 0; k < rc.childCount; k++) {
3304
+ if (rc.child(k)?.type === ".") level++;
3305
+ }
3306
+ } else if (rc.type === "dotted_name") modulePath = rc.text;
3307
+ }
3308
+ break;
3309
+ }
3310
+ if (child.type === "dotted_name") {
3311
+ modulePath = child.text;
3312
+ break;
3313
+ }
3314
+ }
3315
+ if (level > 0 || modulePath) {
3316
+ out.push({ modulePath, level, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
3317
+ }
3318
+ }
3319
+ for (let i = 0; i < node.namedChildCount; i++) {
3320
+ const child = node.namedChild(i);
3321
+ if (child) collectPyImports(child, out);
3322
+ }
3323
+ }
3324
+ async function fileExists(p) {
3325
+ try {
3326
+ await import_node_fs11.promises.access(p);
3327
+ return true;
3328
+ } catch {
3329
+ return false;
3330
+ }
3331
+ }
3332
+ function isWithinServiceDir(candidate, serviceDir) {
3333
+ const rel = import_node_path12.default.relative(serviceDir, candidate);
3334
+ return rel !== "" && !rel.startsWith("..") && !import_node_path12.default.isAbsolute(rel);
3335
+ }
3336
+ var JS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
3337
+ var JS_INDEX_FILES = JS_EXTENSIONS.map((ext) => `index${ext}`);
3338
+ async function firstExistingCandidate(base, serviceDir) {
3339
+ for (const ext of JS_EXTENSIONS) {
3340
+ const candidate = base + ext;
3341
+ if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
3342
+ return toPosix2(import_node_path12.default.relative(serviceDir, candidate));
3343
+ }
3344
+ }
3345
+ for (const indexFile of JS_INDEX_FILES) {
3346
+ const candidate = import_node_path12.default.join(base, indexFile);
3347
+ if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
3348
+ return toPosix2(import_node_path12.default.relative(serviceDir, candidate));
3349
+ }
3350
+ }
3351
+ return null;
3352
+ }
3353
+ async function loadTsPathConfig(serviceDir) {
3354
+ const tsconfigPath = import_node_path12.default.join(serviceDir, "tsconfig.json");
3355
+ let raw;
3356
+ try {
3357
+ raw = await import_node_fs11.promises.readFile(tsconfigPath, "utf8");
3358
+ } catch {
3359
+ return null;
3360
+ }
3361
+ try {
3362
+ const parsed = JSON.parse(raw);
3363
+ const paths = parsed.compilerOptions?.paths;
3364
+ if (!paths || Object.keys(paths).length === 0) return null;
3365
+ const baseUrl = parsed.compilerOptions?.baseUrl;
3366
+ return { paths, baseDir: baseUrl ? import_node_path12.default.resolve(serviceDir, baseUrl) : serviceDir };
3367
+ } catch (err) {
3368
+ recordExtractionError("import alias resolution", tsconfigPath, err);
3369
+ return null;
3370
+ }
3371
+ }
3372
+ async function resolveTsAlias(specifier, config, serviceDir) {
3373
+ for (const [pattern, targets] of Object.entries(config.paths)) {
3374
+ let suffix = null;
3375
+ if (pattern === specifier) {
3376
+ suffix = "";
3377
+ } else if (pattern.endsWith("/*")) {
3378
+ const prefix = pattern.slice(0, -1);
3379
+ if (specifier.startsWith(prefix)) suffix = specifier.slice(prefix.length);
3380
+ }
3381
+ if (suffix === null) continue;
3382
+ for (const target of targets) {
3383
+ const targetBase = target.endsWith("/*") ? target.slice(0, -2) : target.replace(/\*$/, "");
3384
+ const resolvedBase = import_node_path12.default.resolve(config.baseDir, targetBase, suffix);
3385
+ const hit = await firstExistingCandidate(resolvedBase, serviceDir);
3386
+ if (hit) return hit;
3387
+ if (isWithinServiceDir(resolvedBase, serviceDir) && await fileExists(resolvedBase)) {
3388
+ return toPosix2(import_node_path12.default.relative(serviceDir, resolvedBase));
3389
+ }
3390
+ }
3391
+ }
3392
+ return null;
3393
+ }
3394
+ async function resolveJsImport(specifier, importerDir, serviceDir, tsPaths) {
3395
+ if (!specifier) return null;
3396
+ if (specifier.startsWith("./") || specifier.startsWith("../")) {
3397
+ const base = import_node_path12.default.resolve(importerDir, specifier);
3398
+ const ext = import_node_path12.default.extname(specifier);
3399
+ if (ext) {
3400
+ if (ext === ".js" || ext === ".jsx") {
3401
+ const tsExt = ext === ".jsx" ? ".tsx" : ".ts";
3402
+ const tsSibling = base.slice(0, -ext.length) + tsExt;
3403
+ if (isWithinServiceDir(tsSibling, serviceDir) && await fileExists(tsSibling)) {
3404
+ return toPosix2(import_node_path12.default.relative(serviceDir, tsSibling));
3405
+ }
3406
+ }
3407
+ if (isWithinServiceDir(base, serviceDir) && await fileExists(base)) {
3408
+ return toPosix2(import_node_path12.default.relative(serviceDir, base));
3409
+ }
3410
+ return null;
3411
+ }
3412
+ return firstExistingCandidate(base, serviceDir);
3413
+ }
3414
+ if (tsPaths) return resolveTsAlias(specifier, tsPaths, serviceDir);
3415
+ return null;
3416
+ }
3417
+ async function resolvePyImport(imp, importerPath, serviceDir) {
3418
+ if (!imp.modulePath) return null;
3419
+ const relPath = imp.modulePath.split(".").join("/");
3420
+ let baseDir;
3421
+ if (imp.level > 0) {
3422
+ baseDir = import_node_path12.default.dirname(importerPath);
3423
+ for (let i = 1; i < imp.level; i++) baseDir = import_node_path12.default.dirname(baseDir);
3424
+ } else {
3425
+ baseDir = serviceDir;
3426
+ }
3427
+ const candidates = [import_node_path12.default.join(baseDir, `${relPath}.py`), import_node_path12.default.join(baseDir, relPath, "__init__.py")];
3428
+ for (const candidate of candidates) {
3429
+ if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
3430
+ return toPosix2(import_node_path12.default.relative(serviceDir, candidate));
3431
+ }
3432
+ }
3433
+ return null;
3434
+ }
3435
+ function emitImportEdge(graph, serviceName, importerFileId, importerRelPath, importeeRelPath, line, snippet2) {
3436
+ const importeeFileId = (0, import_types8.fileId)(serviceName, importeeRelPath);
3437
+ if (!graph.hasNode(importeeFileId)) return 0;
3438
+ const edgeId = (0, import_types8.extractedEdgeId)(importerFileId, importeeFileId, import_types8.EdgeType.IMPORTS);
3439
+ if (graph.hasEdge(edgeId)) return 0;
3440
+ const edge = {
3441
+ id: edgeId,
3442
+ source: importerFileId,
3443
+ target: importeeFileId,
3444
+ type: import_types8.EdgeType.IMPORTS,
3445
+ provenance: import_types8.Provenance.EXTRACTED,
3446
+ confidence: (0, import_types8.confidenceForExtracted)("structural"),
3447
+ evidence: { file: importerRelPath, line, snippet: snippet2 }
3448
+ };
3449
+ graph.addEdgeWithKey(edgeId, importerFileId, importeeFileId, edge);
3450
+ return 1;
3451
+ }
3452
+ async function addImports(graph, services) {
3453
+ const jsParser = makeJsParser();
3454
+ const pyParser = makePyParser();
3455
+ let edgesAdded = 0;
3456
+ for (const service of services) {
3457
+ const tsPaths = await loadTsPathConfig(service.dir);
3458
+ const files = await loadSourceFiles(service.dir);
3459
+ for (const file of files) {
3460
+ if (isTestPath(file.path)) continue;
3461
+ const relFile = toPosix2(import_node_path12.default.relative(service.dir, file.path));
3462
+ const importerFileId = (0, import_types8.fileId)(service.pkg.name, relFile);
3463
+ const isPython = import_node_path12.default.extname(file.path) === ".py";
3464
+ if (isPython) {
3465
+ let pyImports = [];
3466
+ try {
3467
+ const tree = parseSource(pyParser, file.content);
3468
+ collectPyImports(tree.rootNode, pyImports);
3469
+ } catch (err) {
3470
+ recordExtractionError("import extraction", file.path, err);
3471
+ continue;
3472
+ }
3473
+ for (const imp of pyImports) {
3474
+ const resolved = await resolvePyImport(imp, file.path, service.dir);
3475
+ if (!resolved) continue;
3476
+ edgesAdded += emitImportEdge(
3477
+ graph,
3478
+ service.pkg.name,
3479
+ importerFileId,
3480
+ relFile,
3481
+ resolved,
3482
+ imp.line,
3483
+ imp.snippet
3484
+ );
3485
+ }
3486
+ continue;
3487
+ }
3488
+ let jsImports = [];
3489
+ try {
3490
+ const tree = parseSource(jsParser, file.content);
3491
+ collectJsImports(tree.rootNode, jsImports);
3492
+ } catch (err) {
3493
+ recordExtractionError("import extraction", file.path, err);
3494
+ continue;
3495
+ }
3496
+ for (const imp of jsImports) {
3497
+ const resolved = await resolveJsImport(imp.specifier, import_node_path12.default.dirname(file.path), service.dir, tsPaths);
3498
+ if (!resolved) continue;
3499
+ edgesAdded += emitImportEdge(
3500
+ graph,
3501
+ service.pkg.name,
3502
+ importerFileId,
3503
+ relFile,
3504
+ resolved,
3505
+ imp.line,
3506
+ imp.snippet
3507
+ );
3508
+ }
3509
+ }
3510
+ }
3511
+ return { nodesAdded: 0, edgesAdded };
3512
+ }
3513
+
3514
+ // src/extract/databases/index.ts
3515
+ init_cjs_shims();
3516
+ var import_node_path20 = __toESM(require("path"), 1);
3517
+ var import_types9 = require("@neat.is/types");
3518
+
3194
3519
  // src/extract/databases/db-config-yaml.ts
3195
3520
  init_cjs_shims();
3196
- var import_node_path11 = __toESM(require("path"), 1);
3521
+ var import_node_path13 = __toESM(require("path"), 1);
3197
3522
  async function parse(serviceDir) {
3198
- const yamlPath = import_node_path11.default.join(serviceDir, "db-config.yaml");
3523
+ const yamlPath = import_node_path13.default.join(serviceDir, "db-config.yaml");
3199
3524
  if (!await exists(yamlPath)) return [];
3200
3525
  const raw = await readYaml(yamlPath);
3201
3526
  return [
@@ -3213,13 +3538,13 @@ var dbConfigYamlParser = { name: "db-config.yaml", parse };
3213
3538
 
3214
3539
  // src/extract/databases/dotenv.ts
3215
3540
  init_cjs_shims();
3216
- var import_node_fs12 = require("fs");
3217
- var import_node_path13 = __toESM(require("path"), 1);
3541
+ var import_node_fs13 = require("fs");
3542
+ var import_node_path15 = __toESM(require("path"), 1);
3218
3543
 
3219
3544
  // src/extract/databases/shared.ts
3220
3545
  init_cjs_shims();
3221
- var import_node_fs11 = require("fs");
3222
- var import_node_path12 = __toESM(require("path"), 1);
3546
+ var import_node_fs12 = require("fs");
3547
+ var import_node_path14 = __toESM(require("path"), 1);
3223
3548
  function schemeToEngine(scheme) {
3224
3549
  const s = scheme.toLowerCase().split("+")[0];
3225
3550
  switch (s) {
@@ -3258,14 +3583,14 @@ function parseConnectionString(url) {
3258
3583
  }
3259
3584
  async function readIfExists(filePath) {
3260
3585
  try {
3261
- return await import_node_fs11.promises.readFile(filePath, "utf8");
3586
+ return await import_node_fs12.promises.readFile(filePath, "utf8");
3262
3587
  } catch {
3263
3588
  return null;
3264
3589
  }
3265
3590
  }
3266
3591
  async function findFirst(serviceDir, candidates) {
3267
3592
  for (const rel of candidates) {
3268
- const abs = import_node_path12.default.join(serviceDir, rel);
3593
+ const abs = import_node_path14.default.join(serviceDir, rel);
3269
3594
  const content = await readIfExists(abs);
3270
3595
  if (content !== null) return abs;
3271
3596
  }
@@ -3316,15 +3641,15 @@ function parseDotenvLine(line) {
3316
3641
  return { key, value };
3317
3642
  }
3318
3643
  async function parse2(serviceDir) {
3319
- const entries = await import_node_fs12.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
3644
+ const entries = await import_node_fs13.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
3320
3645
  const configs = [];
3321
3646
  const seen = /* @__PURE__ */ new Set();
3322
3647
  for (const entry of entries) {
3323
3648
  if (!entry.isFile()) continue;
3324
3649
  const match = isConfigFile(entry.name);
3325
3650
  if (!match.match || match.fileType !== "env") continue;
3326
- const filePath = import_node_path13.default.join(serviceDir, entry.name);
3327
- const content = await import_node_fs12.promises.readFile(filePath, "utf8");
3651
+ const filePath = import_node_path15.default.join(serviceDir, entry.name);
3652
+ const content = await import_node_fs13.promises.readFile(filePath, "utf8");
3328
3653
  for (const line of content.split("\n")) {
3329
3654
  const parsed = parseDotenvLine(line);
3330
3655
  if (!parsed) continue;
@@ -3343,9 +3668,9 @@ var dotenvParser = { name: ".env", parse: parse2 };
3343
3668
 
3344
3669
  // src/extract/databases/prisma.ts
3345
3670
  init_cjs_shims();
3346
- var import_node_path14 = __toESM(require("path"), 1);
3671
+ var import_node_path16 = __toESM(require("path"), 1);
3347
3672
  async function parse3(serviceDir) {
3348
- const schemaPath = import_node_path14.default.join(serviceDir, "prisma", "schema.prisma");
3673
+ const schemaPath = import_node_path16.default.join(serviceDir, "prisma", "schema.prisma");
3349
3674
  const content = await readIfExists(schemaPath);
3350
3675
  if (!content) return [];
3351
3676
  const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
@@ -3477,10 +3802,10 @@ var knexParser = { name: "knex", parse: parse5 };
3477
3802
 
3478
3803
  // src/extract/databases/ormconfig.ts
3479
3804
  init_cjs_shims();
3480
- var import_node_path15 = __toESM(require("path"), 1);
3805
+ var import_node_path17 = __toESM(require("path"), 1);
3481
3806
  async function parse6(serviceDir) {
3482
3807
  for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
3483
- const abs = import_node_path15.default.join(serviceDir, candidate);
3808
+ const abs = import_node_path17.default.join(serviceDir, candidate);
3484
3809
  if (!await exists(abs)) continue;
3485
3810
  const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
3486
3811
  const entries = Array.isArray(raw) ? raw : [raw];
@@ -3540,9 +3865,9 @@ var typeormParser = { name: "typeorm", parse: parse7 };
3540
3865
 
3541
3866
  // src/extract/databases/sequelize.ts
3542
3867
  init_cjs_shims();
3543
- var import_node_path16 = __toESM(require("path"), 1);
3868
+ var import_node_path18 = __toESM(require("path"), 1);
3544
3869
  async function parse8(serviceDir) {
3545
- const configPath = import_node_path16.default.join(serviceDir, "config", "config.json");
3870
+ const configPath = import_node_path18.default.join(serviceDir, "config", "config.json");
3546
3871
  if (!await exists(configPath)) return [];
3547
3872
  const raw = await readJson(configPath);
3548
3873
  const out = [];
@@ -3569,7 +3894,7 @@ var sequelizeParser = { name: "sequelize", parse: parse8 };
3569
3894
 
3570
3895
  // src/extract/databases/docker-compose.ts
3571
3896
  init_cjs_shims();
3572
- var import_node_path17 = __toESM(require("path"), 1);
3897
+ var import_node_path19 = __toESM(require("path"), 1);
3573
3898
  function portFromService(svc) {
3574
3899
  for (const raw of svc.ports ?? []) {
3575
3900
  const str = String(raw);
@@ -3596,7 +3921,7 @@ function databaseFromEnv(svc) {
3596
3921
  }
3597
3922
  async function parse9(serviceDir) {
3598
3923
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
3599
- const abs = import_node_path17.default.join(serviceDir, name);
3924
+ const abs = import_node_path19.default.join(serviceDir, name);
3600
3925
  if (!await exists(abs)) continue;
3601
3926
  const raw = await readYaml(abs);
3602
3927
  if (!raw?.services) return [];
@@ -3637,8 +3962,8 @@ function compatibleDriversFor(engine) {
3637
3962
  }
3638
3963
  function toDatabaseNode(config) {
3639
3964
  return {
3640
- id: (0, import_types8.databaseId)(config.host),
3641
- type: import_types8.NodeType.DatabaseNode,
3965
+ id: (0, import_types9.databaseId)(config.host),
3966
+ type: import_types9.NodeType.DatabaseNode,
3642
3967
  name: config.database || config.host,
3643
3968
  engine: config.engine,
3644
3969
  engineVersion: config.engineVersion,
@@ -3768,7 +4093,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
3768
4093
  discoveredVia: mergedDiscoveredVia
3769
4094
  });
3770
4095
  }
3771
- const relConfigFile = toPosix2(import_node_path18.default.relative(service.dir, config.sourceFile));
4096
+ const relConfigFile = toPosix2(import_node_path20.default.relative(service.dir, config.sourceFile));
3772
4097
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
3773
4098
  graph,
3774
4099
  service.pkg.name,
@@ -3777,14 +4102,14 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
3777
4102
  );
3778
4103
  nodesAdded += fn;
3779
4104
  edgesAdded += fe;
3780
- const evidenceFile = toPosix2(import_node_path18.default.relative(scanPath, config.sourceFile));
4105
+ const evidenceFile = toPosix2(import_node_path20.default.relative(scanPath, config.sourceFile));
3781
4106
  const edge = {
3782
- id: (0, import_types4.extractedEdgeId)(fileNodeId, dbNode.id, import_types8.EdgeType.CONNECTS_TO),
4107
+ id: (0, import_types4.extractedEdgeId)(fileNodeId, dbNode.id, import_types9.EdgeType.CONNECTS_TO),
3783
4108
  source: fileNodeId,
3784
4109
  target: dbNode.id,
3785
- type: import_types8.EdgeType.CONNECTS_TO,
3786
- provenance: import_types8.Provenance.EXTRACTED,
3787
- confidence: (0, import_types8.confidenceForExtracted)("structural"),
4110
+ type: import_types9.EdgeType.CONNECTS_TO,
4111
+ provenance: import_types9.Provenance.EXTRACTED,
4112
+ confidence: (0, import_types9.confidenceForExtracted)("structural"),
3788
4113
  evidence: { file: evidenceFile }
3789
4114
  };
3790
4115
  if (!graph.hasEdge(edge.id)) {
@@ -3811,15 +4136,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
3811
4136
 
3812
4137
  // src/extract/configs.ts
3813
4138
  init_cjs_shims();
3814
- var import_node_fs13 = require("fs");
3815
- var import_node_path19 = __toESM(require("path"), 1);
3816
- var import_types9 = require("@neat.is/types");
4139
+ var import_node_fs14 = require("fs");
4140
+ var import_node_path21 = __toESM(require("path"), 1);
4141
+ var import_types10 = require("@neat.is/types");
3817
4142
  async function walkConfigFiles(dir) {
3818
4143
  const out = [];
3819
4144
  async function walk(current) {
3820
- const entries = await import_node_fs13.promises.readdir(current, { withFileTypes: true });
4145
+ const entries = await import_node_fs14.promises.readdir(current, { withFileTypes: true });
3821
4146
  for (const entry of entries) {
3822
- const full = import_node_path19.default.join(current, entry.name);
4147
+ const full = import_node_path21.default.join(current, entry.name);
3823
4148
  if (entry.isDirectory()) {
3824
4149
  if (IGNORED_DIRS.has(entry.name)) continue;
3825
4150
  if (await isPythonVenvDir(full)) continue;
@@ -3838,19 +4163,19 @@ async function addConfigNodes(graph, services, scanPath) {
3838
4163
  for (const service of services) {
3839
4164
  const configFiles = await walkConfigFiles(service.dir);
3840
4165
  for (const file of configFiles) {
3841
- const relPath = import_node_path19.default.relative(scanPath, file);
4166
+ const relPath = import_node_path21.default.relative(scanPath, file);
3842
4167
  const node = {
3843
- id: (0, import_types9.configId)(relPath),
3844
- type: import_types9.NodeType.ConfigNode,
3845
- name: import_node_path19.default.basename(file),
4168
+ id: (0, import_types10.configId)(relPath),
4169
+ type: import_types10.NodeType.ConfigNode,
4170
+ name: import_node_path21.default.basename(file),
3846
4171
  path: relPath,
3847
- fileType: isConfigFile(import_node_path19.default.basename(file)).fileType
4172
+ fileType: isConfigFile(import_node_path21.default.basename(file)).fileType
3848
4173
  };
3849
4174
  if (!graph.hasNode(node.id)) {
3850
4175
  graph.addNode(node.id, node);
3851
4176
  nodesAdded++;
3852
4177
  }
3853
- const relToService = toPosix2(import_node_path19.default.relative(service.dir, file));
4178
+ const relToService = toPosix2(import_node_path21.default.relative(service.dir, file));
3854
4179
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
3855
4180
  graph,
3856
4181
  service.pkg.name,
@@ -3860,13 +4185,13 @@ async function addConfigNodes(graph, services, scanPath) {
3860
4185
  nodesAdded += fn;
3861
4186
  edgesAdded += fe;
3862
4187
  const edge = {
3863
- id: (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types9.EdgeType.CONFIGURED_BY),
4188
+ id: (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types10.EdgeType.CONFIGURED_BY),
3864
4189
  source: fileNodeId,
3865
4190
  target: node.id,
3866
- type: import_types9.EdgeType.CONFIGURED_BY,
3867
- provenance: import_types9.Provenance.EXTRACTED,
3868
- confidence: (0, import_types9.confidenceForExtracted)("structural"),
3869
- evidence: { file: relPath.split(import_node_path19.default.sep).join("/") }
4191
+ type: import_types10.EdgeType.CONFIGURED_BY,
4192
+ provenance: import_types10.Provenance.EXTRACTED,
4193
+ confidence: (0, import_types10.confidenceForExtracted)("structural"),
4194
+ evidence: { file: relPath.split(import_node_path21.default.sep).join("/") }
3870
4195
  };
3871
4196
  if (!graph.hasEdge(edge.id)) {
3872
4197
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -3879,15 +4204,15 @@ async function addConfigNodes(graph, services, scanPath) {
3879
4204
 
3880
4205
  // src/extract/calls/index.ts
3881
4206
  init_cjs_shims();
3882
- var import_types15 = require("@neat.is/types");
4207
+ var import_types16 = require("@neat.is/types");
3883
4208
 
3884
4209
  // src/extract/calls/http.ts
3885
4210
  init_cjs_shims();
3886
- var import_node_path20 = __toESM(require("path"), 1);
3887
- var import_tree_sitter = __toESM(require("tree-sitter"), 1);
3888
- var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
3889
- var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
3890
- var import_types10 = require("@neat.is/types");
4211
+ var import_node_path22 = __toESM(require("path"), 1);
4212
+ var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
4213
+ var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
4214
+ var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
4215
+ var import_types11 = require("@neat.is/types");
3891
4216
  var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
3892
4217
  var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
3893
4218
  function isInsideJsxExternalLink(node) {
@@ -3915,14 +4240,14 @@ function collectStringLiterals(node, out) {
3915
4240
  if (child) collectStringLiterals(child, out);
3916
4241
  }
3917
4242
  }
3918
- var PARSE_CHUNK = 16384;
3919
- function parseSource(parser, source) {
4243
+ var PARSE_CHUNK2 = 16384;
4244
+ function parseSource2(parser, source) {
3920
4245
  return parser.parse(
3921
- (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK)
4246
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
3922
4247
  );
3923
4248
  }
3924
4249
  function callsFromSource(source, parser, knownHosts) {
3925
- const tree = parseSource(parser, source);
4250
+ const tree = parseSource2(parser, source);
3926
4251
  const literals = [];
3927
4252
  collectStringLiterals(tree.rootNode, literals);
3928
4253
  const out = [];
@@ -3936,25 +4261,25 @@ function callsFromSource(source, parser, knownHosts) {
3936
4261
  }
3937
4262
  return out;
3938
4263
  }
3939
- function makeJsParser() {
3940
- const p = new import_tree_sitter.default();
3941
- p.setLanguage(import_tree_sitter_javascript.default);
4264
+ function makeJsParser2() {
4265
+ const p = new import_tree_sitter2.default();
4266
+ p.setLanguage(import_tree_sitter_javascript2.default);
3942
4267
  return p;
3943
4268
  }
3944
- function makePyParser() {
3945
- const p = new import_tree_sitter.default();
3946
- p.setLanguage(import_tree_sitter_python.default);
4269
+ function makePyParser2() {
4270
+ const p = new import_tree_sitter2.default();
4271
+ p.setLanguage(import_tree_sitter_python2.default);
3947
4272
  return p;
3948
4273
  }
3949
4274
  async function addHttpCallEdges(graph, services) {
3950
- const jsParser = makeJsParser();
3951
- const pyParser = makePyParser();
4275
+ const jsParser = makeJsParser2();
4276
+ const pyParser = makePyParser2();
3952
4277
  const knownHosts = /* @__PURE__ */ new Set();
3953
4278
  const hostToNodeId = /* @__PURE__ */ new Map();
3954
4279
  for (const service of services) {
3955
- knownHosts.add(import_node_path20.default.basename(service.dir));
4280
+ knownHosts.add(import_node_path22.default.basename(service.dir));
3956
4281
  knownHosts.add(service.pkg.name);
3957
- hostToNodeId.set(import_node_path20.default.basename(service.dir), service.node.id);
4282
+ hostToNodeId.set(import_node_path22.default.basename(service.dir), service.node.id);
3958
4283
  hostToNodeId.set(service.pkg.name, service.node.id);
3959
4284
  }
3960
4285
  let nodesAdded = 0;
@@ -3964,7 +4289,7 @@ async function addHttpCallEdges(graph, services) {
3964
4289
  const seen = /* @__PURE__ */ new Set();
3965
4290
  for (const file of files) {
3966
4291
  if (isTestPath(file.path)) continue;
3967
- const parser = import_node_path20.default.extname(file.path) === ".py" ? pyParser : jsParser;
4292
+ const parser = import_node_path22.default.extname(file.path) === ".py" ? pyParser : jsParser;
3968
4293
  let sites;
3969
4294
  try {
3970
4295
  sites = callsFromSource(file.content, parser, knownHosts);
@@ -3973,14 +4298,14 @@ async function addHttpCallEdges(graph, services) {
3973
4298
  continue;
3974
4299
  }
3975
4300
  if (sites.length === 0) continue;
3976
- const relFile = toPosix2(import_node_path20.default.relative(service.dir, file.path));
4301
+ const relFile = toPosix2(import_node_path22.default.relative(service.dir, file.path));
3977
4302
  for (const site of sites) {
3978
4303
  const targetId = hostToNodeId.get(site.host);
3979
4304
  if (!targetId || targetId === service.node.id) continue;
3980
4305
  const dedupKey = `${relFile}|${targetId}`;
3981
4306
  if (seen.has(dedupKey)) continue;
3982
4307
  seen.add(dedupKey);
3983
- const confidence = (0, import_types10.confidenceForExtracted)("hostname-shape-match");
4308
+ const confidence = (0, import_types11.confidenceForExtracted)("hostname-shape-match");
3984
4309
  const ev = {
3985
4310
  file: relFile,
3986
4311
  line: site.line,
@@ -3994,25 +4319,25 @@ async function addHttpCallEdges(graph, services) {
3994
4319
  );
3995
4320
  nodesAdded += n;
3996
4321
  edgesAdded += e;
3997
- if (!(0, import_types10.passesExtractedFloor)(confidence)) {
4322
+ if (!(0, import_types11.passesExtractedFloor)(confidence)) {
3998
4323
  noteExtractedDropped({
3999
4324
  source: fileNodeId,
4000
4325
  target: targetId,
4001
- type: import_types10.EdgeType.CALLS,
4326
+ type: import_types11.EdgeType.CALLS,
4002
4327
  confidence,
4003
4328
  confidenceKind: "hostname-shape-match",
4004
4329
  evidence: ev
4005
4330
  });
4006
4331
  continue;
4007
4332
  }
4008
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types10.EdgeType.CALLS);
4333
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types11.EdgeType.CALLS);
4009
4334
  if (!graph.hasEdge(edgeId)) {
4010
4335
  const edge = {
4011
4336
  id: edgeId,
4012
4337
  source: fileNodeId,
4013
4338
  target: targetId,
4014
- type: import_types10.EdgeType.CALLS,
4015
- provenance: import_types10.Provenance.EXTRACTED,
4339
+ type: import_types11.EdgeType.CALLS,
4340
+ provenance: import_types11.Provenance.EXTRACTED,
4016
4341
  confidence,
4017
4342
  evidence: ev
4018
4343
  };
@@ -4027,8 +4352,8 @@ async function addHttpCallEdges(graph, services) {
4027
4352
 
4028
4353
  // src/extract/calls/kafka.ts
4029
4354
  init_cjs_shims();
4030
- var import_node_path21 = __toESM(require("path"), 1);
4031
- var import_types11 = require("@neat.is/types");
4355
+ var import_node_path23 = __toESM(require("path"), 1);
4356
+ var import_types12 = require("@neat.is/types");
4032
4357
  var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
4033
4358
  var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
4034
4359
  function findAll(re, text) {
@@ -4049,7 +4374,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
4049
4374
  seen.add(key);
4050
4375
  const line = lineOf(file.content, topic);
4051
4376
  out.push({
4052
- infraId: (0, import_types11.infraId)("kafka-topic", topic),
4377
+ infraId: (0, import_types12.infraId)("kafka-topic", topic),
4053
4378
  name: topic,
4054
4379
  kind: "kafka-topic",
4055
4380
  edgeType,
@@ -4058,7 +4383,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
4058
4383
  // tier (ADR-066).
4059
4384
  confidenceKind: "verified-call-site",
4060
4385
  evidence: {
4061
- file: import_node_path21.default.relative(serviceDir, file.path),
4386
+ file: import_node_path23.default.relative(serviceDir, file.path),
4062
4387
  line,
4063
4388
  snippet: snippet(file.content, line)
4064
4389
  }
@@ -4071,8 +4396,8 @@ function kafkaEndpointsFromFile(file, serviceDir) {
4071
4396
 
4072
4397
  // src/extract/calls/redis.ts
4073
4398
  init_cjs_shims();
4074
- var import_node_path22 = __toESM(require("path"), 1);
4075
- var import_types12 = require("@neat.is/types");
4399
+ var import_node_path24 = __toESM(require("path"), 1);
4400
+ var import_types13 = require("@neat.is/types");
4076
4401
  var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
4077
4402
  function redisEndpointsFromFile(file, serviceDir) {
4078
4403
  const out = [];
@@ -4085,7 +4410,7 @@ function redisEndpointsFromFile(file, serviceDir) {
4085
4410
  seen.add(host);
4086
4411
  const line = lineOf(file.content, host);
4087
4412
  out.push({
4088
- infraId: (0, import_types12.infraId)("redis", host),
4413
+ infraId: (0, import_types13.infraId)("redis", host),
4089
4414
  name: host,
4090
4415
  kind: "redis",
4091
4416
  edgeType: "CALLS",
@@ -4094,7 +4419,7 @@ function redisEndpointsFromFile(file, serviceDir) {
4094
4419
  // support tier (ADR-066).
4095
4420
  confidenceKind: "url-with-structural-support",
4096
4421
  evidence: {
4097
- file: import_node_path22.default.relative(serviceDir, file.path),
4422
+ file: import_node_path24.default.relative(serviceDir, file.path),
4098
4423
  line,
4099
4424
  snippet: snippet(file.content, line)
4100
4425
  }
@@ -4105,8 +4430,8 @@ function redisEndpointsFromFile(file, serviceDir) {
4105
4430
 
4106
4431
  // src/extract/calls/aws.ts
4107
4432
  init_cjs_shims();
4108
- var import_node_path23 = __toESM(require("path"), 1);
4109
- var import_types13 = require("@neat.is/types");
4433
+ var import_node_path25 = __toESM(require("path"), 1);
4434
+ var import_types14 = require("@neat.is/types");
4110
4435
  var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
4111
4436
  var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
4112
4437
  function hasMarker(text, markers) {
@@ -4130,7 +4455,7 @@ function awsEndpointsFromFile(file, serviceDir) {
4130
4455
  seen.add(key);
4131
4456
  const line = lineOf(file.content, name);
4132
4457
  out.push({
4133
- infraId: (0, import_types13.infraId)(kind, name),
4458
+ infraId: (0, import_types14.infraId)(kind, name),
4134
4459
  name,
4135
4460
  kind,
4136
4461
  edgeType: "CALLS",
@@ -4139,7 +4464,7 @@ function awsEndpointsFromFile(file, serviceDir) {
4139
4464
  // (ADR-066).
4140
4465
  confidenceKind: "verified-call-site",
4141
4466
  evidence: {
4142
- file: import_node_path23.default.relative(serviceDir, file.path),
4467
+ file: import_node_path25.default.relative(serviceDir, file.path),
4143
4468
  line,
4144
4469
  snippet: snippet(file.content, line)
4145
4470
  }
@@ -4164,8 +4489,8 @@ function awsEndpointsFromFile(file, serviceDir) {
4164
4489
 
4165
4490
  // src/extract/calls/grpc.ts
4166
4491
  init_cjs_shims();
4167
- var import_node_path24 = __toESM(require("path"), 1);
4168
- var import_types14 = require("@neat.is/types");
4492
+ var import_node_path26 = __toESM(require("path"), 1);
4493
+ var import_types15 = require("@neat.is/types");
4169
4494
  var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
4170
4495
  var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
4171
4496
  var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
@@ -4214,7 +4539,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
4214
4539
  const { kind } = classified;
4215
4540
  const line = lineOf(file.content, m[0]);
4216
4541
  out.push({
4217
- infraId: (0, import_types14.infraId)(kind, name),
4542
+ infraId: (0, import_types15.infraId)(kind, name),
4218
4543
  name,
4219
4544
  kind,
4220
4545
  edgeType: "CALLS",
@@ -4223,7 +4548,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
4223
4548
  // tier (ADR-066).
4224
4549
  confidenceKind: "verified-call-site",
4225
4550
  evidence: {
4226
- file: import_node_path24.default.relative(serviceDir, file.path),
4551
+ file: import_node_path26.default.relative(serviceDir, file.path),
4227
4552
  line,
4228
4553
  snippet: snippet(file.content, line)
4229
4554
  }
@@ -4236,11 +4561,11 @@ function grpcEndpointsFromFile(file, serviceDir) {
4236
4561
  function edgeTypeFromEndpoint(ep) {
4237
4562
  switch (ep.edgeType) {
4238
4563
  case "PUBLISHES_TO":
4239
- return import_types15.EdgeType.PUBLISHES_TO;
4564
+ return import_types16.EdgeType.PUBLISHES_TO;
4240
4565
  case "CONSUMES_FROM":
4241
- return import_types15.EdgeType.CONSUMES_FROM;
4566
+ return import_types16.EdgeType.CONSUMES_FROM;
4242
4567
  default:
4243
- return import_types15.EdgeType.CALLS;
4568
+ return import_types16.EdgeType.CALLS;
4244
4569
  }
4245
4570
  }
4246
4571
  function isAwsKind(kind) {
@@ -4267,7 +4592,7 @@ async function addExternalEndpointEdges(graph, services) {
4267
4592
  if (!graph.hasNode(ep.infraId)) {
4268
4593
  const node = {
4269
4594
  id: ep.infraId,
4270
- type: import_types15.NodeType.InfraNode,
4595
+ type: import_types16.NodeType.InfraNode,
4271
4596
  name: ep.name,
4272
4597
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
4273
4598
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -4279,7 +4604,7 @@ async function addExternalEndpointEdges(graph, services) {
4279
4604
  nodesAdded++;
4280
4605
  }
4281
4606
  const edgeType = edgeTypeFromEndpoint(ep);
4282
- const confidence = (0, import_types15.confidenceForExtracted)(ep.confidenceKind);
4607
+ const confidence = (0, import_types16.confidenceForExtracted)(ep.confidenceKind);
4283
4608
  const relFile = toPosix2(ep.evidence.file);
4284
4609
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
4285
4610
  graph,
@@ -4289,7 +4614,7 @@ async function addExternalEndpointEdges(graph, services) {
4289
4614
  );
4290
4615
  nodesAdded += n;
4291
4616
  edgesAdded += e;
4292
- if (!(0, import_types15.passesExtractedFloor)(confidence)) {
4617
+ if (!(0, import_types16.passesExtractedFloor)(confidence)) {
4293
4618
  noteExtractedDropped({
4294
4619
  source: fileNodeId,
4295
4620
  target: ep.infraId,
@@ -4309,7 +4634,7 @@ async function addExternalEndpointEdges(graph, services) {
4309
4634
  source: fileNodeId,
4310
4635
  target: ep.infraId,
4311
4636
  type: edgeType,
4312
- provenance: import_types15.Provenance.EXTRACTED,
4637
+ provenance: import_types16.Provenance.EXTRACTED,
4313
4638
  confidence,
4314
4639
  evidence: ep.evidence
4315
4640
  };
@@ -4334,16 +4659,16 @@ init_cjs_shims();
4334
4659
 
4335
4660
  // src/extract/infra/docker-compose.ts
4336
4661
  init_cjs_shims();
4337
- var import_node_path25 = __toESM(require("path"), 1);
4338
- var import_types17 = require("@neat.is/types");
4662
+ var import_node_path27 = __toESM(require("path"), 1);
4663
+ var import_types18 = require("@neat.is/types");
4339
4664
 
4340
4665
  // src/extract/infra/shared.ts
4341
4666
  init_cjs_shims();
4342
- var import_types16 = require("@neat.is/types");
4667
+ var import_types17 = require("@neat.is/types");
4343
4668
  function makeInfraNode(kind, name, provider = "self", extras) {
4344
4669
  return {
4345
- id: (0, import_types16.infraId)(kind, name),
4346
- type: import_types16.NodeType.InfraNode,
4670
+ id: (0, import_types17.infraId)(kind, name),
4671
+ type: import_types17.NodeType.InfraNode,
4347
4672
  name,
4348
4673
  provider,
4349
4674
  kind,
@@ -4372,7 +4697,7 @@ function dependsOnList(value) {
4372
4697
  }
4373
4698
  function serviceNameToServiceNode(name, services) {
4374
4699
  for (const s of services) {
4375
- if (s.node.name === name || import_node_path25.default.basename(s.dir) === name) return s.node.id;
4700
+ if (s.node.name === name || import_node_path27.default.basename(s.dir) === name) return s.node.id;
4376
4701
  }
4377
4702
  return null;
4378
4703
  }
@@ -4381,7 +4706,7 @@ async function addComposeInfra(graph, scanPath, services) {
4381
4706
  let edgesAdded = 0;
4382
4707
  let composePath = null;
4383
4708
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
4384
- const abs = import_node_path25.default.join(scanPath, name);
4709
+ const abs = import_node_path27.default.join(scanPath, name);
4385
4710
  if (await exists(abs)) {
4386
4711
  composePath = abs;
4387
4712
  break;
@@ -4394,13 +4719,13 @@ async function addComposeInfra(graph, scanPath, services) {
4394
4719
  } catch (err) {
4395
4720
  recordExtractionError(
4396
4721
  "infra docker-compose",
4397
- import_node_path25.default.relative(scanPath, composePath),
4722
+ import_node_path27.default.relative(scanPath, composePath),
4398
4723
  err
4399
4724
  );
4400
4725
  return { nodesAdded, edgesAdded };
4401
4726
  }
4402
4727
  if (!compose?.services) return { nodesAdded, edgesAdded };
4403
- const evidenceFile = import_node_path25.default.relative(scanPath, composePath).split(import_node_path25.default.sep).join("/");
4728
+ const evidenceFile = import_node_path27.default.relative(scanPath, composePath).split(import_node_path27.default.sep).join("/");
4404
4729
  const composeNameToNodeId = /* @__PURE__ */ new Map();
4405
4730
  for (const [composeName, svc] of Object.entries(compose.services)) {
4406
4731
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -4422,15 +4747,15 @@ async function addComposeInfra(graph, scanPath, services) {
4422
4747
  for (const dep of dependsOnList(svc.depends_on)) {
4423
4748
  const targetId = composeNameToNodeId.get(dep);
4424
4749
  if (!targetId) continue;
4425
- const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types17.EdgeType.DEPENDS_ON);
4750
+ const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types18.EdgeType.DEPENDS_ON);
4426
4751
  if (graph.hasEdge(edgeId)) continue;
4427
4752
  const edge = {
4428
4753
  id: edgeId,
4429
4754
  source: sourceId,
4430
4755
  target: targetId,
4431
- type: import_types17.EdgeType.DEPENDS_ON,
4432
- provenance: import_types17.Provenance.EXTRACTED,
4433
- confidence: (0, import_types17.confidenceForExtracted)("structural"),
4756
+ type: import_types18.EdgeType.DEPENDS_ON,
4757
+ provenance: import_types18.Provenance.EXTRACTED,
4758
+ confidence: (0, import_types18.confidenceForExtracted)("structural"),
4434
4759
  evidence: { file: evidenceFile }
4435
4760
  };
4436
4761
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -4442,9 +4767,9 @@ async function addComposeInfra(graph, scanPath, services) {
4442
4767
 
4443
4768
  // src/extract/infra/dockerfile.ts
4444
4769
  init_cjs_shims();
4445
- var import_node_path26 = __toESM(require("path"), 1);
4446
- var import_node_fs14 = require("fs");
4447
- var import_types18 = require("@neat.is/types");
4770
+ var import_node_path28 = __toESM(require("path"), 1);
4771
+ var import_node_fs15 = require("fs");
4772
+ var import_types19 = require("@neat.is/types");
4448
4773
  function runtimeImage(content) {
4449
4774
  const lines = content.split("\n");
4450
4775
  let last = null;
@@ -4463,15 +4788,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4463
4788
  let nodesAdded = 0;
4464
4789
  let edgesAdded = 0;
4465
4790
  for (const service of services) {
4466
- const dockerfilePath = import_node_path26.default.join(service.dir, "Dockerfile");
4791
+ const dockerfilePath = import_node_path28.default.join(service.dir, "Dockerfile");
4467
4792
  if (!await exists(dockerfilePath)) continue;
4468
4793
  let content;
4469
4794
  try {
4470
- content = await import_node_fs14.promises.readFile(dockerfilePath, "utf8");
4795
+ content = await import_node_fs15.promises.readFile(dockerfilePath, "utf8");
4471
4796
  } catch (err) {
4472
4797
  recordExtractionError(
4473
4798
  "infra dockerfile",
4474
- import_node_path26.default.relative(scanPath, dockerfilePath),
4799
+ import_node_path28.default.relative(scanPath, dockerfilePath),
4475
4800
  err
4476
4801
  );
4477
4802
  continue;
@@ -4483,7 +4808,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4483
4808
  graph.addNode(node.id, node);
4484
4809
  nodesAdded++;
4485
4810
  }
4486
- const relDockerfile = toPosix2(import_node_path26.default.relative(service.dir, dockerfilePath));
4811
+ const relDockerfile = toPosix2(import_node_path28.default.relative(service.dir, dockerfilePath));
4487
4812
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
4488
4813
  graph,
4489
4814
  service.pkg.name,
@@ -4492,17 +4817,17 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4492
4817
  );
4493
4818
  nodesAdded += fn;
4494
4819
  edgesAdded += fe;
4495
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types18.EdgeType.RUNS_ON);
4820
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types19.EdgeType.RUNS_ON);
4496
4821
  if (!graph.hasEdge(edgeId)) {
4497
4822
  const edge = {
4498
4823
  id: edgeId,
4499
4824
  source: fileNodeId,
4500
4825
  target: node.id,
4501
- type: import_types18.EdgeType.RUNS_ON,
4502
- provenance: import_types18.Provenance.EXTRACTED,
4503
- confidence: (0, import_types18.confidenceForExtracted)("structural"),
4826
+ type: import_types19.EdgeType.RUNS_ON,
4827
+ provenance: import_types19.Provenance.EXTRACTED,
4828
+ confidence: (0, import_types19.confidenceForExtracted)("structural"),
4504
4829
  evidence: {
4505
- file: toPosix2(import_node_path26.default.relative(scanPath, dockerfilePath))
4830
+ file: toPosix2(import_node_path28.default.relative(scanPath, dockerfilePath))
4506
4831
  }
4507
4832
  };
4508
4833
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -4514,21 +4839,21 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4514
4839
 
4515
4840
  // src/extract/infra/terraform.ts
4516
4841
  init_cjs_shims();
4517
- var import_node_fs15 = require("fs");
4518
- var import_node_path27 = __toESM(require("path"), 1);
4842
+ var import_node_fs16 = require("fs");
4843
+ var import_node_path29 = __toESM(require("path"), 1);
4519
4844
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
4520
4845
  async function walkTfFiles(start, depth = 0, max = 5) {
4521
4846
  if (depth > max) return [];
4522
4847
  const out = [];
4523
- const entries = await import_node_fs15.promises.readdir(start, { withFileTypes: true }).catch(() => []);
4848
+ const entries = await import_node_fs16.promises.readdir(start, { withFileTypes: true }).catch(() => []);
4524
4849
  for (const entry of entries) {
4525
4850
  if (entry.isDirectory()) {
4526
4851
  if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
4527
- const child = import_node_path27.default.join(start, entry.name);
4852
+ const child = import_node_path29.default.join(start, entry.name);
4528
4853
  if (await isPythonVenvDir(child)) continue;
4529
4854
  out.push(...await walkTfFiles(child, depth + 1, max));
4530
4855
  } else if (entry.isFile() && entry.name.endsWith(".tf")) {
4531
- out.push(import_node_path27.default.join(start, entry.name));
4856
+ out.push(import_node_path29.default.join(start, entry.name));
4532
4857
  }
4533
4858
  }
4534
4859
  return out;
@@ -4537,7 +4862,7 @@ async function addTerraformResources(graph, scanPath) {
4537
4862
  let nodesAdded = 0;
4538
4863
  const files = await walkTfFiles(scanPath);
4539
4864
  for (const file of files) {
4540
- const content = await import_node_fs15.promises.readFile(file, "utf8");
4865
+ const content = await import_node_fs16.promises.readFile(file, "utf8");
4541
4866
  RESOURCE_RE.lastIndex = 0;
4542
4867
  let m;
4543
4868
  while ((m = RESOURCE_RE.exec(content)) !== null) {
@@ -4555,8 +4880,8 @@ async function addTerraformResources(graph, scanPath) {
4555
4880
 
4556
4881
  // src/extract/infra/k8s.ts
4557
4882
  init_cjs_shims();
4558
- var import_node_fs16 = require("fs");
4559
- var import_node_path28 = __toESM(require("path"), 1);
4883
+ var import_node_fs17 = require("fs");
4884
+ var import_node_path30 = __toESM(require("path"), 1);
4560
4885
  var import_yaml3 = require("yaml");
4561
4886
  var K8S_KIND_TO_INFRA_KIND = {
4562
4887
  Service: "k8s-service",
@@ -4570,15 +4895,15 @@ var K8S_KIND_TO_INFRA_KIND = {
4570
4895
  async function walkYamlFiles2(start, depth = 0, max = 5) {
4571
4896
  if (depth > max) return [];
4572
4897
  const out = [];
4573
- const entries = await import_node_fs16.promises.readdir(start, { withFileTypes: true }).catch(() => []);
4898
+ const entries = await import_node_fs17.promises.readdir(start, { withFileTypes: true }).catch(() => []);
4574
4899
  for (const entry of entries) {
4575
4900
  if (entry.isDirectory()) {
4576
4901
  if (IGNORED_DIRS.has(entry.name)) continue;
4577
- const child = import_node_path28.default.join(start, entry.name);
4902
+ const child = import_node_path30.default.join(start, entry.name);
4578
4903
  if (await isPythonVenvDir(child)) continue;
4579
4904
  out.push(...await walkYamlFiles2(child, depth + 1, max));
4580
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path28.default.extname(entry.name))) {
4581
- out.push(import_node_path28.default.join(start, entry.name));
4905
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path30.default.extname(entry.name))) {
4906
+ out.push(import_node_path30.default.join(start, entry.name));
4582
4907
  }
4583
4908
  }
4584
4909
  return out;
@@ -4587,7 +4912,7 @@ async function addK8sResources(graph, scanPath) {
4587
4912
  let nodesAdded = 0;
4588
4913
  const files = await walkYamlFiles2(scanPath);
4589
4914
  for (const file of files) {
4590
- const content = await import_node_fs16.promises.readFile(file, "utf8");
4915
+ const content = await import_node_fs17.promises.readFile(file, "utf8");
4591
4916
  let docs;
4592
4917
  try {
4593
4918
  docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
@@ -4622,17 +4947,17 @@ async function addInfra(graph, scanPath, services) {
4622
4947
  }
4623
4948
 
4624
4949
  // src/extract/index.ts
4625
- var import_node_path30 = __toESM(require("path"), 1);
4950
+ var import_node_path32 = __toESM(require("path"), 1);
4626
4951
 
4627
4952
  // src/extract/retire.ts
4628
4953
  init_cjs_shims();
4629
- var import_node_fs17 = require("fs");
4630
- var import_node_path29 = __toESM(require("path"), 1);
4631
- var import_types19 = require("@neat.is/types");
4954
+ var import_node_fs18 = require("fs");
4955
+ var import_node_path31 = __toESM(require("path"), 1);
4956
+ var import_types20 = require("@neat.is/types");
4632
4957
  function dropOrphanedFileNodes(graph) {
4633
4958
  const orphans = [];
4634
4959
  graph.forEachNode((id, attrs) => {
4635
- if (attrs.type !== import_types19.NodeType.FileNode) return;
4960
+ if (attrs.type !== import_types20.NodeType.FileNode) return;
4636
4961
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
4637
4962
  orphans.push(id);
4638
4963
  }
@@ -4645,14 +4970,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
4645
4970
  const bases = [scanPath, ...serviceDirs];
4646
4971
  graph.forEachEdge((id, attrs) => {
4647
4972
  const edge = attrs;
4648
- if (edge.provenance !== import_types19.Provenance.EXTRACTED) return;
4973
+ if (edge.provenance !== import_types20.Provenance.EXTRACTED) return;
4649
4974
  const evidenceFile = edge.evidence?.file;
4650
4975
  if (!evidenceFile) return;
4651
- if (import_node_path29.default.isAbsolute(evidenceFile)) {
4652
- if (!(0, import_node_fs17.existsSync)(evidenceFile)) toDrop.push(id);
4976
+ if (import_node_path31.default.isAbsolute(evidenceFile)) {
4977
+ if (!(0, import_node_fs18.existsSync)(evidenceFile)) toDrop.push(id);
4653
4978
  return;
4654
4979
  }
4655
- const found = bases.some((base) => (0, import_node_fs17.existsSync)(import_node_path29.default.join(base, evidenceFile)));
4980
+ const found = bases.some((base) => (0, import_node_fs18.existsSync)(import_node_path31.default.join(base, evidenceFile)));
4656
4981
  if (!found) toDrop.push(id);
4657
4982
  });
4658
4983
  for (const id of toDrop) graph.dropEdge(id);
@@ -4667,6 +4992,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
4667
4992
  const services = await discoverServices(scanPath);
4668
4993
  const phase1Nodes = addServiceNodes(graph, services);
4669
4994
  await addServiceAliases(graph, scanPath, services);
4995
+ const fileEnum = await addFiles(graph, services);
4996
+ const importGraph = await addImports(graph, services);
4670
4997
  const phase2 = await addDatabasesAndCompat(graph, services, scanPath);
4671
4998
  const phase3 = await addConfigNodes(graph, services, scanPath);
4672
4999
  const phase4 = await addCallEdges(graph, services);
@@ -4690,7 +5017,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
4690
5017
  }
4691
5018
  const droppedEntries = drainDroppedExtracted();
4692
5019
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
4693
- const rejectedPath = import_node_path30.default.join(import_node_path30.default.dirname(opts.errorsPath), "rejected.ndjson");
5020
+ const rejectedPath = import_node_path32.default.join(import_node_path32.default.dirname(opts.errorsPath), "rejected.ndjson");
4694
5021
  try {
4695
5022
  await writeRejectedExtracted(droppedEntries, rejectedPath);
4696
5023
  } catch (err) {
@@ -4700,8 +5027,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
4700
5027
  }
4701
5028
  }
4702
5029
  const result = {
4703
- nodesAdded: phase1Nodes + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
4704
- edgesAdded: phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
5030
+ nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
5031
+ edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
4705
5032
  frontiersPromoted,
4706
5033
  extractionErrors: errorEntries.length,
4707
5034
  errorEntries,
@@ -4724,9 +5051,9 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
4724
5051
 
4725
5052
  // src/persist.ts
4726
5053
  init_cjs_shims();
4727
- var import_node_fs18 = require("fs");
4728
- var import_node_path31 = __toESM(require("path"), 1);
4729
- var import_types20 = require("@neat.is/types");
5054
+ var import_node_fs19 = require("fs");
5055
+ var import_node_path33 = __toESM(require("path"), 1);
5056
+ var import_types21 = require("@neat.is/types");
4730
5057
  var SCHEMA_VERSION = 4;
4731
5058
  function migrateV1ToV2(payload) {
4732
5059
  const nodes = payload.graph.nodes;
@@ -4748,12 +5075,12 @@ function migrateV2ToV3(payload) {
4748
5075
  for (const edge of edges) {
4749
5076
  const attrs = edge.attributes;
4750
5077
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
4751
- attrs.provenance = import_types20.Provenance.OBSERVED;
5078
+ attrs.provenance = import_types21.Provenance.OBSERVED;
4752
5079
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
4753
5080
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
4754
5081
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
4755
5082
  if (type && source && target) {
4756
- const newId = (0, import_types20.observedEdgeId)(source, target, type);
5083
+ const newId = (0, import_types21.observedEdgeId)(source, target, type);
4757
5084
  attrs.id = newId;
4758
5085
  if (edge.key) edge.key = newId;
4759
5086
  }
@@ -4762,7 +5089,7 @@ function migrateV2ToV3(payload) {
4762
5089
  return { ...payload, schemaVersion: 3 };
4763
5090
  }
4764
5091
  async function ensureDir(filePath) {
4765
- await import_node_fs18.promises.mkdir(import_node_path31.default.dirname(filePath), { recursive: true });
5092
+ await import_node_fs19.promises.mkdir(import_node_path33.default.dirname(filePath), { recursive: true });
4766
5093
  }
4767
5094
  async function saveGraphToDisk(graph, outPath) {
4768
5095
  await ensureDir(outPath);
@@ -4772,13 +5099,13 @@ async function saveGraphToDisk(graph, outPath) {
4772
5099
  graph: graph.export()
4773
5100
  };
4774
5101
  const tmp = `${outPath}.tmp`;
4775
- await import_node_fs18.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
4776
- await import_node_fs18.promises.rename(tmp, outPath);
5102
+ await import_node_fs19.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
5103
+ await import_node_fs19.promises.rename(tmp, outPath);
4777
5104
  }
4778
5105
  async function loadGraphFromDisk(graph, outPath) {
4779
5106
  let raw;
4780
5107
  try {
4781
- raw = await import_node_fs18.promises.readFile(outPath, "utf8");
5108
+ raw = await import_node_fs19.promises.readFile(outPath, "utf8");
4782
5109
  } catch (err) {
4783
5110
  if (err.code === "ENOENT") return;
4784
5111
  throw err;
@@ -4839,19 +5166,19 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
4839
5166
  init_cjs_shims();
4840
5167
  var import_fastify = __toESM(require("fastify"), 1);
4841
5168
  var import_cors = __toESM(require("@fastify/cors"), 1);
4842
- var import_types23 = require("@neat.is/types");
5169
+ var import_types24 = require("@neat.is/types");
4843
5170
 
4844
5171
  // src/extend/index.ts
4845
5172
  init_cjs_shims();
4846
- var import_node_fs20 = require("fs");
4847
- var import_node_path33 = __toESM(require("path"), 1);
5173
+ var import_node_fs21 = require("fs");
5174
+ var import_node_path35 = __toESM(require("path"), 1);
4848
5175
  var import_node_os2 = __toESM(require("os"), 1);
4849
5176
  var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
4850
5177
 
4851
5178
  // src/installers/package-manager.ts
4852
5179
  init_cjs_shims();
4853
- var import_node_fs19 = require("fs");
4854
- var import_node_path32 = __toESM(require("path"), 1);
5180
+ var import_node_fs20 = require("fs");
5181
+ var import_node_path34 = __toESM(require("path"), 1);
4855
5182
  var import_node_child_process = require("child_process");
4856
5183
  var LOCKFILE_PRIORITY = [
4857
5184
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -4866,29 +5193,29 @@ var LOCKFILE_PRIORITY = [
4866
5193
  var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
4867
5194
  async function exists2(p) {
4868
5195
  try {
4869
- await import_node_fs19.promises.access(p);
5196
+ await import_node_fs20.promises.access(p);
4870
5197
  return true;
4871
5198
  } catch {
4872
5199
  return false;
4873
5200
  }
4874
5201
  }
4875
5202
  async function detectPackageManager(serviceDir) {
4876
- let dir = import_node_path32.default.resolve(serviceDir);
5203
+ let dir = import_node_path34.default.resolve(serviceDir);
4877
5204
  const stops = /* @__PURE__ */ new Set();
4878
5205
  for (let i = 0; i < 64; i++) {
4879
5206
  if (stops.has(dir)) break;
4880
5207
  stops.add(dir);
4881
5208
  for (const candidate of LOCKFILE_PRIORITY) {
4882
- const lockPath = import_node_path32.default.join(dir, candidate.lockfile);
5209
+ const lockPath = import_node_path34.default.join(dir, candidate.lockfile);
4883
5210
  if (await exists2(lockPath)) {
4884
5211
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
4885
5212
  }
4886
5213
  }
4887
- const parent = import_node_path32.default.dirname(dir);
5214
+ const parent = import_node_path34.default.dirname(dir);
4888
5215
  if (parent === dir) break;
4889
5216
  dir = parent;
4890
5217
  }
4891
- return { pm: "npm", cwd: import_node_path32.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
5218
+ return { pm: "npm", cwd: import_node_path34.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
4892
5219
  }
4893
5220
  async function runPackageManagerInstall(cmd) {
4894
5221
  return new Promise((resolve) => {
@@ -4928,32 +5255,32 @@ ${err.message}`
4928
5255
  }
4929
5256
 
4930
5257
  // src/extend/index.ts
4931
- async function fileExists(p) {
5258
+ async function fileExists2(p) {
4932
5259
  try {
4933
- await import_node_fs20.promises.access(p);
5260
+ await import_node_fs21.promises.access(p);
4934
5261
  return true;
4935
5262
  } catch {
4936
5263
  return false;
4937
5264
  }
4938
5265
  }
4939
5266
  async function readPackageJson(scanPath) {
4940
- const pkgPath = import_node_path33.default.join(scanPath, "package.json");
4941
- const raw = await import_node_fs20.promises.readFile(pkgPath, "utf8");
5267
+ const pkgPath = import_node_path35.default.join(scanPath, "package.json");
5268
+ const raw = await import_node_fs21.promises.readFile(pkgPath, "utf8");
4942
5269
  return JSON.parse(raw);
4943
5270
  }
4944
5271
  async function findHookFiles(scanPath) {
4945
- const entries = await import_node_fs20.promises.readdir(scanPath);
5272
+ const entries = await import_node_fs21.promises.readdir(scanPath);
4946
5273
  return entries.filter(
4947
5274
  (e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
4948
5275
  ).sort();
4949
5276
  }
4950
5277
  function extendLogPath() {
4951
- return process.env.NEAT_EXTEND_LOG ?? import_node_path33.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
5278
+ return process.env.NEAT_EXTEND_LOG ?? import_node_path35.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
4952
5279
  }
4953
5280
  async function appendExtendLog(entry) {
4954
5281
  const logPath = extendLogPath();
4955
- await import_node_fs20.promises.mkdir(import_node_path33.default.dirname(logPath), { recursive: true });
4956
- await import_node_fs20.promises.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
5282
+ await import_node_fs21.promises.mkdir(import_node_path35.default.dirname(logPath), { recursive: true });
5283
+ await import_node_fs21.promises.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
4957
5284
  }
4958
5285
  function splicedContent(fileContent, snippet2) {
4959
5286
  if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
@@ -5011,7 +5338,7 @@ function lookupInstrumentation(library, installedVersion) {
5011
5338
  }
5012
5339
  async function describeProjectInstrumentation(ctx) {
5013
5340
  const hookFiles = await findHookFiles(ctx.scanPath);
5014
- const envNeat = await fileExists(import_node_path33.default.join(ctx.scanPath, ".env.neat"));
5341
+ const envNeat = await fileExists2(import_node_path35.default.join(ctx.scanPath, ".env.neat"));
5015
5342
  const registryInstrPackages = new Set(
5016
5343
  (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
5017
5344
  );
@@ -5033,31 +5360,31 @@ async function applyExtension(ctx, args, options) {
5033
5360
  );
5034
5361
  }
5035
5362
  for (const file of hookFiles) {
5036
- const content = await import_node_fs20.promises.readFile(import_node_path33.default.join(ctx.scanPath, file), "utf8");
5363
+ const content = await import_node_fs21.promises.readFile(import_node_path35.default.join(ctx.scanPath, file), "utf8");
5037
5364
  if (content.includes(args.registration_snippet)) {
5038
5365
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
5039
5366
  }
5040
5367
  }
5041
5368
  const primaryFile = hookFiles[0];
5042
- const primaryPath = import_node_path33.default.join(ctx.scanPath, primaryFile);
5369
+ const primaryPath = import_node_path35.default.join(ctx.scanPath, primaryFile);
5043
5370
  const filesTouched = [];
5044
5371
  const depsAdded = [];
5045
- const pkgPath = import_node_path33.default.join(ctx.scanPath, "package.json");
5372
+ const pkgPath = import_node_path35.default.join(ctx.scanPath, "package.json");
5046
5373
  const pkg = await readPackageJson(ctx.scanPath);
5047
5374
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
5048
5375
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
5049
- await import_node_fs20.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
5376
+ await import_node_fs21.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
5050
5377
  filesTouched.push("package.json");
5051
5378
  depsAdded.push(`${args.instrumentation_package}@${args.version}`);
5052
5379
  }
5053
- const hookContent = await import_node_fs20.promises.readFile(primaryPath, "utf8");
5380
+ const hookContent = await import_node_fs21.promises.readFile(primaryPath, "utf8");
5054
5381
  const patched = splicedContent(hookContent, args.registration_snippet);
5055
5382
  if (!patched) {
5056
5383
  throw new Error(
5057
5384
  `Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
5058
5385
  );
5059
5386
  }
5060
- await import_node_fs20.promises.writeFile(primaryPath, patched, "utf8");
5387
+ await import_node_fs21.promises.writeFile(primaryPath, patched, "utf8");
5061
5388
  filesTouched.push(primaryFile);
5062
5389
  const cmd = await detectPackageManager(ctx.scanPath);
5063
5390
  const installer = options?.runInstall ?? runPackageManagerInstall;
@@ -5088,7 +5415,7 @@ async function dryRunExtension(ctx, args) {
5088
5415
  };
5089
5416
  }
5090
5417
  for (const file of hookFiles) {
5091
- const content = await import_node_fs20.promises.readFile(import_node_path33.default.join(ctx.scanPath, file), "utf8");
5418
+ const content = await import_node_fs21.promises.readFile(import_node_path35.default.join(ctx.scanPath, file), "utf8");
5092
5419
  if (content.includes(args.registration_snippet)) {
5093
5420
  return {
5094
5421
  library: args.library,
@@ -5110,7 +5437,7 @@ async function dryRunExtension(ctx, args) {
5110
5437
  depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
5111
5438
  filesTouched.push("package.json");
5112
5439
  }
5113
- const hookContent = await import_node_fs20.promises.readFile(import_node_path33.default.join(ctx.scanPath, primaryFile), "utf8");
5440
+ const hookContent = await import_node_fs21.promises.readFile(import_node_path35.default.join(ctx.scanPath, primaryFile), "utf8");
5114
5441
  const patched = splicedContent(hookContent, args.registration_snippet);
5115
5442
  if (patched) {
5116
5443
  filesTouched.push(primaryFile);
@@ -5122,31 +5449,31 @@ async function dryRunExtension(ctx, args) {
5122
5449
  }
5123
5450
  async function rollbackExtension(ctx, args) {
5124
5451
  const logPath = extendLogPath();
5125
- if (!await fileExists(logPath)) {
5452
+ if (!await fileExists2(logPath)) {
5126
5453
  return { undone: false, message: "no apply found for library" };
5127
5454
  }
5128
- const raw = await import_node_fs20.promises.readFile(logPath, "utf8");
5455
+ const raw = await import_node_fs21.promises.readFile(logPath, "utf8");
5129
5456
  const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
5130
5457
  const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
5131
5458
  if (!match) {
5132
5459
  return { undone: false, message: "no apply found for library" };
5133
5460
  }
5134
- const pkgPath = import_node_path33.default.join(ctx.scanPath, "package.json");
5135
- if (await fileExists(pkgPath)) {
5461
+ const pkgPath = import_node_path35.default.join(ctx.scanPath, "package.json");
5462
+ if (await fileExists2(pkgPath)) {
5136
5463
  const pkg = await readPackageJson(ctx.scanPath);
5137
5464
  if (pkg.dependencies?.[match.instrumentation_package]) {
5138
5465
  const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
5139
5466
  pkg.dependencies = rest;
5140
- await import_node_fs20.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
5467
+ await import_node_fs21.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
5141
5468
  }
5142
5469
  }
5143
5470
  const hookFiles = await findHookFiles(ctx.scanPath);
5144
5471
  for (const file of hookFiles) {
5145
- const filePath = import_node_path33.default.join(ctx.scanPath, file);
5146
- const content = await import_node_fs20.promises.readFile(filePath, "utf8");
5472
+ const filePath = import_node_path35.default.join(ctx.scanPath, file);
5473
+ const content = await import_node_fs21.promises.readFile(filePath, "utf8");
5147
5474
  if (content.includes(match.registration_snippet)) {
5148
5475
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
5149
- await import_node_fs20.promises.writeFile(filePath, filtered, "utf8");
5476
+ await import_node_fs21.promises.writeFile(filePath, filtered, "utf8");
5150
5477
  break;
5151
5478
  }
5152
5479
  }
@@ -5158,7 +5485,7 @@ async function rollbackExtension(ctx, args) {
5158
5485
 
5159
5486
  // src/divergences.ts
5160
5487
  init_cjs_shims();
5161
- var import_types21 = require("@neat.is/types");
5488
+ var import_types22 = require("@neat.is/types");
5162
5489
  function bucketKey(source, target, type) {
5163
5490
  return `${type}|${source}|${target}`;
5164
5491
  }
@@ -5166,22 +5493,22 @@ function bucketEdges(graph) {
5166
5493
  const buckets = /* @__PURE__ */ new Map();
5167
5494
  graph.forEachEdge((id, attrs) => {
5168
5495
  const e = attrs;
5169
- const parsed = (0, import_types21.parseEdgeId)(id);
5496
+ const parsed = (0, import_types22.parseEdgeId)(id);
5170
5497
  const provenance = parsed?.provenance ?? e.provenance;
5171
5498
  const key = bucketKey(e.source, e.target, e.type);
5172
5499
  const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
5173
5500
  switch (provenance) {
5174
- case import_types21.Provenance.EXTRACTED:
5501
+ case import_types22.Provenance.EXTRACTED:
5175
5502
  cur.extracted = e;
5176
5503
  break;
5177
- case import_types21.Provenance.OBSERVED:
5504
+ case import_types22.Provenance.OBSERVED:
5178
5505
  cur.observed = e;
5179
5506
  break;
5180
- case import_types21.Provenance.INFERRED:
5507
+ case import_types22.Provenance.INFERRED:
5181
5508
  cur.inferred = e;
5182
5509
  break;
5183
5510
  default:
5184
- if (e.provenance === import_types21.Provenance.STALE) cur.stale = e;
5511
+ if (e.provenance === import_types22.Provenance.STALE) cur.stale = e;
5185
5512
  }
5186
5513
  buckets.set(key, cur);
5187
5514
  });
@@ -5190,7 +5517,7 @@ function bucketEdges(graph) {
5190
5517
  function nodeIsFrontier(graph, nodeId) {
5191
5518
  if (!graph.hasNode(nodeId)) return false;
5192
5519
  const attrs = graph.getNodeAttributes(nodeId);
5193
- return attrs.type === import_types21.NodeType.FrontierNode;
5520
+ return attrs.type === import_types22.NodeType.FrontierNode;
5194
5521
  }
5195
5522
  function clampConfidence(n) {
5196
5523
  if (!Number.isFinite(n)) return 0;
@@ -5211,7 +5538,7 @@ function gradedConfidence(edge) {
5211
5538
  }
5212
5539
  function detectMissingDivergences(graph, bucket) {
5213
5540
  const out = [];
5214
- if (bucket.type === import_types21.EdgeType.CONTAINS) return out;
5541
+ if (bucket.type === import_types22.EdgeType.CONTAINS) return out;
5215
5542
  if (bucket.extracted && !bucket.observed) {
5216
5543
  if (!nodeIsFrontier(graph, bucket.target)) {
5217
5544
  out.push({
@@ -5252,7 +5579,7 @@ function declaredHostFor(svc) {
5252
5579
  function hasExtractedConfiguredBy(graph, svcId) {
5253
5580
  for (const edgeId of graph.outboundEdges(svcId)) {
5254
5581
  const e = graph.getEdgeAttributes(edgeId);
5255
- if (e.type === import_types21.EdgeType.CONFIGURED_BY && e.provenance === import_types21.Provenance.EXTRACTED) {
5582
+ if (e.type === import_types22.EdgeType.CONFIGURED_BY && e.provenance === import_types22.Provenance.EXTRACTED) {
5256
5583
  return true;
5257
5584
  }
5258
5585
  }
@@ -5265,10 +5592,10 @@ function detectHostMismatch(graph, svcId, svc) {
5265
5592
  const out = [];
5266
5593
  for (const edgeId of graph.outboundEdges(svcId)) {
5267
5594
  const edge = graph.getEdgeAttributes(edgeId);
5268
- if (edge.type !== import_types21.EdgeType.CONNECTS_TO) continue;
5269
- if (edge.provenance !== import_types21.Provenance.OBSERVED) continue;
5595
+ if (edge.type !== import_types22.EdgeType.CONNECTS_TO) continue;
5596
+ if (edge.provenance !== import_types22.Provenance.OBSERVED) continue;
5270
5597
  const target = graph.getNodeAttributes(edge.target);
5271
- if (target.type !== import_types21.NodeType.DatabaseNode) continue;
5598
+ if (target.type !== import_types22.NodeType.DatabaseNode) continue;
5272
5599
  const observedHost = target.host?.trim();
5273
5600
  if (!observedHost) continue;
5274
5601
  if (observedHost === declaredHost) continue;
@@ -5290,10 +5617,10 @@ function detectCompatDivergences(graph, svcId, svc) {
5290
5617
  const deps = svc.dependencies ?? {};
5291
5618
  for (const edgeId of graph.outboundEdges(svcId)) {
5292
5619
  const edge = graph.getEdgeAttributes(edgeId);
5293
- if (edge.type !== import_types21.EdgeType.CONNECTS_TO) continue;
5294
- if (edge.provenance !== import_types21.Provenance.OBSERVED) continue;
5620
+ if (edge.type !== import_types22.EdgeType.CONNECTS_TO) continue;
5621
+ if (edge.provenance !== import_types22.Provenance.OBSERVED) continue;
5295
5622
  const target = graph.getNodeAttributes(edge.target);
5296
- if (target.type !== import_types21.NodeType.DatabaseNode) continue;
5623
+ if (target.type !== import_types22.NodeType.DatabaseNode) continue;
5297
5624
  for (const pair of compatPairs()) {
5298
5625
  if (pair.engine !== target.engine) continue;
5299
5626
  const declared = deps[pair.driver];
@@ -5354,7 +5681,7 @@ function computeDivergences(graph, opts = {}) {
5354
5681
  }
5355
5682
  graph.forEachNode((nodeId, attrs) => {
5356
5683
  const n = attrs;
5357
- if (n.type !== import_types21.NodeType.ServiceNode) return;
5684
+ if (n.type !== import_types22.NodeType.ServiceNode) return;
5358
5685
  const svc = n;
5359
5686
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
5360
5687
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
@@ -5387,7 +5714,7 @@ function computeDivergences(graph, opts = {}) {
5387
5714
  if (a.source !== b.source) return a.source.localeCompare(b.source);
5388
5715
  return a.target.localeCompare(b.target);
5389
5716
  });
5390
- return import_types21.DivergenceResultSchema.parse({
5717
+ return import_types22.DivergenceResultSchema.parse({
5391
5718
  divergences: filtered,
5392
5719
  totalAffected: filtered.length,
5393
5720
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -5396,7 +5723,7 @@ function computeDivergences(graph, opts = {}) {
5396
5723
 
5397
5724
  // src/diff.ts
5398
5725
  init_cjs_shims();
5399
- var import_node_fs21 = require("fs");
5726
+ var import_node_fs22 = require("fs");
5400
5727
  async function loadSnapshotForDiff(target) {
5401
5728
  if (/^https?:\/\//i.test(target)) {
5402
5729
  const res = await fetch(target);
@@ -5405,7 +5732,7 @@ async function loadSnapshotForDiff(target) {
5405
5732
  }
5406
5733
  return await res.json();
5407
5734
  }
5408
- const raw = await import_node_fs21.promises.readFile(target, "utf8");
5735
+ const raw = await import_node_fs22.promises.readFile(target, "utf8");
5409
5736
  return JSON.parse(raw);
5410
5737
  }
5411
5738
  function indexEntries(entries) {
@@ -5473,23 +5800,23 @@ function canonicalJson(value) {
5473
5800
 
5474
5801
  // src/projects.ts
5475
5802
  init_cjs_shims();
5476
- var import_node_path34 = __toESM(require("path"), 1);
5803
+ var import_node_path36 = __toESM(require("path"), 1);
5477
5804
  function pathsForProject(project, baseDir) {
5478
5805
  if (project === DEFAULT_PROJECT) {
5479
5806
  return {
5480
- snapshotPath: import_node_path34.default.join(baseDir, "graph.json"),
5481
- errorsPath: import_node_path34.default.join(baseDir, "errors.ndjson"),
5482
- staleEventsPath: import_node_path34.default.join(baseDir, "stale-events.ndjson"),
5483
- embeddingsCachePath: import_node_path34.default.join(baseDir, "embeddings.json"),
5484
- policyViolationsPath: import_node_path34.default.join(baseDir, "policy-violations.ndjson")
5807
+ snapshotPath: import_node_path36.default.join(baseDir, "graph.json"),
5808
+ errorsPath: import_node_path36.default.join(baseDir, "errors.ndjson"),
5809
+ staleEventsPath: import_node_path36.default.join(baseDir, "stale-events.ndjson"),
5810
+ embeddingsCachePath: import_node_path36.default.join(baseDir, "embeddings.json"),
5811
+ policyViolationsPath: import_node_path36.default.join(baseDir, "policy-violations.ndjson")
5485
5812
  };
5486
5813
  }
5487
5814
  return {
5488
- snapshotPath: import_node_path34.default.join(baseDir, `${project}.json`),
5489
- errorsPath: import_node_path34.default.join(baseDir, `errors.${project}.ndjson`),
5490
- staleEventsPath: import_node_path34.default.join(baseDir, `stale-events.${project}.ndjson`),
5491
- embeddingsCachePath: import_node_path34.default.join(baseDir, `embeddings.${project}.json`),
5492
- policyViolationsPath: import_node_path34.default.join(baseDir, `policy-violations.${project}.ndjson`)
5815
+ snapshotPath: import_node_path36.default.join(baseDir, `${project}.json`),
5816
+ errorsPath: import_node_path36.default.join(baseDir, `errors.${project}.ndjson`),
5817
+ staleEventsPath: import_node_path36.default.join(baseDir, `stale-events.${project}.ndjson`),
5818
+ embeddingsCachePath: import_node_path36.default.join(baseDir, `embeddings.${project}.json`),
5819
+ policyViolationsPath: import_node_path36.default.join(baseDir, `policy-violations.${project}.ndjson`)
5493
5820
  };
5494
5821
  }
5495
5822
  var Projects = class {
@@ -5525,25 +5852,25 @@ var Projects = class {
5525
5852
 
5526
5853
  // src/registry.ts
5527
5854
  init_cjs_shims();
5528
- var import_node_fs22 = require("fs");
5855
+ var import_node_fs23 = require("fs");
5529
5856
  var import_node_os3 = __toESM(require("os"), 1);
5530
- var import_node_path35 = __toESM(require("path"), 1);
5531
- var import_types22 = require("@neat.is/types");
5857
+ var import_node_path37 = __toESM(require("path"), 1);
5858
+ var import_types23 = require("@neat.is/types");
5532
5859
  var LOCK_TIMEOUT_MS = 5e3;
5533
5860
  var LOCK_RETRY_MS = 50;
5534
5861
  function neatHome() {
5535
5862
  const override = process.env.NEAT_HOME;
5536
- if (override && override.length > 0) return import_node_path35.default.resolve(override);
5537
- return import_node_path35.default.join(import_node_os3.default.homedir(), ".neat");
5863
+ if (override && override.length > 0) return import_node_path37.default.resolve(override);
5864
+ return import_node_path37.default.join(import_node_os3.default.homedir(), ".neat");
5538
5865
  }
5539
5866
  function registryPath() {
5540
- return import_node_path35.default.join(neatHome(), "projects.json");
5867
+ return import_node_path37.default.join(neatHome(), "projects.json");
5541
5868
  }
5542
5869
  function registryLockPath() {
5543
- return import_node_path35.default.join(neatHome(), "projects.json.lock");
5870
+ return import_node_path37.default.join(neatHome(), "projects.json.lock");
5544
5871
  }
5545
5872
  function daemonPidPath() {
5546
- return import_node_path35.default.join(neatHome(), "neatd.pid");
5873
+ return import_node_path37.default.join(neatHome(), "neatd.pid");
5547
5874
  }
5548
5875
  function isPidAliveDefault(pid) {
5549
5876
  try {
@@ -5555,7 +5882,7 @@ function isPidAliveDefault(pid) {
5555
5882
  }
5556
5883
  async function readPidFile(file) {
5557
5884
  try {
5558
- const raw = await import_node_fs22.promises.readFile(file, "utf8");
5885
+ const raw = await import_node_fs23.promises.readFile(file, "utf8");
5559
5886
  const pid = Number.parseInt(raw.trim(), 10);
5560
5887
  return Number.isInteger(pid) && pid > 0 ? pid : void 0;
5561
5888
  } catch {
@@ -5603,32 +5930,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
5603
5930
  }
5604
5931
  }
5605
5932
  async function normalizeProjectPath(input) {
5606
- const resolved = import_node_path35.default.resolve(input);
5933
+ const resolved = import_node_path37.default.resolve(input);
5607
5934
  try {
5608
- return await import_node_fs22.promises.realpath(resolved);
5935
+ return await import_node_fs23.promises.realpath(resolved);
5609
5936
  } catch {
5610
5937
  return resolved;
5611
5938
  }
5612
5939
  }
5613
5940
  async function writeAtomically(target, contents) {
5614
- await import_node_fs22.promises.mkdir(import_node_path35.default.dirname(target), { recursive: true });
5941
+ await import_node_fs23.promises.mkdir(import_node_path37.default.dirname(target), { recursive: true });
5615
5942
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
5616
- const fd = await import_node_fs22.promises.open(tmp, "w");
5943
+ const fd = await import_node_fs23.promises.open(tmp, "w");
5617
5944
  try {
5618
5945
  await fd.writeFile(contents, "utf8");
5619
5946
  await fd.sync();
5620
5947
  } finally {
5621
5948
  await fd.close();
5622
5949
  }
5623
- await import_node_fs22.promises.rename(tmp, target);
5950
+ await import_node_fs23.promises.rename(tmp, target);
5624
5951
  }
5625
5952
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
5626
5953
  const deadline = Date.now() + timeoutMs;
5627
- await import_node_fs22.promises.mkdir(import_node_path35.default.dirname(lockPath), { recursive: true });
5954
+ await import_node_fs23.promises.mkdir(import_node_path37.default.dirname(lockPath), { recursive: true });
5628
5955
  let probedHolder = false;
5629
5956
  while (true) {
5630
5957
  try {
5631
- const fd = await import_node_fs22.promises.open(lockPath, "wx");
5958
+ const fd = await import_node_fs23.promises.open(lockPath, "wx");
5632
5959
  try {
5633
5960
  await fd.writeFile(`${process.pid}
5634
5961
  `, "utf8");
@@ -5653,7 +5980,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
5653
5980
  }
5654
5981
  }
5655
5982
  async function releaseLock(lockPath) {
5656
- await import_node_fs22.promises.unlink(lockPath).catch(() => {
5983
+ await import_node_fs23.promises.unlink(lockPath).catch(() => {
5657
5984
  });
5658
5985
  }
5659
5986
  async function withLock(fn) {
@@ -5669,7 +5996,7 @@ async function readRegistry() {
5669
5996
  const file = registryPath();
5670
5997
  let raw;
5671
5998
  try {
5672
- raw = await import_node_fs22.promises.readFile(file, "utf8");
5999
+ raw = await import_node_fs23.promises.readFile(file, "utf8");
5673
6000
  } catch (err) {
5674
6001
  if (err.code === "ENOENT") {
5675
6002
  return { version: 1, projects: [] };
@@ -5677,10 +6004,10 @@ async function readRegistry() {
5677
6004
  throw err;
5678
6005
  }
5679
6006
  const parsed = JSON.parse(raw);
5680
- return import_types22.RegistryFileSchema.parse(parsed);
6007
+ return import_types23.RegistryFileSchema.parse(parsed);
5681
6008
  }
5682
6009
  async function writeRegistry(reg) {
5683
- const validated = import_types22.RegistryFileSchema.parse(reg);
6010
+ const validated = import_types23.RegistryFileSchema.parse(reg);
5684
6011
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
5685
6012
  }
5686
6013
  var ProjectNameCollisionError = class extends Error {
@@ -5952,11 +6279,11 @@ function registerRoutes(scope, ctx) {
5952
6279
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
5953
6280
  const parsed = [];
5954
6281
  for (const c of candidates) {
5955
- const r = import_types23.DivergenceTypeSchema.safeParse(c);
6282
+ const r = import_types24.DivergenceTypeSchema.safeParse(c);
5956
6283
  if (!r.success) {
5957
6284
  return reply.code(400).send({
5958
6285
  error: `unknown divergence type "${c}"`,
5959
- allowed: import_types23.DivergenceTypeSchema.options
6286
+ allowed: import_types24.DivergenceTypeSchema.options
5960
6287
  });
5961
6288
  }
5962
6289
  parsed.push(r.data);
@@ -6169,7 +6496,7 @@ function registerRoutes(scope, ctx) {
6169
6496
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
6170
6497
  let violations = await log.readAll();
6171
6498
  if (req.query.severity) {
6172
- const sev = import_types23.PolicySeveritySchema.safeParse(req.query.severity);
6499
+ const sev = import_types24.PolicySeveritySchema.safeParse(req.query.severity);
6173
6500
  if (!sev.success) {
6174
6501
  return reply.code(400).send({
6175
6502
  error: "invalid severity",
@@ -6186,7 +6513,7 @@ function registerRoutes(scope, ctx) {
6186
6513
  scope.post("/policies/check", async (req, reply) => {
6187
6514
  const proj = resolveProject(registry, req, reply, ctx.bootstrap);
6188
6515
  if (!proj) return;
6189
- const parsed = import_types23.PoliciesCheckBodySchema.safeParse(req.body ?? {});
6516
+ const parsed = import_types24.PoliciesCheckBodySchema.safeParse(req.body ?? {});
6190
6517
  if (!parsed.success) {
6191
6518
  return reply.code(400).send({
6192
6519
  error: "invalid /policies/check body",
@@ -6428,15 +6755,15 @@ init_otel_grpc();
6428
6755
 
6429
6756
  // src/daemon.ts
6430
6757
  init_cjs_shims();
6431
- var import_node_fs24 = require("fs");
6432
- var import_node_path39 = __toESM(require("path"), 1);
6758
+ var import_node_fs25 = require("fs");
6759
+ var import_node_path41 = __toESM(require("path"), 1);
6433
6760
  init_otel();
6434
6761
  init_auth();
6435
6762
 
6436
6763
  // src/unrouted.ts
6437
6764
  init_cjs_shims();
6438
- var import_node_fs23 = require("fs");
6439
- var import_node_path38 = __toESM(require("path"), 1);
6765
+ var import_node_fs24 = require("fs");
6766
+ var import_node_path40 = __toESM(require("path"), 1);
6440
6767
  function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
6441
6768
  return {
6442
6769
  timestamp: now.toISOString(),
@@ -6446,21 +6773,21 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
6446
6773
  };
6447
6774
  }
6448
6775
  async function appendUnroutedSpan(neatHome2, record) {
6449
- const target = import_node_path38.default.join(neatHome2, "errors.ndjson");
6450
- await import_node_fs23.promises.mkdir(neatHome2, { recursive: true });
6451
- await import_node_fs23.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
6776
+ const target = import_node_path40.default.join(neatHome2, "errors.ndjson");
6777
+ await import_node_fs24.promises.mkdir(neatHome2, { recursive: true });
6778
+ await import_node_fs24.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
6452
6779
  }
6453
6780
  function unroutedErrorsPath(neatHome2) {
6454
- return import_node_path38.default.join(neatHome2, "errors.ndjson");
6781
+ return import_node_path40.default.join(neatHome2, "errors.ndjson");
6455
6782
  }
6456
6783
 
6457
6784
  // src/daemon.ts
6458
6785
  function neatHomeFor(opts) {
6459
- if (opts.neatHome && opts.neatHome.length > 0) return import_node_path39.default.resolve(opts.neatHome);
6786
+ if (opts.neatHome && opts.neatHome.length > 0) return import_node_path41.default.resolve(opts.neatHome);
6460
6787
  const env = process.env.NEAT_HOME;
6461
- if (env && env.length > 0) return import_node_path39.default.resolve(env);
6788
+ if (env && env.length > 0) return import_node_path41.default.resolve(env);
6462
6789
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
6463
- return import_node_path39.default.join(home, ".neat");
6790
+ return import_node_path41.default.join(home, ".neat");
6464
6791
  }
6465
6792
  function routeSpanToProject(serviceName, projects) {
6466
6793
  if (!serviceName) return DEFAULT_PROJECT;
@@ -6495,9 +6822,9 @@ function isTokenContained(needle, haystack) {
6495
6822
  return tokens.includes(needle);
6496
6823
  }
6497
6824
  async function bootstrapProject(entry) {
6498
- const paths = pathsForProject(entry.name, import_node_path39.default.join(entry.path, "neat-out"));
6825
+ const paths = pathsForProject(entry.name, import_node_path41.default.join(entry.path, "neat-out"));
6499
6826
  try {
6500
- const stat = await import_node_fs24.promises.stat(entry.path);
6827
+ const stat = await import_node_fs25.promises.stat(entry.path);
6501
6828
  if (!stat.isDirectory()) {
6502
6829
  throw new Error(`registered path ${entry.path} is not a directory`);
6503
6830
  }
@@ -6563,13 +6890,13 @@ async function startDaemon(opts = {}) {
6563
6890
  const home = neatHomeFor(opts);
6564
6891
  const regPath = registryPath();
6565
6892
  try {
6566
- await import_node_fs24.promises.access(regPath);
6893
+ await import_node_fs25.promises.access(regPath);
6567
6894
  } catch {
6568
6895
  throw new Error(
6569
6896
  `neatd: registry not found at ${regPath}. Run \`neat init <path>\` to register a project before starting the daemon.`
6570
6897
  );
6571
6898
  }
6572
- const pidPath = import_node_path39.default.join(home, "neatd.pid");
6899
+ const pidPath = import_node_path41.default.join(home, "neatd.pid");
6573
6900
  await writeAtomically(pidPath, `${process.pid}
6574
6901
  `);
6575
6902
  const slots = /* @__PURE__ */ new Map();
@@ -6727,7 +7054,7 @@ async function startDaemon(opts = {}) {
6727
7054
  }
6728
7055
  if (restApp) await restApp.close().catch(() => {
6729
7056
  });
6730
- await import_node_fs24.promises.unlink(pidPath).catch(() => {
7057
+ await import_node_fs25.promises.unlink(pidPath).catch(() => {
6731
7058
  });
6732
7059
  throw new Error(
6733
7060
  `neatd: failed to bind REST on port ${restPort} \u2014 ${err.message}`
@@ -6833,7 +7160,7 @@ async function startDaemon(opts = {}) {
6833
7160
  });
6834
7161
  if (otlpApp) await otlpApp.close().catch(() => {
6835
7162
  });
6836
- await import_node_fs24.promises.unlink(pidPath).catch(() => {
7163
+ await import_node_fs25.promises.unlink(pidPath).catch(() => {
6837
7164
  });
6838
7165
  throw new Error(
6839
7166
  `neatd: failed to bind OTLP on port ${otlpPort} \u2014 ${err.message}`
@@ -6879,9 +7206,9 @@ async function startDaemon(opts = {}) {
6879
7206
  let registryWatcher = null;
6880
7207
  let reloadTimer = null;
6881
7208
  try {
6882
- const regDir = import_node_path39.default.dirname(regPath);
6883
- const regBase = import_node_path39.default.basename(regPath);
6884
- registryWatcher = (0, import_node_fs24.watch)(regDir, (_eventType, filename) => {
7209
+ const regDir = import_node_path41.default.dirname(regPath);
7210
+ const regBase = import_node_path41.default.basename(regPath);
7211
+ registryWatcher = (0, import_node_fs25.watch)(regDir, (_eventType, filename) => {
6885
7212
  if (filename !== null && filename !== regBase) return;
6886
7213
  if (reloadTimer) clearTimeout(reloadTimer);
6887
7214
  reloadTimer = setTimeout(() => {
@@ -6924,7 +7251,7 @@ async function startDaemon(opts = {}) {
6924
7251
  } catch {
6925
7252
  }
6926
7253
  }
6927
- await import_node_fs24.promises.unlink(pidPath).catch(() => {
7254
+ await import_node_fs25.promises.unlink(pidPath).catch(() => {
6928
7255
  });
6929
7256
  };
6930
7257
  return {