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