@neat.is/core 0.4.12 → 0.4.13

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