@neat.is/core 0.4.12 → 0.4.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -58,9 +58,9 @@ function mountBearerAuth(app, opts) {
58
58
  const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
59
59
  const publicRead = opts.publicRead === true;
60
60
  app.addHook("preHandler", (req, reply, done) => {
61
- const path47 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
61
+ const path49 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
62
62
  for (const suffix of suffixes) {
63
- if (path47 === suffix || path47.endsWith(suffix)) {
63
+ if (path49 === suffix || path49.endsWith(suffix)) {
64
64
  done();
65
65
  return;
66
66
  }
@@ -134,7 +134,9 @@ __export(otel_grpc_exports, {
134
134
  });
135
135
  function bytesToHex(buf) {
136
136
  if (!buf) return "";
137
- return Buffer.isBuffer(buf) ? buf.toString("hex") : "";
137
+ if (Buffer.isBuffer(buf)) return buf.toString("hex");
138
+ if (buf instanceof Uint8Array) return Buffer.from(buf).toString("hex");
139
+ return "";
138
140
  }
139
141
  function nanosToString(n) {
140
142
  if (n === void 0 || n === null) return "0";
@@ -186,8 +188,8 @@ function reshapeGrpcRequest(req) {
186
188
  };
187
189
  }
188
190
  function resolveProtoRoot() {
189
- const here = import_node_path37.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
190
- return import_node_path37.default.resolve(here, "..", "proto");
191
+ const here = import_node_path39.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
192
+ return import_node_path39.default.resolve(here, "..", "proto");
191
193
  }
192
194
  function loadTraceService() {
193
195
  const protoRoot = resolveProtoRoot();
@@ -255,13 +257,13 @@ async function startOtelGrpcReceiver(opts) {
255
257
  })
256
258
  };
257
259
  }
258
- var import_node_url, import_node_path37, import_node_crypto2, grpc, protoLoader;
260
+ var import_node_url, import_node_path39, import_node_crypto2, grpc, protoLoader;
259
261
  var init_otel_grpc = __esm({
260
262
  "src/otel-grpc.ts"() {
261
263
  "use strict";
262
264
  init_cjs_shims();
263
265
  import_node_url = require("url");
264
- import_node_path37 = __toESM(require("path"), 1);
266
+ import_node_path39 = __toESM(require("path"), 1);
265
267
  import_node_crypto2 = require("crypto");
266
268
  grpc = __toESM(require("@grpc/grpc-js"), 1);
267
269
  protoLoader = __toESM(require("@grpc/proto-loader"), 1);
@@ -371,10 +373,10 @@ function parseOtlpRequest(body) {
371
373
  return out;
372
374
  }
373
375
  function loadProtoRoot() {
374
- const here = import_node_path38.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
375
- const protoRoot = import_node_path38.default.resolve(here, "..", "proto");
376
+ const here = import_node_path40.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
377
+ const protoRoot = import_node_path40.default.resolve(here, "..", "proto");
376
378
  const root = new import_protobufjs.default.Root();
377
- root.resolvePath = (_origin, target) => import_node_path38.default.resolve(protoRoot, target);
379
+ root.resolvePath = (_origin, target) => import_node_path40.default.resolve(protoRoot, target);
378
380
  root.loadSync(
379
381
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
380
382
  { keepCase: true }
@@ -407,7 +409,10 @@ function encodeProtobufResponseBody() {
407
409
  }
408
410
  async function decodeProtobufBody(buf) {
409
411
  const Type = loadProtobufDecoder();
410
- const decoded = Type.decode(buf).toJSON();
412
+ const decoded = Type.toObject(Type.decode(buf), {
413
+ longs: String,
414
+ enums: Number
415
+ });
411
416
  const { reshapeGrpcRequest: reshapeGrpcRequest2 } = await Promise.resolve().then(() => (init_otel_grpc(), otel_grpc_exports));
412
417
  return reshapeGrpcRequest2(decoded);
413
418
  }
@@ -567,12 +572,12 @@ async function buildOtelReceiver(opts) {
567
572
  };
568
573
  return decorated;
569
574
  }
570
- var import_node_path38, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
575
+ var import_node_path40, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
571
576
  var init_otel = __esm({
572
577
  "src/otel.ts"() {
573
578
  "use strict";
574
579
  init_cjs_shims();
575
- import_node_path38 = __toESM(require("path"), 1);
580
+ import_node_path40 = __toESM(require("path"), 1);
576
581
  import_node_url2 = require("url");
577
582
  import_fastify2 = __toESM(require("fastify"), 1);
578
583
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -600,8 +605,8 @@ __export(cli_exports, {
600
605
  });
601
606
  module.exports = __toCommonJS(cli_exports);
602
607
  init_cjs_shims();
603
- var import_node_path46 = __toESM(require("path"), 1);
604
- var import_node_fs30 = require("fs");
608
+ var import_node_path48 = __toESM(require("path"), 1);
609
+ var import_node_fs31 = require("fs");
605
610
  var import_node_url3 = require("url");
606
611
 
607
612
  // src/graph.ts
@@ -1131,19 +1136,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1131
1136
  function longestIncomingWalk(graph, start, maxDepth) {
1132
1137
  let best = { path: [start], edges: [] };
1133
1138
  const visited = /* @__PURE__ */ new Set([start]);
1134
- function step(node, path47, edges) {
1135
- if (path47.length > best.path.length) {
1136
- best = { path: [...path47], edges: [...edges] };
1139
+ function step(node, path49, edges) {
1140
+ if (path49.length > best.path.length) {
1141
+ best = { path: [...path49], edges: [...edges] };
1137
1142
  }
1138
- if (path47.length - 1 >= maxDepth) return;
1143
+ if (path49.length - 1 >= maxDepth) return;
1139
1144
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1140
1145
  for (const [srcId, edge] of incoming) {
1141
1146
  if (visited.has(srcId)) continue;
1142
1147
  visited.add(srcId);
1143
- path47.push(srcId);
1148
+ path49.push(srcId);
1144
1149
  edges.push(edge);
1145
- step(srcId, path47, edges);
1146
- path47.pop();
1150
+ step(srcId, path49, edges);
1151
+ path49.pop();
1147
1152
  edges.pop();
1148
1153
  visited.delete(srcId);
1149
1154
  }
@@ -3115,10 +3120,8 @@ async function addServiceAliases(graph, scanPath, services) {
3115
3120
  await collectK8sAliases(graph, scanPath, byName);
3116
3121
  }
3117
3122
 
3118
- // src/extract/databases/index.ts
3123
+ // src/extract/files.ts
3119
3124
  init_cjs_shims();
3120
- var import_node_path18 = __toESM(require("path"), 1);
3121
- var import_types8 = require("@neat.is/types");
3122
3125
 
3123
3126
  // src/extract/calls/shared.ts
3124
3127
  init_cjs_shims();
@@ -3217,11 +3220,333 @@ function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
3217
3220
  return { fileNodeId, nodesAdded, edgesAdded };
3218
3221
  }
3219
3222
 
3223
+ // src/extract/files.ts
3224
+ var import_node_path11 = __toESM(require("path"), 1);
3225
+ async function addFiles(graph, services) {
3226
+ let nodesAdded = 0;
3227
+ let edgesAdded = 0;
3228
+ for (const service of services) {
3229
+ const filePaths = await walkSourceFiles(service.dir);
3230
+ for (const filePath of filePaths) {
3231
+ const relPath = toPosix2(import_node_path11.default.relative(service.dir, filePath));
3232
+ const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
3233
+ graph,
3234
+ service.pkg.name,
3235
+ service.node.id,
3236
+ relPath
3237
+ );
3238
+ nodesAdded += n;
3239
+ edgesAdded += e;
3240
+ }
3241
+ }
3242
+ return { nodesAdded, edgesAdded };
3243
+ }
3244
+
3245
+ // src/extract/imports.ts
3246
+ init_cjs_shims();
3247
+ var import_node_path12 = __toESM(require("path"), 1);
3248
+ var import_node_fs11 = require("fs");
3249
+ var import_tree_sitter = __toESM(require("tree-sitter"), 1);
3250
+ var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
3251
+ var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
3252
+ var import_types8 = require("@neat.is/types");
3253
+ var PARSE_CHUNK = 16384;
3254
+ function parseSource(parser, source) {
3255
+ return parser.parse(
3256
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK)
3257
+ );
3258
+ }
3259
+ function makeJsParser() {
3260
+ const p = new import_tree_sitter.default();
3261
+ p.setLanguage(import_tree_sitter_javascript.default);
3262
+ return p;
3263
+ }
3264
+ function makePyParser() {
3265
+ const p = new import_tree_sitter.default();
3266
+ p.setLanguage(import_tree_sitter_python.default);
3267
+ return p;
3268
+ }
3269
+ function stringLiteralText(node) {
3270
+ for (let i = 0; i < node.childCount; i++) {
3271
+ const child = node.child(i);
3272
+ if (child?.type === "string_fragment") return child.text;
3273
+ }
3274
+ const raw = node.text;
3275
+ if (raw.length >= 2) return raw.slice(1, -1);
3276
+ return raw.length === 0 ? null : "";
3277
+ }
3278
+ function clipSnippet(text) {
3279
+ const oneLine = text.split("\n")[0] ?? text;
3280
+ return oneLine.length > 120 ? oneLine.slice(0, 120) : oneLine;
3281
+ }
3282
+ function collectJsImports(node, out) {
3283
+ if (node.type === "import_statement") {
3284
+ const source = node.childForFieldName("source");
3285
+ if (source) {
3286
+ const specifier = stringLiteralText(source);
3287
+ if (specifier) {
3288
+ out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
3289
+ }
3290
+ }
3291
+ return;
3292
+ }
3293
+ if (node.type === "call_expression") {
3294
+ const fn = node.childForFieldName("function");
3295
+ if (fn?.type === "identifier" && fn.text === "require") {
3296
+ const args = node.childForFieldName("arguments");
3297
+ const firstArg = args?.namedChild(0);
3298
+ if (firstArg?.type === "string") {
3299
+ const specifier = stringLiteralText(firstArg);
3300
+ if (specifier) {
3301
+ out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
3302
+ }
3303
+ }
3304
+ }
3305
+ }
3306
+ for (let i = 0; i < node.namedChildCount; i++) {
3307
+ const child = node.namedChild(i);
3308
+ if (child) collectJsImports(child, out);
3309
+ }
3310
+ }
3311
+ function collectPyImports(node, out) {
3312
+ if (node.type === "import_from_statement") {
3313
+ let level = 0;
3314
+ let modulePath = "";
3315
+ let pastFrom = false;
3316
+ for (let i = 0; i < node.childCount; i++) {
3317
+ const child = node.child(i);
3318
+ if (!child) continue;
3319
+ if (!pastFrom) {
3320
+ if (child.type === "from") pastFrom = true;
3321
+ continue;
3322
+ }
3323
+ if (child.type === "import") break;
3324
+ if (child.type === "relative_import") {
3325
+ for (let j = 0; j < child.childCount; j++) {
3326
+ const rc = child.child(j);
3327
+ if (!rc) continue;
3328
+ if (rc.type === "import_prefix") {
3329
+ for (let k = 0; k < rc.childCount; k++) {
3330
+ if (rc.child(k)?.type === ".") level++;
3331
+ }
3332
+ } else if (rc.type === "dotted_name") modulePath = rc.text;
3333
+ }
3334
+ break;
3335
+ }
3336
+ if (child.type === "dotted_name") {
3337
+ modulePath = child.text;
3338
+ break;
3339
+ }
3340
+ }
3341
+ if (level > 0 || modulePath) {
3342
+ out.push({ modulePath, level, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
3343
+ }
3344
+ }
3345
+ for (let i = 0; i < node.namedChildCount; i++) {
3346
+ const child = node.namedChild(i);
3347
+ if (child) collectPyImports(child, out);
3348
+ }
3349
+ }
3350
+ async function fileExists(p) {
3351
+ try {
3352
+ await import_node_fs11.promises.access(p);
3353
+ return true;
3354
+ } catch {
3355
+ return false;
3356
+ }
3357
+ }
3358
+ function isWithinServiceDir(candidate, serviceDir) {
3359
+ const rel = import_node_path12.default.relative(serviceDir, candidate);
3360
+ return rel !== "" && !rel.startsWith("..") && !import_node_path12.default.isAbsolute(rel);
3361
+ }
3362
+ var JS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
3363
+ var JS_INDEX_FILES = JS_EXTENSIONS.map((ext) => `index${ext}`);
3364
+ async function firstExistingCandidate(base, serviceDir) {
3365
+ for (const ext of JS_EXTENSIONS) {
3366
+ const candidate = base + ext;
3367
+ if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
3368
+ return toPosix2(import_node_path12.default.relative(serviceDir, candidate));
3369
+ }
3370
+ }
3371
+ for (const indexFile of JS_INDEX_FILES) {
3372
+ const candidate = import_node_path12.default.join(base, indexFile);
3373
+ if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
3374
+ return toPosix2(import_node_path12.default.relative(serviceDir, candidate));
3375
+ }
3376
+ }
3377
+ return null;
3378
+ }
3379
+ async function loadTsPathConfig(serviceDir) {
3380
+ const tsconfigPath = import_node_path12.default.join(serviceDir, "tsconfig.json");
3381
+ let raw;
3382
+ try {
3383
+ raw = await import_node_fs11.promises.readFile(tsconfigPath, "utf8");
3384
+ } catch {
3385
+ return null;
3386
+ }
3387
+ try {
3388
+ const parsed = JSON.parse(raw);
3389
+ const paths = parsed.compilerOptions?.paths;
3390
+ if (!paths || Object.keys(paths).length === 0) return null;
3391
+ const baseUrl = parsed.compilerOptions?.baseUrl;
3392
+ return { paths, baseDir: baseUrl ? import_node_path12.default.resolve(serviceDir, baseUrl) : serviceDir };
3393
+ } catch (err) {
3394
+ recordExtractionError("import alias resolution", tsconfigPath, err);
3395
+ return null;
3396
+ }
3397
+ }
3398
+ async function resolveTsAlias(specifier, config, serviceDir) {
3399
+ for (const [pattern, targets] of Object.entries(config.paths)) {
3400
+ let suffix = null;
3401
+ if (pattern === specifier) {
3402
+ suffix = "";
3403
+ } else if (pattern.endsWith("/*")) {
3404
+ const prefix = pattern.slice(0, -1);
3405
+ if (specifier.startsWith(prefix)) suffix = specifier.slice(prefix.length);
3406
+ }
3407
+ if (suffix === null) continue;
3408
+ for (const target of targets) {
3409
+ const targetBase = target.endsWith("/*") ? target.slice(0, -2) : target.replace(/\*$/, "");
3410
+ const resolvedBase = import_node_path12.default.resolve(config.baseDir, targetBase, suffix);
3411
+ const hit = await firstExistingCandidate(resolvedBase, serviceDir);
3412
+ if (hit) return hit;
3413
+ if (isWithinServiceDir(resolvedBase, serviceDir) && await fileExists(resolvedBase)) {
3414
+ return toPosix2(import_node_path12.default.relative(serviceDir, resolvedBase));
3415
+ }
3416
+ }
3417
+ }
3418
+ return null;
3419
+ }
3420
+ async function resolveJsImport(specifier, importerDir, serviceDir, tsPaths) {
3421
+ if (!specifier) return null;
3422
+ if (specifier.startsWith("./") || specifier.startsWith("../")) {
3423
+ const base = import_node_path12.default.resolve(importerDir, specifier);
3424
+ const ext = import_node_path12.default.extname(specifier);
3425
+ if (ext) {
3426
+ if (ext === ".js" || ext === ".jsx") {
3427
+ const tsExt = ext === ".jsx" ? ".tsx" : ".ts";
3428
+ const tsSibling = base.slice(0, -ext.length) + tsExt;
3429
+ if (isWithinServiceDir(tsSibling, serviceDir) && await fileExists(tsSibling)) {
3430
+ return toPosix2(import_node_path12.default.relative(serviceDir, tsSibling));
3431
+ }
3432
+ }
3433
+ if (isWithinServiceDir(base, serviceDir) && await fileExists(base)) {
3434
+ return toPosix2(import_node_path12.default.relative(serviceDir, base));
3435
+ }
3436
+ return null;
3437
+ }
3438
+ return firstExistingCandidate(base, serviceDir);
3439
+ }
3440
+ if (tsPaths) return resolveTsAlias(specifier, tsPaths, serviceDir);
3441
+ return null;
3442
+ }
3443
+ async function resolvePyImport(imp, importerPath, serviceDir) {
3444
+ if (!imp.modulePath) return null;
3445
+ const relPath = imp.modulePath.split(".").join("/");
3446
+ let baseDir;
3447
+ if (imp.level > 0) {
3448
+ baseDir = import_node_path12.default.dirname(importerPath);
3449
+ for (let i = 1; i < imp.level; i++) baseDir = import_node_path12.default.dirname(baseDir);
3450
+ } else {
3451
+ baseDir = serviceDir;
3452
+ }
3453
+ const candidates = [import_node_path12.default.join(baseDir, `${relPath}.py`), import_node_path12.default.join(baseDir, relPath, "__init__.py")];
3454
+ for (const candidate of candidates) {
3455
+ if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
3456
+ return toPosix2(import_node_path12.default.relative(serviceDir, candidate));
3457
+ }
3458
+ }
3459
+ return null;
3460
+ }
3461
+ function emitImportEdge(graph, serviceName, importerFileId, importerRelPath, importeeRelPath, line, snippet2) {
3462
+ const importeeFileId = (0, import_types8.fileId)(serviceName, importeeRelPath);
3463
+ if (!graph.hasNode(importeeFileId)) return 0;
3464
+ const edgeId = (0, import_types8.extractedEdgeId)(importerFileId, importeeFileId, import_types8.EdgeType.IMPORTS);
3465
+ if (graph.hasEdge(edgeId)) return 0;
3466
+ const edge = {
3467
+ id: edgeId,
3468
+ source: importerFileId,
3469
+ target: importeeFileId,
3470
+ type: import_types8.EdgeType.IMPORTS,
3471
+ provenance: import_types8.Provenance.EXTRACTED,
3472
+ confidence: (0, import_types8.confidenceForExtracted)("structural"),
3473
+ evidence: { file: importerRelPath, line, snippet: snippet2 }
3474
+ };
3475
+ graph.addEdgeWithKey(edgeId, importerFileId, importeeFileId, edge);
3476
+ return 1;
3477
+ }
3478
+ async function addImports(graph, services) {
3479
+ const jsParser = makeJsParser();
3480
+ const pyParser = makePyParser();
3481
+ let edgesAdded = 0;
3482
+ for (const service of services) {
3483
+ const tsPaths = await loadTsPathConfig(service.dir);
3484
+ const files = await loadSourceFiles(service.dir);
3485
+ for (const file of files) {
3486
+ if (isTestPath(file.path)) continue;
3487
+ const relFile = toPosix2(import_node_path12.default.relative(service.dir, file.path));
3488
+ const importerFileId = (0, import_types8.fileId)(service.pkg.name, relFile);
3489
+ const isPython = import_node_path12.default.extname(file.path) === ".py";
3490
+ if (isPython) {
3491
+ let pyImports = [];
3492
+ try {
3493
+ const tree = parseSource(pyParser, file.content);
3494
+ collectPyImports(tree.rootNode, pyImports);
3495
+ } catch (err) {
3496
+ recordExtractionError("import extraction", file.path, err);
3497
+ continue;
3498
+ }
3499
+ for (const imp of pyImports) {
3500
+ const resolved = await resolvePyImport(imp, file.path, service.dir);
3501
+ if (!resolved) continue;
3502
+ edgesAdded += emitImportEdge(
3503
+ graph,
3504
+ service.pkg.name,
3505
+ importerFileId,
3506
+ relFile,
3507
+ resolved,
3508
+ imp.line,
3509
+ imp.snippet
3510
+ );
3511
+ }
3512
+ continue;
3513
+ }
3514
+ let jsImports = [];
3515
+ try {
3516
+ const tree = parseSource(jsParser, file.content);
3517
+ collectJsImports(tree.rootNode, jsImports);
3518
+ } catch (err) {
3519
+ recordExtractionError("import extraction", file.path, err);
3520
+ continue;
3521
+ }
3522
+ for (const imp of jsImports) {
3523
+ const resolved = await resolveJsImport(imp.specifier, import_node_path12.default.dirname(file.path), service.dir, tsPaths);
3524
+ if (!resolved) continue;
3525
+ edgesAdded += emitImportEdge(
3526
+ graph,
3527
+ service.pkg.name,
3528
+ importerFileId,
3529
+ relFile,
3530
+ resolved,
3531
+ imp.line,
3532
+ imp.snippet
3533
+ );
3534
+ }
3535
+ }
3536
+ }
3537
+ return { nodesAdded: 0, edgesAdded };
3538
+ }
3539
+
3540
+ // src/extract/databases/index.ts
3541
+ init_cjs_shims();
3542
+ var import_node_path20 = __toESM(require("path"), 1);
3543
+ var import_types9 = require("@neat.is/types");
3544
+
3220
3545
  // src/extract/databases/db-config-yaml.ts
3221
3546
  init_cjs_shims();
3222
- var import_node_path11 = __toESM(require("path"), 1);
3547
+ var import_node_path13 = __toESM(require("path"), 1);
3223
3548
  async function parse(serviceDir) {
3224
- const yamlPath = import_node_path11.default.join(serviceDir, "db-config.yaml");
3549
+ const yamlPath = import_node_path13.default.join(serviceDir, "db-config.yaml");
3225
3550
  if (!await exists(yamlPath)) return [];
3226
3551
  const raw = await readYaml(yamlPath);
3227
3552
  return [
@@ -3239,13 +3564,13 @@ var dbConfigYamlParser = { name: "db-config.yaml", parse };
3239
3564
 
3240
3565
  // src/extract/databases/dotenv.ts
3241
3566
  init_cjs_shims();
3242
- var import_node_fs12 = require("fs");
3243
- var import_node_path13 = __toESM(require("path"), 1);
3567
+ var import_node_fs13 = require("fs");
3568
+ var import_node_path15 = __toESM(require("path"), 1);
3244
3569
 
3245
3570
  // src/extract/databases/shared.ts
3246
3571
  init_cjs_shims();
3247
- var import_node_fs11 = require("fs");
3248
- var import_node_path12 = __toESM(require("path"), 1);
3572
+ var import_node_fs12 = require("fs");
3573
+ var import_node_path14 = __toESM(require("path"), 1);
3249
3574
  function schemeToEngine(scheme) {
3250
3575
  const s = scheme.toLowerCase().split("+")[0];
3251
3576
  switch (s) {
@@ -3284,14 +3609,14 @@ function parseConnectionString(url) {
3284
3609
  }
3285
3610
  async function readIfExists(filePath) {
3286
3611
  try {
3287
- return await import_node_fs11.promises.readFile(filePath, "utf8");
3612
+ return await import_node_fs12.promises.readFile(filePath, "utf8");
3288
3613
  } catch {
3289
3614
  return null;
3290
3615
  }
3291
3616
  }
3292
3617
  async function findFirst(serviceDir, candidates) {
3293
3618
  for (const rel of candidates) {
3294
- const abs = import_node_path12.default.join(serviceDir, rel);
3619
+ const abs = import_node_path14.default.join(serviceDir, rel);
3295
3620
  const content = await readIfExists(abs);
3296
3621
  if (content !== null) return abs;
3297
3622
  }
@@ -3342,15 +3667,15 @@ function parseDotenvLine(line) {
3342
3667
  return { key, value };
3343
3668
  }
3344
3669
  async function parse2(serviceDir) {
3345
- const entries = await import_node_fs12.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
3670
+ const entries = await import_node_fs13.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
3346
3671
  const configs = [];
3347
3672
  const seen = /* @__PURE__ */ new Set();
3348
3673
  for (const entry2 of entries) {
3349
3674
  if (!entry2.isFile()) continue;
3350
3675
  const match = isConfigFile(entry2.name);
3351
3676
  if (!match.match || match.fileType !== "env") continue;
3352
- const filePath = import_node_path13.default.join(serviceDir, entry2.name);
3353
- const content = await import_node_fs12.promises.readFile(filePath, "utf8");
3677
+ const filePath = import_node_path15.default.join(serviceDir, entry2.name);
3678
+ const content = await import_node_fs13.promises.readFile(filePath, "utf8");
3354
3679
  for (const line of content.split("\n")) {
3355
3680
  const parsed = parseDotenvLine(line);
3356
3681
  if (!parsed) continue;
@@ -3369,9 +3694,9 @@ var dotenvParser = { name: ".env", parse: parse2 };
3369
3694
 
3370
3695
  // src/extract/databases/prisma.ts
3371
3696
  init_cjs_shims();
3372
- var import_node_path14 = __toESM(require("path"), 1);
3697
+ var import_node_path16 = __toESM(require("path"), 1);
3373
3698
  async function parse3(serviceDir) {
3374
- const schemaPath = import_node_path14.default.join(serviceDir, "prisma", "schema.prisma");
3699
+ const schemaPath = import_node_path16.default.join(serviceDir, "prisma", "schema.prisma");
3375
3700
  const content = await readIfExists(schemaPath);
3376
3701
  if (!content) return [];
3377
3702
  const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
@@ -3503,10 +3828,10 @@ var knexParser = { name: "knex", parse: parse5 };
3503
3828
 
3504
3829
  // src/extract/databases/ormconfig.ts
3505
3830
  init_cjs_shims();
3506
- var import_node_path15 = __toESM(require("path"), 1);
3831
+ var import_node_path17 = __toESM(require("path"), 1);
3507
3832
  async function parse6(serviceDir) {
3508
3833
  for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
3509
- const abs = import_node_path15.default.join(serviceDir, candidate);
3834
+ const abs = import_node_path17.default.join(serviceDir, candidate);
3510
3835
  if (!await exists(abs)) continue;
3511
3836
  const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
3512
3837
  const entries = Array.isArray(raw) ? raw : [raw];
@@ -3566,9 +3891,9 @@ var typeormParser = { name: "typeorm", parse: parse7 };
3566
3891
 
3567
3892
  // src/extract/databases/sequelize.ts
3568
3893
  init_cjs_shims();
3569
- var import_node_path16 = __toESM(require("path"), 1);
3894
+ var import_node_path18 = __toESM(require("path"), 1);
3570
3895
  async function parse8(serviceDir) {
3571
- const configPath = import_node_path16.default.join(serviceDir, "config", "config.json");
3896
+ const configPath = import_node_path18.default.join(serviceDir, "config", "config.json");
3572
3897
  if (!await exists(configPath)) return [];
3573
3898
  const raw = await readJson(configPath);
3574
3899
  const out = [];
@@ -3595,7 +3920,7 @@ var sequelizeParser = { name: "sequelize", parse: parse8 };
3595
3920
 
3596
3921
  // src/extract/databases/docker-compose.ts
3597
3922
  init_cjs_shims();
3598
- var import_node_path17 = __toESM(require("path"), 1);
3923
+ var import_node_path19 = __toESM(require("path"), 1);
3599
3924
  function portFromService(svc) {
3600
3925
  for (const raw of svc.ports ?? []) {
3601
3926
  const str = String(raw);
@@ -3622,7 +3947,7 @@ function databaseFromEnv(svc) {
3622
3947
  }
3623
3948
  async function parse9(serviceDir) {
3624
3949
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
3625
- const abs = import_node_path17.default.join(serviceDir, name);
3950
+ const abs = import_node_path19.default.join(serviceDir, name);
3626
3951
  if (!await exists(abs)) continue;
3627
3952
  const raw = await readYaml(abs);
3628
3953
  if (!raw?.services) return [];
@@ -3663,8 +3988,8 @@ function compatibleDriversFor(engine) {
3663
3988
  }
3664
3989
  function toDatabaseNode(config) {
3665
3990
  return {
3666
- id: (0, import_types8.databaseId)(config.host),
3667
- type: import_types8.NodeType.DatabaseNode,
3991
+ id: (0, import_types9.databaseId)(config.host),
3992
+ type: import_types9.NodeType.DatabaseNode,
3668
3993
  name: config.database || config.host,
3669
3994
  engine: config.engine,
3670
3995
  engineVersion: config.engineVersion,
@@ -3794,7 +4119,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
3794
4119
  discoveredVia: mergedDiscoveredVia
3795
4120
  });
3796
4121
  }
3797
- const relConfigFile = toPosix2(import_node_path18.default.relative(service.dir, config.sourceFile));
4122
+ const relConfigFile = toPosix2(import_node_path20.default.relative(service.dir, config.sourceFile));
3798
4123
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
3799
4124
  graph,
3800
4125
  service.pkg.name,
@@ -3803,14 +4128,14 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
3803
4128
  );
3804
4129
  nodesAdded += fn;
3805
4130
  edgesAdded += fe;
3806
- const evidenceFile = toPosix2(import_node_path18.default.relative(scanPath, config.sourceFile));
4131
+ const evidenceFile = toPosix2(import_node_path20.default.relative(scanPath, config.sourceFile));
3807
4132
  const edge = {
3808
- id: (0, import_types4.extractedEdgeId)(fileNodeId, dbNode.id, import_types8.EdgeType.CONNECTS_TO),
4133
+ id: (0, import_types4.extractedEdgeId)(fileNodeId, dbNode.id, import_types9.EdgeType.CONNECTS_TO),
3809
4134
  source: fileNodeId,
3810
4135
  target: dbNode.id,
3811
- type: import_types8.EdgeType.CONNECTS_TO,
3812
- provenance: import_types8.Provenance.EXTRACTED,
3813
- confidence: (0, import_types8.confidenceForExtracted)("structural"),
4136
+ type: import_types9.EdgeType.CONNECTS_TO,
4137
+ provenance: import_types9.Provenance.EXTRACTED,
4138
+ confidence: (0, import_types9.confidenceForExtracted)("structural"),
3814
4139
  evidence: { file: evidenceFile }
3815
4140
  };
3816
4141
  if (!graph.hasEdge(edge.id)) {
@@ -3837,15 +4162,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
3837
4162
 
3838
4163
  // src/extract/configs.ts
3839
4164
  init_cjs_shims();
3840
- var import_node_fs13 = require("fs");
3841
- var import_node_path19 = __toESM(require("path"), 1);
3842
- var import_types9 = require("@neat.is/types");
4165
+ var import_node_fs14 = require("fs");
4166
+ var import_node_path21 = __toESM(require("path"), 1);
4167
+ var import_types10 = require("@neat.is/types");
3843
4168
  async function walkConfigFiles(dir) {
3844
4169
  const out = [];
3845
4170
  async function walk(current) {
3846
- const entries = await import_node_fs13.promises.readdir(current, { withFileTypes: true });
4171
+ const entries = await import_node_fs14.promises.readdir(current, { withFileTypes: true });
3847
4172
  for (const entry2 of entries) {
3848
- const full = import_node_path19.default.join(current, entry2.name);
4173
+ const full = import_node_path21.default.join(current, entry2.name);
3849
4174
  if (entry2.isDirectory()) {
3850
4175
  if (IGNORED_DIRS.has(entry2.name)) continue;
3851
4176
  if (await isPythonVenvDir(full)) continue;
@@ -3864,19 +4189,19 @@ async function addConfigNodes(graph, services, scanPath) {
3864
4189
  for (const service of services) {
3865
4190
  const configFiles = await walkConfigFiles(service.dir);
3866
4191
  for (const file of configFiles) {
3867
- const relPath = import_node_path19.default.relative(scanPath, file);
4192
+ const relPath = import_node_path21.default.relative(scanPath, file);
3868
4193
  const node = {
3869
- id: (0, import_types9.configId)(relPath),
3870
- type: import_types9.NodeType.ConfigNode,
3871
- name: import_node_path19.default.basename(file),
4194
+ id: (0, import_types10.configId)(relPath),
4195
+ type: import_types10.NodeType.ConfigNode,
4196
+ name: import_node_path21.default.basename(file),
3872
4197
  path: relPath,
3873
- fileType: isConfigFile(import_node_path19.default.basename(file)).fileType
4198
+ fileType: isConfigFile(import_node_path21.default.basename(file)).fileType
3874
4199
  };
3875
4200
  if (!graph.hasNode(node.id)) {
3876
4201
  graph.addNode(node.id, node);
3877
4202
  nodesAdded++;
3878
4203
  }
3879
- const relToService = toPosix2(import_node_path19.default.relative(service.dir, file));
4204
+ const relToService = toPosix2(import_node_path21.default.relative(service.dir, file));
3880
4205
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
3881
4206
  graph,
3882
4207
  service.pkg.name,
@@ -3886,13 +4211,13 @@ async function addConfigNodes(graph, services, scanPath) {
3886
4211
  nodesAdded += fn;
3887
4212
  edgesAdded += fe;
3888
4213
  const edge = {
3889
- id: (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types9.EdgeType.CONFIGURED_BY),
4214
+ id: (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types10.EdgeType.CONFIGURED_BY),
3890
4215
  source: fileNodeId,
3891
4216
  target: node.id,
3892
- type: import_types9.EdgeType.CONFIGURED_BY,
3893
- provenance: import_types9.Provenance.EXTRACTED,
3894
- confidence: (0, import_types9.confidenceForExtracted)("structural"),
3895
- evidence: { file: relPath.split(import_node_path19.default.sep).join("/") }
4217
+ type: import_types10.EdgeType.CONFIGURED_BY,
4218
+ provenance: import_types10.Provenance.EXTRACTED,
4219
+ confidence: (0, import_types10.confidenceForExtracted)("structural"),
4220
+ evidence: { file: relPath.split(import_node_path21.default.sep).join("/") }
3896
4221
  };
3897
4222
  if (!graph.hasEdge(edge.id)) {
3898
4223
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -3905,15 +4230,15 @@ async function addConfigNodes(graph, services, scanPath) {
3905
4230
 
3906
4231
  // src/extract/calls/index.ts
3907
4232
  init_cjs_shims();
3908
- var import_types15 = require("@neat.is/types");
4233
+ var import_types16 = require("@neat.is/types");
3909
4234
 
3910
4235
  // src/extract/calls/http.ts
3911
4236
  init_cjs_shims();
3912
- var import_node_path20 = __toESM(require("path"), 1);
3913
- var import_tree_sitter = __toESM(require("tree-sitter"), 1);
3914
- var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
3915
- var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
3916
- var import_types10 = require("@neat.is/types");
4237
+ var import_node_path22 = __toESM(require("path"), 1);
4238
+ var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
4239
+ var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
4240
+ var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
4241
+ var import_types11 = require("@neat.is/types");
3917
4242
  var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
3918
4243
  var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
3919
4244
  function isInsideJsxExternalLink(node) {
@@ -3941,14 +4266,14 @@ function collectStringLiterals(node, out) {
3941
4266
  if (child) collectStringLiterals(child, out);
3942
4267
  }
3943
4268
  }
3944
- var PARSE_CHUNK = 16384;
3945
- function parseSource(parser, source) {
4269
+ var PARSE_CHUNK2 = 16384;
4270
+ function parseSource2(parser, source) {
3946
4271
  return parser.parse(
3947
- (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK)
4272
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
3948
4273
  );
3949
4274
  }
3950
4275
  function callsFromSource(source, parser, knownHosts) {
3951
- const tree = parseSource(parser, source);
4276
+ const tree = parseSource2(parser, source);
3952
4277
  const literals = [];
3953
4278
  collectStringLiterals(tree.rootNode, literals);
3954
4279
  const out = [];
@@ -3962,25 +4287,25 @@ function callsFromSource(source, parser, knownHosts) {
3962
4287
  }
3963
4288
  return out;
3964
4289
  }
3965
- function makeJsParser() {
3966
- const p = new import_tree_sitter.default();
3967
- p.setLanguage(import_tree_sitter_javascript.default);
4290
+ function makeJsParser2() {
4291
+ const p = new import_tree_sitter2.default();
4292
+ p.setLanguage(import_tree_sitter_javascript2.default);
3968
4293
  return p;
3969
4294
  }
3970
- function makePyParser() {
3971
- const p = new import_tree_sitter.default();
3972
- p.setLanguage(import_tree_sitter_python.default);
4295
+ function makePyParser2() {
4296
+ const p = new import_tree_sitter2.default();
4297
+ p.setLanguage(import_tree_sitter_python2.default);
3973
4298
  return p;
3974
4299
  }
3975
4300
  async function addHttpCallEdges(graph, services) {
3976
- const jsParser = makeJsParser();
3977
- const pyParser = makePyParser();
4301
+ const jsParser = makeJsParser2();
4302
+ const pyParser = makePyParser2();
3978
4303
  const knownHosts = /* @__PURE__ */ new Set();
3979
4304
  const hostToNodeId = /* @__PURE__ */ new Map();
3980
4305
  for (const service of services) {
3981
- knownHosts.add(import_node_path20.default.basename(service.dir));
4306
+ knownHosts.add(import_node_path22.default.basename(service.dir));
3982
4307
  knownHosts.add(service.pkg.name);
3983
- hostToNodeId.set(import_node_path20.default.basename(service.dir), service.node.id);
4308
+ hostToNodeId.set(import_node_path22.default.basename(service.dir), service.node.id);
3984
4309
  hostToNodeId.set(service.pkg.name, service.node.id);
3985
4310
  }
3986
4311
  let nodesAdded = 0;
@@ -3990,7 +4315,7 @@ async function addHttpCallEdges(graph, services) {
3990
4315
  const seen = /* @__PURE__ */ new Set();
3991
4316
  for (const file of files) {
3992
4317
  if (isTestPath(file.path)) continue;
3993
- const parser = import_node_path20.default.extname(file.path) === ".py" ? pyParser : jsParser;
4318
+ const parser = import_node_path22.default.extname(file.path) === ".py" ? pyParser : jsParser;
3994
4319
  let sites;
3995
4320
  try {
3996
4321
  sites = callsFromSource(file.content, parser, knownHosts);
@@ -3999,14 +4324,14 @@ async function addHttpCallEdges(graph, services) {
3999
4324
  continue;
4000
4325
  }
4001
4326
  if (sites.length === 0) continue;
4002
- const relFile = toPosix2(import_node_path20.default.relative(service.dir, file.path));
4327
+ const relFile = toPosix2(import_node_path22.default.relative(service.dir, file.path));
4003
4328
  for (const site of sites) {
4004
4329
  const targetId = hostToNodeId.get(site.host);
4005
4330
  if (!targetId || targetId === service.node.id) continue;
4006
4331
  const dedupKey = `${relFile}|${targetId}`;
4007
4332
  if (seen.has(dedupKey)) continue;
4008
4333
  seen.add(dedupKey);
4009
- const confidence = (0, import_types10.confidenceForExtracted)("hostname-shape-match");
4334
+ const confidence = (0, import_types11.confidenceForExtracted)("hostname-shape-match");
4010
4335
  const ev = {
4011
4336
  file: relFile,
4012
4337
  line: site.line,
@@ -4020,25 +4345,25 @@ async function addHttpCallEdges(graph, services) {
4020
4345
  );
4021
4346
  nodesAdded += n;
4022
4347
  edgesAdded += e;
4023
- if (!(0, import_types10.passesExtractedFloor)(confidence)) {
4348
+ if (!(0, import_types11.passesExtractedFloor)(confidence)) {
4024
4349
  noteExtractedDropped({
4025
4350
  source: fileNodeId,
4026
4351
  target: targetId,
4027
- type: import_types10.EdgeType.CALLS,
4352
+ type: import_types11.EdgeType.CALLS,
4028
4353
  confidence,
4029
4354
  confidenceKind: "hostname-shape-match",
4030
4355
  evidence: ev
4031
4356
  });
4032
4357
  continue;
4033
4358
  }
4034
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types10.EdgeType.CALLS);
4359
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types11.EdgeType.CALLS);
4035
4360
  if (!graph.hasEdge(edgeId)) {
4036
4361
  const edge = {
4037
4362
  id: edgeId,
4038
4363
  source: fileNodeId,
4039
4364
  target: targetId,
4040
- type: import_types10.EdgeType.CALLS,
4041
- provenance: import_types10.Provenance.EXTRACTED,
4365
+ type: import_types11.EdgeType.CALLS,
4366
+ provenance: import_types11.Provenance.EXTRACTED,
4042
4367
  confidence,
4043
4368
  evidence: ev
4044
4369
  };
@@ -4053,8 +4378,8 @@ async function addHttpCallEdges(graph, services) {
4053
4378
 
4054
4379
  // src/extract/calls/kafka.ts
4055
4380
  init_cjs_shims();
4056
- var import_node_path21 = __toESM(require("path"), 1);
4057
- var import_types11 = require("@neat.is/types");
4381
+ var import_node_path23 = __toESM(require("path"), 1);
4382
+ var import_types12 = require("@neat.is/types");
4058
4383
  var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
4059
4384
  var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
4060
4385
  function findAll(re, text) {
@@ -4075,7 +4400,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
4075
4400
  seen.add(key);
4076
4401
  const line = lineOf(file.content, topic);
4077
4402
  out.push({
4078
- infraId: (0, import_types11.infraId)("kafka-topic", topic),
4403
+ infraId: (0, import_types12.infraId)("kafka-topic", topic),
4079
4404
  name: topic,
4080
4405
  kind: "kafka-topic",
4081
4406
  edgeType,
@@ -4084,7 +4409,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
4084
4409
  // tier (ADR-066).
4085
4410
  confidenceKind: "verified-call-site",
4086
4411
  evidence: {
4087
- file: import_node_path21.default.relative(serviceDir, file.path),
4412
+ file: import_node_path23.default.relative(serviceDir, file.path),
4088
4413
  line,
4089
4414
  snippet: snippet(file.content, line)
4090
4415
  }
@@ -4097,8 +4422,8 @@ function kafkaEndpointsFromFile(file, serviceDir) {
4097
4422
 
4098
4423
  // src/extract/calls/redis.ts
4099
4424
  init_cjs_shims();
4100
- var import_node_path22 = __toESM(require("path"), 1);
4101
- var import_types12 = require("@neat.is/types");
4425
+ var import_node_path24 = __toESM(require("path"), 1);
4426
+ var import_types13 = require("@neat.is/types");
4102
4427
  var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
4103
4428
  function redisEndpointsFromFile(file, serviceDir) {
4104
4429
  const out = [];
@@ -4111,7 +4436,7 @@ function redisEndpointsFromFile(file, serviceDir) {
4111
4436
  seen.add(host);
4112
4437
  const line = lineOf(file.content, host);
4113
4438
  out.push({
4114
- infraId: (0, import_types12.infraId)("redis", host),
4439
+ infraId: (0, import_types13.infraId)("redis", host),
4115
4440
  name: host,
4116
4441
  kind: "redis",
4117
4442
  edgeType: "CALLS",
@@ -4120,7 +4445,7 @@ function redisEndpointsFromFile(file, serviceDir) {
4120
4445
  // support tier (ADR-066).
4121
4446
  confidenceKind: "url-with-structural-support",
4122
4447
  evidence: {
4123
- file: import_node_path22.default.relative(serviceDir, file.path),
4448
+ file: import_node_path24.default.relative(serviceDir, file.path),
4124
4449
  line,
4125
4450
  snippet: snippet(file.content, line)
4126
4451
  }
@@ -4131,8 +4456,8 @@ function redisEndpointsFromFile(file, serviceDir) {
4131
4456
 
4132
4457
  // src/extract/calls/aws.ts
4133
4458
  init_cjs_shims();
4134
- var import_node_path23 = __toESM(require("path"), 1);
4135
- var import_types13 = require("@neat.is/types");
4459
+ var import_node_path25 = __toESM(require("path"), 1);
4460
+ var import_types14 = require("@neat.is/types");
4136
4461
  var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
4137
4462
  var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
4138
4463
  function hasMarker(text, markers) {
@@ -4156,7 +4481,7 @@ function awsEndpointsFromFile(file, serviceDir) {
4156
4481
  seen.add(key);
4157
4482
  const line = lineOf(file.content, name);
4158
4483
  out.push({
4159
- infraId: (0, import_types13.infraId)(kind, name),
4484
+ infraId: (0, import_types14.infraId)(kind, name),
4160
4485
  name,
4161
4486
  kind,
4162
4487
  edgeType: "CALLS",
@@ -4165,7 +4490,7 @@ function awsEndpointsFromFile(file, serviceDir) {
4165
4490
  // (ADR-066).
4166
4491
  confidenceKind: "verified-call-site",
4167
4492
  evidence: {
4168
- file: import_node_path23.default.relative(serviceDir, file.path),
4493
+ file: import_node_path25.default.relative(serviceDir, file.path),
4169
4494
  line,
4170
4495
  snippet: snippet(file.content, line)
4171
4496
  }
@@ -4190,8 +4515,8 @@ function awsEndpointsFromFile(file, serviceDir) {
4190
4515
 
4191
4516
  // src/extract/calls/grpc.ts
4192
4517
  init_cjs_shims();
4193
- var import_node_path24 = __toESM(require("path"), 1);
4194
- var import_types14 = require("@neat.is/types");
4518
+ var import_node_path26 = __toESM(require("path"), 1);
4519
+ var import_types15 = require("@neat.is/types");
4195
4520
  var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
4196
4521
  var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
4197
4522
  var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
@@ -4240,7 +4565,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
4240
4565
  const { kind } = classified;
4241
4566
  const line = lineOf(file.content, m[0]);
4242
4567
  out.push({
4243
- infraId: (0, import_types14.infraId)(kind, name),
4568
+ infraId: (0, import_types15.infraId)(kind, name),
4244
4569
  name,
4245
4570
  kind,
4246
4571
  edgeType: "CALLS",
@@ -4249,7 +4574,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
4249
4574
  // tier (ADR-066).
4250
4575
  confidenceKind: "verified-call-site",
4251
4576
  evidence: {
4252
- file: import_node_path24.default.relative(serviceDir, file.path),
4577
+ file: import_node_path26.default.relative(serviceDir, file.path),
4253
4578
  line,
4254
4579
  snippet: snippet(file.content, line)
4255
4580
  }
@@ -4262,11 +4587,11 @@ function grpcEndpointsFromFile(file, serviceDir) {
4262
4587
  function edgeTypeFromEndpoint(ep) {
4263
4588
  switch (ep.edgeType) {
4264
4589
  case "PUBLISHES_TO":
4265
- return import_types15.EdgeType.PUBLISHES_TO;
4590
+ return import_types16.EdgeType.PUBLISHES_TO;
4266
4591
  case "CONSUMES_FROM":
4267
- return import_types15.EdgeType.CONSUMES_FROM;
4592
+ return import_types16.EdgeType.CONSUMES_FROM;
4268
4593
  default:
4269
- return import_types15.EdgeType.CALLS;
4594
+ return import_types16.EdgeType.CALLS;
4270
4595
  }
4271
4596
  }
4272
4597
  function isAwsKind(kind) {
@@ -4293,7 +4618,7 @@ async function addExternalEndpointEdges(graph, services) {
4293
4618
  if (!graph.hasNode(ep.infraId)) {
4294
4619
  const node = {
4295
4620
  id: ep.infraId,
4296
- type: import_types15.NodeType.InfraNode,
4621
+ type: import_types16.NodeType.InfraNode,
4297
4622
  name: ep.name,
4298
4623
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
4299
4624
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -4305,7 +4630,7 @@ async function addExternalEndpointEdges(graph, services) {
4305
4630
  nodesAdded++;
4306
4631
  }
4307
4632
  const edgeType = edgeTypeFromEndpoint(ep);
4308
- const confidence = (0, import_types15.confidenceForExtracted)(ep.confidenceKind);
4633
+ const confidence = (0, import_types16.confidenceForExtracted)(ep.confidenceKind);
4309
4634
  const relFile = toPosix2(ep.evidence.file);
4310
4635
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
4311
4636
  graph,
@@ -4315,7 +4640,7 @@ async function addExternalEndpointEdges(graph, services) {
4315
4640
  );
4316
4641
  nodesAdded += n;
4317
4642
  edgesAdded += e;
4318
- if (!(0, import_types15.passesExtractedFloor)(confidence)) {
4643
+ if (!(0, import_types16.passesExtractedFloor)(confidence)) {
4319
4644
  noteExtractedDropped({
4320
4645
  source: fileNodeId,
4321
4646
  target: ep.infraId,
@@ -4335,7 +4660,7 @@ async function addExternalEndpointEdges(graph, services) {
4335
4660
  source: fileNodeId,
4336
4661
  target: ep.infraId,
4337
4662
  type: edgeType,
4338
- provenance: import_types15.Provenance.EXTRACTED,
4663
+ provenance: import_types16.Provenance.EXTRACTED,
4339
4664
  confidence,
4340
4665
  evidence: ep.evidence
4341
4666
  };
@@ -4360,16 +4685,16 @@ init_cjs_shims();
4360
4685
 
4361
4686
  // src/extract/infra/docker-compose.ts
4362
4687
  init_cjs_shims();
4363
- var import_node_path25 = __toESM(require("path"), 1);
4364
- var import_types17 = require("@neat.is/types");
4688
+ var import_node_path27 = __toESM(require("path"), 1);
4689
+ var import_types18 = require("@neat.is/types");
4365
4690
 
4366
4691
  // src/extract/infra/shared.ts
4367
4692
  init_cjs_shims();
4368
- var import_types16 = require("@neat.is/types");
4693
+ var import_types17 = require("@neat.is/types");
4369
4694
  function makeInfraNode(kind, name, provider = "self", extras) {
4370
4695
  return {
4371
- id: (0, import_types16.infraId)(kind, name),
4372
- type: import_types16.NodeType.InfraNode,
4696
+ id: (0, import_types17.infraId)(kind, name),
4697
+ type: import_types17.NodeType.InfraNode,
4373
4698
  name,
4374
4699
  provider,
4375
4700
  kind,
@@ -4398,7 +4723,7 @@ function dependsOnList(value) {
4398
4723
  }
4399
4724
  function serviceNameToServiceNode(name, services) {
4400
4725
  for (const s of services) {
4401
- if (s.node.name === name || import_node_path25.default.basename(s.dir) === name) return s.node.id;
4726
+ if (s.node.name === name || import_node_path27.default.basename(s.dir) === name) return s.node.id;
4402
4727
  }
4403
4728
  return null;
4404
4729
  }
@@ -4407,7 +4732,7 @@ async function addComposeInfra(graph, scanPath, services) {
4407
4732
  let edgesAdded = 0;
4408
4733
  let composePath = null;
4409
4734
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
4410
- const abs = import_node_path25.default.join(scanPath, name);
4735
+ const abs = import_node_path27.default.join(scanPath, name);
4411
4736
  if (await exists(abs)) {
4412
4737
  composePath = abs;
4413
4738
  break;
@@ -4420,13 +4745,13 @@ async function addComposeInfra(graph, scanPath, services) {
4420
4745
  } catch (err) {
4421
4746
  recordExtractionError(
4422
4747
  "infra docker-compose",
4423
- import_node_path25.default.relative(scanPath, composePath),
4748
+ import_node_path27.default.relative(scanPath, composePath),
4424
4749
  err
4425
4750
  );
4426
4751
  return { nodesAdded, edgesAdded };
4427
4752
  }
4428
4753
  if (!compose?.services) return { nodesAdded, edgesAdded };
4429
- const evidenceFile = import_node_path25.default.relative(scanPath, composePath).split(import_node_path25.default.sep).join("/");
4754
+ const evidenceFile = import_node_path27.default.relative(scanPath, composePath).split(import_node_path27.default.sep).join("/");
4430
4755
  const composeNameToNodeId = /* @__PURE__ */ new Map();
4431
4756
  for (const [composeName, svc] of Object.entries(compose.services)) {
4432
4757
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -4448,15 +4773,15 @@ async function addComposeInfra(graph, scanPath, services) {
4448
4773
  for (const dep of dependsOnList(svc.depends_on)) {
4449
4774
  const targetId = composeNameToNodeId.get(dep);
4450
4775
  if (!targetId) continue;
4451
- const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types17.EdgeType.DEPENDS_ON);
4776
+ const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types18.EdgeType.DEPENDS_ON);
4452
4777
  if (graph.hasEdge(edgeId)) continue;
4453
4778
  const edge = {
4454
4779
  id: edgeId,
4455
4780
  source: sourceId,
4456
4781
  target: targetId,
4457
- type: import_types17.EdgeType.DEPENDS_ON,
4458
- provenance: import_types17.Provenance.EXTRACTED,
4459
- confidence: (0, import_types17.confidenceForExtracted)("structural"),
4782
+ type: import_types18.EdgeType.DEPENDS_ON,
4783
+ provenance: import_types18.Provenance.EXTRACTED,
4784
+ confidence: (0, import_types18.confidenceForExtracted)("structural"),
4460
4785
  evidence: { file: evidenceFile }
4461
4786
  };
4462
4787
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -4468,9 +4793,9 @@ async function addComposeInfra(graph, scanPath, services) {
4468
4793
 
4469
4794
  // src/extract/infra/dockerfile.ts
4470
4795
  init_cjs_shims();
4471
- var import_node_path26 = __toESM(require("path"), 1);
4472
- var import_node_fs14 = require("fs");
4473
- var import_types18 = require("@neat.is/types");
4796
+ var import_node_path28 = __toESM(require("path"), 1);
4797
+ var import_node_fs15 = require("fs");
4798
+ var import_types19 = require("@neat.is/types");
4474
4799
  function runtimeImage(content) {
4475
4800
  const lines = content.split("\n");
4476
4801
  let last = null;
@@ -4489,15 +4814,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4489
4814
  let nodesAdded = 0;
4490
4815
  let edgesAdded = 0;
4491
4816
  for (const service of services) {
4492
- const dockerfilePath = import_node_path26.default.join(service.dir, "Dockerfile");
4817
+ const dockerfilePath = import_node_path28.default.join(service.dir, "Dockerfile");
4493
4818
  if (!await exists(dockerfilePath)) continue;
4494
4819
  let content;
4495
4820
  try {
4496
- content = await import_node_fs14.promises.readFile(dockerfilePath, "utf8");
4821
+ content = await import_node_fs15.promises.readFile(dockerfilePath, "utf8");
4497
4822
  } catch (err) {
4498
4823
  recordExtractionError(
4499
4824
  "infra dockerfile",
4500
- import_node_path26.default.relative(scanPath, dockerfilePath),
4825
+ import_node_path28.default.relative(scanPath, dockerfilePath),
4501
4826
  err
4502
4827
  );
4503
4828
  continue;
@@ -4509,7 +4834,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4509
4834
  graph.addNode(node.id, node);
4510
4835
  nodesAdded++;
4511
4836
  }
4512
- const relDockerfile = toPosix2(import_node_path26.default.relative(service.dir, dockerfilePath));
4837
+ const relDockerfile = toPosix2(import_node_path28.default.relative(service.dir, dockerfilePath));
4513
4838
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
4514
4839
  graph,
4515
4840
  service.pkg.name,
@@ -4518,17 +4843,17 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4518
4843
  );
4519
4844
  nodesAdded += fn;
4520
4845
  edgesAdded += fe;
4521
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types18.EdgeType.RUNS_ON);
4846
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types19.EdgeType.RUNS_ON);
4522
4847
  if (!graph.hasEdge(edgeId)) {
4523
4848
  const edge = {
4524
4849
  id: edgeId,
4525
4850
  source: fileNodeId,
4526
4851
  target: node.id,
4527
- type: import_types18.EdgeType.RUNS_ON,
4528
- provenance: import_types18.Provenance.EXTRACTED,
4529
- confidence: (0, import_types18.confidenceForExtracted)("structural"),
4852
+ type: import_types19.EdgeType.RUNS_ON,
4853
+ provenance: import_types19.Provenance.EXTRACTED,
4854
+ confidence: (0, import_types19.confidenceForExtracted)("structural"),
4530
4855
  evidence: {
4531
- file: toPosix2(import_node_path26.default.relative(scanPath, dockerfilePath))
4856
+ file: toPosix2(import_node_path28.default.relative(scanPath, dockerfilePath))
4532
4857
  }
4533
4858
  };
4534
4859
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -4540,21 +4865,21 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4540
4865
 
4541
4866
  // src/extract/infra/terraform.ts
4542
4867
  init_cjs_shims();
4543
- var import_node_fs15 = require("fs");
4544
- var import_node_path27 = __toESM(require("path"), 1);
4868
+ var import_node_fs16 = require("fs");
4869
+ var import_node_path29 = __toESM(require("path"), 1);
4545
4870
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
4546
4871
  async function walkTfFiles(start, depth = 0, max = 5) {
4547
4872
  if (depth > max) return [];
4548
4873
  const out = [];
4549
- const entries = await import_node_fs15.promises.readdir(start, { withFileTypes: true }).catch(() => []);
4874
+ const entries = await import_node_fs16.promises.readdir(start, { withFileTypes: true }).catch(() => []);
4550
4875
  for (const entry2 of entries) {
4551
4876
  if (entry2.isDirectory()) {
4552
4877
  if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
4553
- const child = import_node_path27.default.join(start, entry2.name);
4878
+ const child = import_node_path29.default.join(start, entry2.name);
4554
4879
  if (await isPythonVenvDir(child)) continue;
4555
4880
  out.push(...await walkTfFiles(child, depth + 1, max));
4556
4881
  } else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
4557
- out.push(import_node_path27.default.join(start, entry2.name));
4882
+ out.push(import_node_path29.default.join(start, entry2.name));
4558
4883
  }
4559
4884
  }
4560
4885
  return out;
@@ -4563,7 +4888,7 @@ async function addTerraformResources(graph, scanPath) {
4563
4888
  let nodesAdded = 0;
4564
4889
  const files = await walkTfFiles(scanPath);
4565
4890
  for (const file of files) {
4566
- const content = await import_node_fs15.promises.readFile(file, "utf8");
4891
+ const content = await import_node_fs16.promises.readFile(file, "utf8");
4567
4892
  RESOURCE_RE.lastIndex = 0;
4568
4893
  let m;
4569
4894
  while ((m = RESOURCE_RE.exec(content)) !== null) {
@@ -4581,8 +4906,8 @@ async function addTerraformResources(graph, scanPath) {
4581
4906
 
4582
4907
  // src/extract/infra/k8s.ts
4583
4908
  init_cjs_shims();
4584
- var import_node_fs16 = require("fs");
4585
- var import_node_path28 = __toESM(require("path"), 1);
4909
+ var import_node_fs17 = require("fs");
4910
+ var import_node_path30 = __toESM(require("path"), 1);
4586
4911
  var import_yaml3 = require("yaml");
4587
4912
  var K8S_KIND_TO_INFRA_KIND = {
4588
4913
  Service: "k8s-service",
@@ -4596,15 +4921,15 @@ var K8S_KIND_TO_INFRA_KIND = {
4596
4921
  async function walkYamlFiles2(start, depth = 0, max = 5) {
4597
4922
  if (depth > max) return [];
4598
4923
  const out = [];
4599
- const entries = await import_node_fs16.promises.readdir(start, { withFileTypes: true }).catch(() => []);
4924
+ const entries = await import_node_fs17.promises.readdir(start, { withFileTypes: true }).catch(() => []);
4600
4925
  for (const entry2 of entries) {
4601
4926
  if (entry2.isDirectory()) {
4602
4927
  if (IGNORED_DIRS.has(entry2.name)) continue;
4603
- const child = import_node_path28.default.join(start, entry2.name);
4928
+ const child = import_node_path30.default.join(start, entry2.name);
4604
4929
  if (await isPythonVenvDir(child)) continue;
4605
4930
  out.push(...await walkYamlFiles2(child, depth + 1, max));
4606
- } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path28.default.extname(entry2.name))) {
4607
- out.push(import_node_path28.default.join(start, entry2.name));
4931
+ } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path30.default.extname(entry2.name))) {
4932
+ out.push(import_node_path30.default.join(start, entry2.name));
4608
4933
  }
4609
4934
  }
4610
4935
  return out;
@@ -4613,7 +4938,7 @@ async function addK8sResources(graph, scanPath) {
4613
4938
  let nodesAdded = 0;
4614
4939
  const files = await walkYamlFiles2(scanPath);
4615
4940
  for (const file of files) {
4616
- const content = await import_node_fs16.promises.readFile(file, "utf8");
4941
+ const content = await import_node_fs17.promises.readFile(file, "utf8");
4617
4942
  let docs;
4618
4943
  try {
4619
4944
  docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
@@ -4648,17 +4973,17 @@ async function addInfra(graph, scanPath, services) {
4648
4973
  }
4649
4974
 
4650
4975
  // src/extract/index.ts
4651
- var import_node_path30 = __toESM(require("path"), 1);
4976
+ var import_node_path32 = __toESM(require("path"), 1);
4652
4977
 
4653
4978
  // src/extract/retire.ts
4654
4979
  init_cjs_shims();
4655
- var import_node_fs17 = require("fs");
4656
- var import_node_path29 = __toESM(require("path"), 1);
4657
- var import_types19 = require("@neat.is/types");
4980
+ var import_node_fs18 = require("fs");
4981
+ var import_node_path31 = __toESM(require("path"), 1);
4982
+ var import_types20 = require("@neat.is/types");
4658
4983
  function dropOrphanedFileNodes(graph) {
4659
4984
  const orphans = [];
4660
4985
  graph.forEachNode((id, attrs) => {
4661
- if (attrs.type !== import_types19.NodeType.FileNode) return;
4986
+ if (attrs.type !== import_types20.NodeType.FileNode) return;
4662
4987
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
4663
4988
  orphans.push(id);
4664
4989
  }
@@ -4671,7 +4996,7 @@ function retireEdgesByFile(graph, file) {
4671
4996
  const toDrop = [];
4672
4997
  graph.forEachEdge((id, attrs) => {
4673
4998
  const edge = attrs;
4674
- if (edge.provenance !== import_types19.Provenance.EXTRACTED) return;
4999
+ if (edge.provenance !== import_types20.Provenance.EXTRACTED) return;
4675
5000
  if (!edge.evidence?.file) return;
4676
5001
  if (edge.evidence.file === normalized) toDrop.push(id);
4677
5002
  });
@@ -4684,14 +5009,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
4684
5009
  const bases = [scanPath, ...serviceDirs];
4685
5010
  graph.forEachEdge((id, attrs) => {
4686
5011
  const edge = attrs;
4687
- if (edge.provenance !== import_types19.Provenance.EXTRACTED) return;
5012
+ if (edge.provenance !== import_types20.Provenance.EXTRACTED) return;
4688
5013
  const evidenceFile = edge.evidence?.file;
4689
5014
  if (!evidenceFile) return;
4690
- if (import_node_path29.default.isAbsolute(evidenceFile)) {
4691
- if (!(0, import_node_fs17.existsSync)(evidenceFile)) toDrop.push(id);
5015
+ if (import_node_path31.default.isAbsolute(evidenceFile)) {
5016
+ if (!(0, import_node_fs18.existsSync)(evidenceFile)) toDrop.push(id);
4692
5017
  return;
4693
5018
  }
4694
- const found = bases.some((base) => (0, import_node_fs17.existsSync)(import_node_path29.default.join(base, evidenceFile)));
5019
+ const found = bases.some((base) => (0, import_node_fs18.existsSync)(import_node_path31.default.join(base, evidenceFile)));
4695
5020
  if (!found) toDrop.push(id);
4696
5021
  });
4697
5022
  for (const id of toDrop) graph.dropEdge(id);
@@ -4706,6 +5031,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
4706
5031
  const services = await discoverServices(scanPath);
4707
5032
  const phase1Nodes = addServiceNodes(graph, services);
4708
5033
  await addServiceAliases(graph, scanPath, services);
5034
+ const fileEnum = await addFiles(graph, services);
5035
+ const importGraph = await addImports(graph, services);
4709
5036
  const phase2 = await addDatabasesAndCompat(graph, services, scanPath);
4710
5037
  const phase3 = await addConfigNodes(graph, services, scanPath);
4711
5038
  const phase4 = await addCallEdges(graph, services);
@@ -4729,7 +5056,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
4729
5056
  }
4730
5057
  const droppedEntries = drainDroppedExtracted();
4731
5058
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
4732
- const rejectedPath = import_node_path30.default.join(import_node_path30.default.dirname(opts.errorsPath), "rejected.ndjson");
5059
+ const rejectedPath = import_node_path32.default.join(import_node_path32.default.dirname(opts.errorsPath), "rejected.ndjson");
4733
5060
  try {
4734
5061
  await writeRejectedExtracted(droppedEntries, rejectedPath);
4735
5062
  } catch (err) {
@@ -4739,8 +5066,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
4739
5066
  }
4740
5067
  }
4741
5068
  const result = {
4742
- nodesAdded: phase1Nodes + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
4743
- edgesAdded: phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
5069
+ nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
5070
+ edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
4744
5071
  frontiersPromoted,
4745
5072
  extractionErrors: errorEntries.length,
4746
5073
  errorEntries,
@@ -4763,7 +5090,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
4763
5090
 
4764
5091
  // src/divergences.ts
4765
5092
  init_cjs_shims();
4766
- var import_types20 = require("@neat.is/types");
5093
+ var import_types21 = require("@neat.is/types");
4767
5094
  function bucketKey(source, target, type) {
4768
5095
  return `${type}|${source}|${target}`;
4769
5096
  }
@@ -4771,22 +5098,22 @@ function bucketEdges(graph) {
4771
5098
  const buckets = /* @__PURE__ */ new Map();
4772
5099
  graph.forEachEdge((id, attrs) => {
4773
5100
  const e = attrs;
4774
- const parsed = (0, import_types20.parseEdgeId)(id);
5101
+ const parsed = (0, import_types21.parseEdgeId)(id);
4775
5102
  const provenance = parsed?.provenance ?? e.provenance;
4776
5103
  const key = bucketKey(e.source, e.target, e.type);
4777
5104
  const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
4778
5105
  switch (provenance) {
4779
- case import_types20.Provenance.EXTRACTED:
5106
+ case import_types21.Provenance.EXTRACTED:
4780
5107
  cur.extracted = e;
4781
5108
  break;
4782
- case import_types20.Provenance.OBSERVED:
5109
+ case import_types21.Provenance.OBSERVED:
4783
5110
  cur.observed = e;
4784
5111
  break;
4785
- case import_types20.Provenance.INFERRED:
5112
+ case import_types21.Provenance.INFERRED:
4786
5113
  cur.inferred = e;
4787
5114
  break;
4788
5115
  default:
4789
- if (e.provenance === import_types20.Provenance.STALE) cur.stale = e;
5116
+ if (e.provenance === import_types21.Provenance.STALE) cur.stale = e;
4790
5117
  }
4791
5118
  buckets.set(key, cur);
4792
5119
  });
@@ -4795,7 +5122,7 @@ function bucketEdges(graph) {
4795
5122
  function nodeIsFrontier(graph, nodeId) {
4796
5123
  if (!graph.hasNode(nodeId)) return false;
4797
5124
  const attrs = graph.getNodeAttributes(nodeId);
4798
- return attrs.type === import_types20.NodeType.FrontierNode;
5125
+ return attrs.type === import_types21.NodeType.FrontierNode;
4799
5126
  }
4800
5127
  function clampConfidence(n) {
4801
5128
  if (!Number.isFinite(n)) return 0;
@@ -4816,7 +5143,7 @@ function gradedConfidence(edge) {
4816
5143
  }
4817
5144
  function detectMissingDivergences(graph, bucket) {
4818
5145
  const out = [];
4819
- if (bucket.type === import_types20.EdgeType.CONTAINS) return out;
5146
+ if (bucket.type === import_types21.EdgeType.CONTAINS) return out;
4820
5147
  if (bucket.extracted && !bucket.observed) {
4821
5148
  if (!nodeIsFrontier(graph, bucket.target)) {
4822
5149
  out.push({
@@ -4857,7 +5184,7 @@ function declaredHostFor(svc) {
4857
5184
  function hasExtractedConfiguredBy(graph, svcId) {
4858
5185
  for (const edgeId of graph.outboundEdges(svcId)) {
4859
5186
  const e = graph.getEdgeAttributes(edgeId);
4860
- if (e.type === import_types20.EdgeType.CONFIGURED_BY && e.provenance === import_types20.Provenance.EXTRACTED) {
5187
+ if (e.type === import_types21.EdgeType.CONFIGURED_BY && e.provenance === import_types21.Provenance.EXTRACTED) {
4861
5188
  return true;
4862
5189
  }
4863
5190
  }
@@ -4870,10 +5197,10 @@ function detectHostMismatch(graph, svcId, svc) {
4870
5197
  const out = [];
4871
5198
  for (const edgeId of graph.outboundEdges(svcId)) {
4872
5199
  const edge = graph.getEdgeAttributes(edgeId);
4873
- if (edge.type !== import_types20.EdgeType.CONNECTS_TO) continue;
4874
- if (edge.provenance !== import_types20.Provenance.OBSERVED) continue;
5200
+ if (edge.type !== import_types21.EdgeType.CONNECTS_TO) continue;
5201
+ if (edge.provenance !== import_types21.Provenance.OBSERVED) continue;
4875
5202
  const target = graph.getNodeAttributes(edge.target);
4876
- if (target.type !== import_types20.NodeType.DatabaseNode) continue;
5203
+ if (target.type !== import_types21.NodeType.DatabaseNode) continue;
4877
5204
  const observedHost = target.host?.trim();
4878
5205
  if (!observedHost) continue;
4879
5206
  if (observedHost === declaredHost) continue;
@@ -4895,10 +5222,10 @@ function detectCompatDivergences(graph, svcId, svc) {
4895
5222
  const deps = svc.dependencies ?? {};
4896
5223
  for (const edgeId of graph.outboundEdges(svcId)) {
4897
5224
  const edge = graph.getEdgeAttributes(edgeId);
4898
- if (edge.type !== import_types20.EdgeType.CONNECTS_TO) continue;
4899
- if (edge.provenance !== import_types20.Provenance.OBSERVED) continue;
5225
+ if (edge.type !== import_types21.EdgeType.CONNECTS_TO) continue;
5226
+ if (edge.provenance !== import_types21.Provenance.OBSERVED) continue;
4900
5227
  const target = graph.getNodeAttributes(edge.target);
4901
- if (target.type !== import_types20.NodeType.DatabaseNode) continue;
5228
+ if (target.type !== import_types21.NodeType.DatabaseNode) continue;
4902
5229
  for (const pair of compatPairs()) {
4903
5230
  if (pair.engine !== target.engine) continue;
4904
5231
  const declared = deps[pair.driver];
@@ -4959,7 +5286,7 @@ function computeDivergences(graph, opts = {}) {
4959
5286
  }
4960
5287
  graph.forEachNode((nodeId, attrs) => {
4961
5288
  const n = attrs;
4962
- if (n.type !== import_types20.NodeType.ServiceNode) return;
5289
+ if (n.type !== import_types21.NodeType.ServiceNode) return;
4963
5290
  const svc = n;
4964
5291
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
4965
5292
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
@@ -4992,7 +5319,7 @@ function computeDivergences(graph, opts = {}) {
4992
5319
  if (a.source !== b.source) return a.source.localeCompare(b.source);
4993
5320
  return a.target.localeCompare(b.target);
4994
5321
  });
4995
- return import_types20.DivergenceResultSchema.parse({
5322
+ return import_types21.DivergenceResultSchema.parse({
4996
5323
  divergences: filtered,
4997
5324
  totalAffected: filtered.length,
4998
5325
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -5001,9 +5328,9 @@ function computeDivergences(graph, opts = {}) {
5001
5328
 
5002
5329
  // src/persist.ts
5003
5330
  init_cjs_shims();
5004
- var import_node_fs18 = require("fs");
5005
- var import_node_path31 = __toESM(require("path"), 1);
5006
- var import_types21 = require("@neat.is/types");
5331
+ var import_node_fs19 = require("fs");
5332
+ var import_node_path33 = __toESM(require("path"), 1);
5333
+ var import_types22 = require("@neat.is/types");
5007
5334
  var SCHEMA_VERSION = 4;
5008
5335
  function migrateV1ToV2(payload) {
5009
5336
  const nodes = payload.graph.nodes;
@@ -5025,12 +5352,12 @@ function migrateV2ToV3(payload) {
5025
5352
  for (const edge of edges) {
5026
5353
  const attrs = edge.attributes;
5027
5354
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
5028
- attrs.provenance = import_types21.Provenance.OBSERVED;
5355
+ attrs.provenance = import_types22.Provenance.OBSERVED;
5029
5356
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
5030
5357
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
5031
5358
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
5032
5359
  if (type && source && target) {
5033
- const newId = (0, import_types21.observedEdgeId)(source, target, type);
5360
+ const newId = (0, import_types22.observedEdgeId)(source, target, type);
5034
5361
  attrs.id = newId;
5035
5362
  if (edge.key) edge.key = newId;
5036
5363
  }
@@ -5039,7 +5366,7 @@ function migrateV2ToV3(payload) {
5039
5366
  return { ...payload, schemaVersion: 3 };
5040
5367
  }
5041
5368
  async function ensureDir(filePath) {
5042
- await import_node_fs18.promises.mkdir(import_node_path31.default.dirname(filePath), { recursive: true });
5369
+ await import_node_fs19.promises.mkdir(import_node_path33.default.dirname(filePath), { recursive: true });
5043
5370
  }
5044
5371
  async function saveGraphToDisk(graph, outPath) {
5045
5372
  await ensureDir(outPath);
@@ -5049,13 +5376,13 @@ async function saveGraphToDisk(graph, outPath) {
5049
5376
  graph: graph.export()
5050
5377
  };
5051
5378
  const tmp = `${outPath}.tmp`;
5052
- await import_node_fs18.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
5053
- await import_node_fs18.promises.rename(tmp, outPath);
5379
+ await import_node_fs19.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
5380
+ await import_node_fs19.promises.rename(tmp, outPath);
5054
5381
  }
5055
5382
  async function loadGraphFromDisk(graph, outPath) {
5056
5383
  let raw;
5057
5384
  try {
5058
- raw = await import_node_fs18.promises.readFile(outPath, "utf8");
5385
+ raw = await import_node_fs19.promises.readFile(outPath, "utf8");
5059
5386
  } catch (err) {
5060
5387
  if (err.code === "ENOENT") return;
5061
5388
  throw err;
@@ -5114,8 +5441,8 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
5114
5441
 
5115
5442
  // src/gitignore.ts
5116
5443
  init_cjs_shims();
5117
- var import_node_fs19 = require("fs");
5118
- var import_node_path32 = __toESM(require("path"), 1);
5444
+ var import_node_fs20 = require("fs");
5445
+ var import_node_path34 = __toESM(require("path"), 1);
5119
5446
  var NEAT_OUT_LINE = "neat-out/";
5120
5447
  var NEAT_HEADER = "# NEAT \u2014 machine-local snapshots and events";
5121
5448
  function isNeatOutLine(line) {
@@ -5123,15 +5450,15 @@ function isNeatOutLine(line) {
5123
5450
  return trimmed === "neat-out/" || trimmed === "neat-out";
5124
5451
  }
5125
5452
  async function ensureNeatOutIgnored(projectDir) {
5126
- const file = import_node_path32.default.join(projectDir, ".gitignore");
5453
+ const file = import_node_path34.default.join(projectDir, ".gitignore");
5127
5454
  let existing = null;
5128
5455
  try {
5129
- existing = await import_node_fs19.promises.readFile(file, "utf8");
5456
+ existing = await import_node_fs20.promises.readFile(file, "utf8");
5130
5457
  } catch (err) {
5131
5458
  if (err.code !== "ENOENT") throw err;
5132
5459
  }
5133
5460
  if (existing === null) {
5134
- await import_node_fs19.promises.writeFile(file, `${NEAT_HEADER}
5461
+ await import_node_fs20.promises.writeFile(file, `${NEAT_HEADER}
5135
5462
  ${NEAT_OUT_LINE}
5136
5463
  `, "utf8");
5137
5464
  return { action: "created", file };
@@ -5144,13 +5471,13 @@ ${NEAT_OUT_LINE}
5144
5471
  ${NEAT_HEADER}
5145
5472
  ${NEAT_OUT_LINE}
5146
5473
  `;
5147
- await import_node_fs19.promises.writeFile(file, existing + appended, "utf8");
5474
+ await import_node_fs20.promises.writeFile(file, existing + appended, "utf8");
5148
5475
  return { action: "added", file };
5149
5476
  }
5150
5477
 
5151
5478
  // src/summary.ts
5152
5479
  init_cjs_shims();
5153
- var import_types22 = require("@neat.is/types");
5480
+ var import_types23 = require("@neat.is/types");
5154
5481
  function renderOtelEnvBlock() {
5155
5482
  return [
5156
5483
  "for prod OTel routing, set these in your deploy platform's env:",
@@ -5160,19 +5487,19 @@ function renderOtelEnvBlock() {
5160
5487
  }
5161
5488
  function findIncompatServices(nodes) {
5162
5489
  return nodes.filter(
5163
- (n) => n.type === import_types22.NodeType.ServiceNode && Array.isArray(n.incompatibilities) && (n.incompatibilities ?? []).length > 0
5490
+ (n) => n.type === import_types23.NodeType.ServiceNode && Array.isArray(n.incompatibilities) && (n.incompatibilities ?? []).length > 0
5164
5491
  );
5165
5492
  }
5166
5493
  function servicesWithoutObserved(nodes, edges) {
5167
5494
  const seen = /* @__PURE__ */ new Set();
5168
5495
  for (const e of edges) {
5169
- if (e.provenance === import_types22.Provenance.OBSERVED) {
5496
+ if (e.provenance === import_types23.Provenance.OBSERVED) {
5170
5497
  seen.add(e.source);
5171
5498
  seen.add(e.target);
5172
5499
  }
5173
5500
  }
5174
5501
  return nodes.filter(
5175
- (n) => n.type === import_types22.NodeType.ServiceNode && !seen.has(n.id)
5502
+ (n) => n.type === import_types23.NodeType.ServiceNode && !seen.has(n.id)
5176
5503
  );
5177
5504
  }
5178
5505
  function formatDivergence(d) {
@@ -5246,27 +5573,27 @@ function formatIncompat(inc) {
5246
5573
 
5247
5574
  // src/watch.ts
5248
5575
  init_cjs_shims();
5249
- var import_node_fs25 = __toESM(require("fs"), 1);
5250
- var import_node_path40 = __toESM(require("path"), 1);
5576
+ var import_node_fs26 = __toESM(require("fs"), 1);
5577
+ var import_node_path42 = __toESM(require("path"), 1);
5251
5578
  var import_chokidar = __toESM(require("chokidar"), 1);
5252
5579
 
5253
5580
  // src/api.ts
5254
5581
  init_cjs_shims();
5255
5582
  var import_fastify = __toESM(require("fastify"), 1);
5256
5583
  var import_cors = __toESM(require("@fastify/cors"), 1);
5257
- var import_types24 = require("@neat.is/types");
5584
+ var import_types25 = require("@neat.is/types");
5258
5585
 
5259
5586
  // src/extend/index.ts
5260
5587
  init_cjs_shims();
5261
- var import_node_fs21 = require("fs");
5262
- var import_node_path34 = __toESM(require("path"), 1);
5588
+ var import_node_fs22 = require("fs");
5589
+ var import_node_path36 = __toESM(require("path"), 1);
5263
5590
  var import_node_os2 = __toESM(require("os"), 1);
5264
5591
  var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
5265
5592
 
5266
5593
  // src/installers/package-manager.ts
5267
5594
  init_cjs_shims();
5268
- var import_node_fs20 = require("fs");
5269
- var import_node_path33 = __toESM(require("path"), 1);
5595
+ var import_node_fs21 = require("fs");
5596
+ var import_node_path35 = __toESM(require("path"), 1);
5270
5597
  var import_node_child_process = require("child_process");
5271
5598
  var LOCKFILE_PRIORITY = [
5272
5599
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -5281,29 +5608,29 @@ var LOCKFILE_PRIORITY = [
5281
5608
  var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
5282
5609
  async function exists2(p) {
5283
5610
  try {
5284
- await import_node_fs20.promises.access(p);
5611
+ await import_node_fs21.promises.access(p);
5285
5612
  return true;
5286
5613
  } catch {
5287
5614
  return false;
5288
5615
  }
5289
5616
  }
5290
5617
  async function detectPackageManager(serviceDir) {
5291
- let dir = import_node_path33.default.resolve(serviceDir);
5618
+ let dir = import_node_path35.default.resolve(serviceDir);
5292
5619
  const stops = /* @__PURE__ */ new Set();
5293
5620
  for (let i = 0; i < 64; i++) {
5294
5621
  if (stops.has(dir)) break;
5295
5622
  stops.add(dir);
5296
5623
  for (const candidate of LOCKFILE_PRIORITY) {
5297
- const lockPath = import_node_path33.default.join(dir, candidate.lockfile);
5624
+ const lockPath = import_node_path35.default.join(dir, candidate.lockfile);
5298
5625
  if (await exists2(lockPath)) {
5299
5626
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
5300
5627
  }
5301
5628
  }
5302
- const parent = import_node_path33.default.dirname(dir);
5629
+ const parent = import_node_path35.default.dirname(dir);
5303
5630
  if (parent === dir) break;
5304
5631
  dir = parent;
5305
5632
  }
5306
- return { pm: "npm", cwd: import_node_path33.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
5633
+ return { pm: "npm", cwd: import_node_path35.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
5307
5634
  }
5308
5635
  async function runPackageManagerInstall(cmd) {
5309
5636
  return new Promise((resolve) => {
@@ -5343,32 +5670,32 @@ ${err.message}`
5343
5670
  }
5344
5671
 
5345
5672
  // src/extend/index.ts
5346
- async function fileExists(p) {
5673
+ async function fileExists2(p) {
5347
5674
  try {
5348
- await import_node_fs21.promises.access(p);
5675
+ await import_node_fs22.promises.access(p);
5349
5676
  return true;
5350
5677
  } catch {
5351
5678
  return false;
5352
5679
  }
5353
5680
  }
5354
5681
  async function readPackageJson(scanPath) {
5355
- const pkgPath = import_node_path34.default.join(scanPath, "package.json");
5356
- const raw = await import_node_fs21.promises.readFile(pkgPath, "utf8");
5682
+ const pkgPath = import_node_path36.default.join(scanPath, "package.json");
5683
+ const raw = await import_node_fs22.promises.readFile(pkgPath, "utf8");
5357
5684
  return JSON.parse(raw);
5358
5685
  }
5359
5686
  async function findHookFiles(scanPath) {
5360
- const entries = await import_node_fs21.promises.readdir(scanPath);
5687
+ const entries = await import_node_fs22.promises.readdir(scanPath);
5361
5688
  return entries.filter(
5362
5689
  (e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
5363
5690
  ).sort();
5364
5691
  }
5365
5692
  function extendLogPath() {
5366
- return process.env.NEAT_EXTEND_LOG ?? import_node_path34.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
5693
+ return process.env.NEAT_EXTEND_LOG ?? import_node_path36.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
5367
5694
  }
5368
5695
  async function appendExtendLog(entry2) {
5369
5696
  const logPath = extendLogPath();
5370
- await import_node_fs21.promises.mkdir(import_node_path34.default.dirname(logPath), { recursive: true });
5371
- await import_node_fs21.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
5697
+ await import_node_fs22.promises.mkdir(import_node_path36.default.dirname(logPath), { recursive: true });
5698
+ await import_node_fs22.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
5372
5699
  }
5373
5700
  function splicedContent(fileContent, snippet2) {
5374
5701
  if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
@@ -5426,7 +5753,7 @@ function lookupInstrumentation(library, installedVersion) {
5426
5753
  }
5427
5754
  async function describeProjectInstrumentation(ctx) {
5428
5755
  const hookFiles = await findHookFiles(ctx.scanPath);
5429
- const envNeat = await fileExists(import_node_path34.default.join(ctx.scanPath, ".env.neat"));
5756
+ const envNeat = await fileExists2(import_node_path36.default.join(ctx.scanPath, ".env.neat"));
5430
5757
  const registryInstrPackages = new Set(
5431
5758
  (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
5432
5759
  );
@@ -5448,31 +5775,31 @@ async function applyExtension(ctx, args, options) {
5448
5775
  );
5449
5776
  }
5450
5777
  for (const file of hookFiles) {
5451
- const content = await import_node_fs21.promises.readFile(import_node_path34.default.join(ctx.scanPath, file), "utf8");
5778
+ const content = await import_node_fs22.promises.readFile(import_node_path36.default.join(ctx.scanPath, file), "utf8");
5452
5779
  if (content.includes(args.registration_snippet)) {
5453
5780
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
5454
5781
  }
5455
5782
  }
5456
5783
  const primaryFile = hookFiles[0];
5457
- const primaryPath = import_node_path34.default.join(ctx.scanPath, primaryFile);
5784
+ const primaryPath = import_node_path36.default.join(ctx.scanPath, primaryFile);
5458
5785
  const filesTouched = [];
5459
5786
  const depsAdded = [];
5460
- const pkgPath = import_node_path34.default.join(ctx.scanPath, "package.json");
5787
+ const pkgPath = import_node_path36.default.join(ctx.scanPath, "package.json");
5461
5788
  const pkg = await readPackageJson(ctx.scanPath);
5462
5789
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
5463
5790
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
5464
- await import_node_fs21.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
5791
+ await import_node_fs22.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
5465
5792
  filesTouched.push("package.json");
5466
5793
  depsAdded.push(`${args.instrumentation_package}@${args.version}`);
5467
5794
  }
5468
- const hookContent = await import_node_fs21.promises.readFile(primaryPath, "utf8");
5795
+ const hookContent = await import_node_fs22.promises.readFile(primaryPath, "utf8");
5469
5796
  const patched = splicedContent(hookContent, args.registration_snippet);
5470
5797
  if (!patched) {
5471
5798
  throw new Error(
5472
5799
  `Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
5473
5800
  );
5474
5801
  }
5475
- await import_node_fs21.promises.writeFile(primaryPath, patched, "utf8");
5802
+ await import_node_fs22.promises.writeFile(primaryPath, patched, "utf8");
5476
5803
  filesTouched.push(primaryFile);
5477
5804
  const cmd = await detectPackageManager(ctx.scanPath);
5478
5805
  const installer = options?.runInstall ?? runPackageManagerInstall;
@@ -5503,7 +5830,7 @@ async function dryRunExtension(ctx, args) {
5503
5830
  };
5504
5831
  }
5505
5832
  for (const file of hookFiles) {
5506
- const content = await import_node_fs21.promises.readFile(import_node_path34.default.join(ctx.scanPath, file), "utf8");
5833
+ const content = await import_node_fs22.promises.readFile(import_node_path36.default.join(ctx.scanPath, file), "utf8");
5507
5834
  if (content.includes(args.registration_snippet)) {
5508
5835
  return {
5509
5836
  library: args.library,
@@ -5525,7 +5852,7 @@ async function dryRunExtension(ctx, args) {
5525
5852
  depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
5526
5853
  filesTouched.push("package.json");
5527
5854
  }
5528
- const hookContent = await import_node_fs21.promises.readFile(import_node_path34.default.join(ctx.scanPath, primaryFile), "utf8");
5855
+ const hookContent = await import_node_fs22.promises.readFile(import_node_path36.default.join(ctx.scanPath, primaryFile), "utf8");
5529
5856
  const patched = splicedContent(hookContent, args.registration_snippet);
5530
5857
  if (patched) {
5531
5858
  filesTouched.push(primaryFile);
@@ -5537,31 +5864,31 @@ async function dryRunExtension(ctx, args) {
5537
5864
  }
5538
5865
  async function rollbackExtension(ctx, args) {
5539
5866
  const logPath = extendLogPath();
5540
- if (!await fileExists(logPath)) {
5867
+ if (!await fileExists2(logPath)) {
5541
5868
  return { undone: false, message: "no apply found for library" };
5542
5869
  }
5543
- const raw = await import_node_fs21.promises.readFile(logPath, "utf8");
5870
+ const raw = await import_node_fs22.promises.readFile(logPath, "utf8");
5544
5871
  const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
5545
5872
  const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
5546
5873
  if (!match) {
5547
5874
  return { undone: false, message: "no apply found for library" };
5548
5875
  }
5549
- const pkgPath = import_node_path34.default.join(ctx.scanPath, "package.json");
5550
- if (await fileExists(pkgPath)) {
5876
+ const pkgPath = import_node_path36.default.join(ctx.scanPath, "package.json");
5877
+ if (await fileExists2(pkgPath)) {
5551
5878
  const pkg = await readPackageJson(ctx.scanPath);
5552
5879
  if (pkg.dependencies?.[match.instrumentation_package]) {
5553
5880
  const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
5554
5881
  pkg.dependencies = rest;
5555
- await import_node_fs21.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
5882
+ await import_node_fs22.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
5556
5883
  }
5557
5884
  }
5558
5885
  const hookFiles = await findHookFiles(ctx.scanPath);
5559
5886
  for (const file of hookFiles) {
5560
- const filePath = import_node_path34.default.join(ctx.scanPath, file);
5561
- const content = await import_node_fs21.promises.readFile(filePath, "utf8");
5887
+ const filePath = import_node_path36.default.join(ctx.scanPath, file);
5888
+ const content = await import_node_fs22.promises.readFile(filePath, "utf8");
5562
5889
  if (content.includes(match.registration_snippet)) {
5563
5890
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
5564
- await import_node_fs21.promises.writeFile(filePath, filtered, "utf8");
5891
+ await import_node_fs22.promises.writeFile(filePath, filtered, "utf8");
5565
5892
  break;
5566
5893
  }
5567
5894
  }
@@ -5573,7 +5900,7 @@ async function rollbackExtension(ctx, args) {
5573
5900
 
5574
5901
  // src/diff.ts
5575
5902
  init_cjs_shims();
5576
- var import_node_fs22 = require("fs");
5903
+ var import_node_fs23 = require("fs");
5577
5904
  async function loadSnapshotForDiff(target) {
5578
5905
  if (/^https?:\/\//i.test(target)) {
5579
5906
  const res = await fetch(target);
@@ -5582,7 +5909,7 @@ async function loadSnapshotForDiff(target) {
5582
5909
  }
5583
5910
  return await res.json();
5584
5911
  }
5585
- const raw = await import_node_fs22.promises.readFile(target, "utf8");
5912
+ const raw = await import_node_fs23.promises.readFile(target, "utf8");
5586
5913
  return JSON.parse(raw);
5587
5914
  }
5588
5915
  function indexEntries(entries) {
@@ -5650,23 +5977,23 @@ function canonicalJson(value) {
5650
5977
 
5651
5978
  // src/projects.ts
5652
5979
  init_cjs_shims();
5653
- var import_node_path35 = __toESM(require("path"), 1);
5980
+ var import_node_path37 = __toESM(require("path"), 1);
5654
5981
  function pathsForProject(project, baseDir) {
5655
5982
  if (project === DEFAULT_PROJECT) {
5656
5983
  return {
5657
- snapshotPath: import_node_path35.default.join(baseDir, "graph.json"),
5658
- errorsPath: import_node_path35.default.join(baseDir, "errors.ndjson"),
5659
- staleEventsPath: import_node_path35.default.join(baseDir, "stale-events.ndjson"),
5660
- embeddingsCachePath: import_node_path35.default.join(baseDir, "embeddings.json"),
5661
- policyViolationsPath: import_node_path35.default.join(baseDir, "policy-violations.ndjson")
5984
+ snapshotPath: import_node_path37.default.join(baseDir, "graph.json"),
5985
+ errorsPath: import_node_path37.default.join(baseDir, "errors.ndjson"),
5986
+ staleEventsPath: import_node_path37.default.join(baseDir, "stale-events.ndjson"),
5987
+ embeddingsCachePath: import_node_path37.default.join(baseDir, "embeddings.json"),
5988
+ policyViolationsPath: import_node_path37.default.join(baseDir, "policy-violations.ndjson")
5662
5989
  };
5663
5990
  }
5664
5991
  return {
5665
- snapshotPath: import_node_path35.default.join(baseDir, `${project}.json`),
5666
- errorsPath: import_node_path35.default.join(baseDir, `errors.${project}.ndjson`),
5667
- staleEventsPath: import_node_path35.default.join(baseDir, `stale-events.${project}.ndjson`),
5668
- embeddingsCachePath: import_node_path35.default.join(baseDir, `embeddings.${project}.json`),
5669
- policyViolationsPath: import_node_path35.default.join(baseDir, `policy-violations.${project}.ndjson`)
5992
+ snapshotPath: import_node_path37.default.join(baseDir, `${project}.json`),
5993
+ errorsPath: import_node_path37.default.join(baseDir, `errors.${project}.ndjson`),
5994
+ staleEventsPath: import_node_path37.default.join(baseDir, `stale-events.${project}.ndjson`),
5995
+ embeddingsCachePath: import_node_path37.default.join(baseDir, `embeddings.${project}.json`),
5996
+ policyViolationsPath: import_node_path37.default.join(baseDir, `policy-violations.${project}.ndjson`)
5670
5997
  };
5671
5998
  }
5672
5999
  var Projects = class {
@@ -5702,25 +6029,25 @@ var Projects = class {
5702
6029
 
5703
6030
  // src/registry.ts
5704
6031
  init_cjs_shims();
5705
- var import_node_fs23 = require("fs");
6032
+ var import_node_fs24 = require("fs");
5706
6033
  var import_node_os3 = __toESM(require("os"), 1);
5707
- var import_node_path36 = __toESM(require("path"), 1);
5708
- var import_types23 = require("@neat.is/types");
6034
+ var import_node_path38 = __toESM(require("path"), 1);
6035
+ var import_types24 = require("@neat.is/types");
5709
6036
  var LOCK_TIMEOUT_MS = 5e3;
5710
6037
  var LOCK_RETRY_MS = 50;
5711
6038
  function neatHome() {
5712
6039
  const override = process.env.NEAT_HOME;
5713
- if (override && override.length > 0) return import_node_path36.default.resolve(override);
5714
- return import_node_path36.default.join(import_node_os3.default.homedir(), ".neat");
6040
+ if (override && override.length > 0) return import_node_path38.default.resolve(override);
6041
+ return import_node_path38.default.join(import_node_os3.default.homedir(), ".neat");
5715
6042
  }
5716
6043
  function registryPath() {
5717
- return import_node_path36.default.join(neatHome(), "projects.json");
6044
+ return import_node_path38.default.join(neatHome(), "projects.json");
5718
6045
  }
5719
6046
  function registryLockPath() {
5720
- return import_node_path36.default.join(neatHome(), "projects.json.lock");
6047
+ return import_node_path38.default.join(neatHome(), "projects.json.lock");
5721
6048
  }
5722
6049
  function daemonPidPath() {
5723
- return import_node_path36.default.join(neatHome(), "neatd.pid");
6050
+ return import_node_path38.default.join(neatHome(), "neatd.pid");
5724
6051
  }
5725
6052
  function isPidAliveDefault(pid) {
5726
6053
  try {
@@ -5732,7 +6059,7 @@ function isPidAliveDefault(pid) {
5732
6059
  }
5733
6060
  async function readPidFile(file) {
5734
6061
  try {
5735
- const raw = await import_node_fs23.promises.readFile(file, "utf8");
6062
+ const raw = await import_node_fs24.promises.readFile(file, "utf8");
5736
6063
  const pid = Number.parseInt(raw.trim(), 10);
5737
6064
  return Number.isInteger(pid) && pid > 0 ? pid : void 0;
5738
6065
  } catch {
@@ -5780,32 +6107,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
5780
6107
  }
5781
6108
  }
5782
6109
  async function normalizeProjectPath(input) {
5783
- const resolved = import_node_path36.default.resolve(input);
6110
+ const resolved = import_node_path38.default.resolve(input);
5784
6111
  try {
5785
- return await import_node_fs23.promises.realpath(resolved);
6112
+ return await import_node_fs24.promises.realpath(resolved);
5786
6113
  } catch {
5787
6114
  return resolved;
5788
6115
  }
5789
6116
  }
5790
6117
  async function writeAtomically(target, contents) {
5791
- await import_node_fs23.promises.mkdir(import_node_path36.default.dirname(target), { recursive: true });
6118
+ await import_node_fs24.promises.mkdir(import_node_path38.default.dirname(target), { recursive: true });
5792
6119
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
5793
- const fd = await import_node_fs23.promises.open(tmp, "w");
6120
+ const fd = await import_node_fs24.promises.open(tmp, "w");
5794
6121
  try {
5795
6122
  await fd.writeFile(contents, "utf8");
5796
6123
  await fd.sync();
5797
6124
  } finally {
5798
6125
  await fd.close();
5799
6126
  }
5800
- await import_node_fs23.promises.rename(tmp, target);
6127
+ await import_node_fs24.promises.rename(tmp, target);
5801
6128
  }
5802
6129
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
5803
6130
  const deadline = Date.now() + timeoutMs;
5804
- await import_node_fs23.promises.mkdir(import_node_path36.default.dirname(lockPath), { recursive: true });
6131
+ await import_node_fs24.promises.mkdir(import_node_path38.default.dirname(lockPath), { recursive: true });
5805
6132
  let probedHolder = false;
5806
6133
  while (true) {
5807
6134
  try {
5808
- const fd = await import_node_fs23.promises.open(lockPath, "wx");
6135
+ const fd = await import_node_fs24.promises.open(lockPath, "wx");
5809
6136
  try {
5810
6137
  await fd.writeFile(`${process.pid}
5811
6138
  `, "utf8");
@@ -5830,7 +6157,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
5830
6157
  }
5831
6158
  }
5832
6159
  async function releaseLock(lockPath) {
5833
- await import_node_fs23.promises.unlink(lockPath).catch(() => {
6160
+ await import_node_fs24.promises.unlink(lockPath).catch(() => {
5834
6161
  });
5835
6162
  }
5836
6163
  async function withLock(fn) {
@@ -5846,7 +6173,7 @@ async function readRegistry() {
5846
6173
  const file = registryPath();
5847
6174
  let raw;
5848
6175
  try {
5849
- raw = await import_node_fs23.promises.readFile(file, "utf8");
6176
+ raw = await import_node_fs24.promises.readFile(file, "utf8");
5850
6177
  } catch (err) {
5851
6178
  if (err.code === "ENOENT") {
5852
6179
  return { version: 1, projects: [] };
@@ -5854,10 +6181,10 @@ async function readRegistry() {
5854
6181
  throw err;
5855
6182
  }
5856
6183
  const parsed = JSON.parse(raw);
5857
- return import_types23.RegistryFileSchema.parse(parsed);
6184
+ return import_types24.RegistryFileSchema.parse(parsed);
5858
6185
  }
5859
6186
  async function writeRegistry(reg) {
5860
- const validated = import_types23.RegistryFileSchema.parse(reg);
6187
+ const validated = import_types24.RegistryFileSchema.parse(reg);
5861
6188
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
5862
6189
  }
5863
6190
  var ProjectNameCollisionError = class extends Error {
@@ -6120,11 +6447,11 @@ function registerRoutes(scope, ctx) {
6120
6447
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
6121
6448
  const parsed = [];
6122
6449
  for (const c of candidates) {
6123
- const r = import_types24.DivergenceTypeSchema.safeParse(c);
6450
+ const r = import_types25.DivergenceTypeSchema.safeParse(c);
6124
6451
  if (!r.success) {
6125
6452
  return reply.code(400).send({
6126
6453
  error: `unknown divergence type "${c}"`,
6127
- allowed: import_types24.DivergenceTypeSchema.options
6454
+ allowed: import_types25.DivergenceTypeSchema.options
6128
6455
  });
6129
6456
  }
6130
6457
  parsed.push(r.data);
@@ -6337,7 +6664,7 @@ function registerRoutes(scope, ctx) {
6337
6664
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
6338
6665
  let violations = await log.readAll();
6339
6666
  if (req.query.severity) {
6340
- const sev = import_types24.PolicySeveritySchema.safeParse(req.query.severity);
6667
+ const sev = import_types25.PolicySeveritySchema.safeParse(req.query.severity);
6341
6668
  if (!sev.success) {
6342
6669
  return reply.code(400).send({
6343
6670
  error: "invalid severity",
@@ -6354,7 +6681,7 @@ function registerRoutes(scope, ctx) {
6354
6681
  scope.post("/policies/check", async (req, reply) => {
6355
6682
  const proj = resolveProject(registry, req, reply, ctx.bootstrap);
6356
6683
  if (!proj) return;
6357
- const parsed = import_types24.PoliciesCheckBodySchema.safeParse(req.body ?? {});
6684
+ const parsed = import_types25.PoliciesCheckBodySchema.safeParse(req.body ?? {});
6358
6685
  if (!parsed.success) {
6359
6686
  return reply.code(400).send({
6360
6687
  error: "invalid /policies/check body",
@@ -6597,8 +6924,8 @@ init_otel_grpc();
6597
6924
 
6598
6925
  // src/search.ts
6599
6926
  init_cjs_shims();
6600
- var import_node_fs24 = require("fs");
6601
- var import_node_path39 = __toESM(require("path"), 1);
6927
+ var import_node_fs25 = require("fs");
6928
+ var import_node_path41 = __toESM(require("path"), 1);
6602
6929
  var import_node_crypto3 = require("crypto");
6603
6930
  var DEFAULT_LIMIT = 10;
6604
6931
  var NOMIC_DIM = 768;
@@ -6728,7 +7055,7 @@ async function pickEmbedder() {
6728
7055
  }
6729
7056
  async function readCache(cachePath) {
6730
7057
  try {
6731
- const raw = await import_node_fs24.promises.readFile(cachePath, "utf8");
7058
+ const raw = await import_node_fs25.promises.readFile(cachePath, "utf8");
6732
7059
  const parsed = JSON.parse(raw);
6733
7060
  if (parsed.version !== 1) return null;
6734
7061
  return parsed;
@@ -6737,8 +7064,8 @@ async function readCache(cachePath) {
6737
7064
  }
6738
7065
  }
6739
7066
  async function writeCache(cachePath, cache) {
6740
- await import_node_fs24.promises.mkdir(import_node_path39.default.dirname(cachePath), { recursive: true });
6741
- await import_node_fs24.promises.writeFile(cachePath, JSON.stringify(cache));
7067
+ await import_node_fs25.promises.mkdir(import_node_path41.default.dirname(cachePath), { recursive: true });
7068
+ await import_node_fs25.promises.writeFile(cachePath, JSON.stringify(cache));
6742
7069
  }
6743
7070
  var VectorIndex = class {
6744
7071
  constructor(embedder, cachePath) {
@@ -6886,6 +7213,7 @@ async function buildSearchIndex(graph, options = {}) {
6886
7213
  var ALL_PHASES = [
6887
7214
  "services",
6888
7215
  "aliases",
7216
+ "imports",
6889
7217
  "databases",
6890
7218
  "configs",
6891
7219
  "calls",
@@ -6893,8 +7221,8 @@ var ALL_PHASES = [
6893
7221
  ];
6894
7222
  function classifyChange(relPath) {
6895
7223
  const phases = /* @__PURE__ */ new Set();
6896
- const base = import_node_path40.default.basename(relPath).toLowerCase();
6897
- const segments = relPath.split(import_node_path40.default.sep).map((s) => s.toLowerCase());
7224
+ const base = import_node_path42.default.basename(relPath).toLowerCase();
7225
+ const segments = relPath.split(import_node_path42.default.sep).map((s) => s.toLowerCase());
6898
7226
  if (base === "package.json" || base === "requirements.txt" || base === "pyproject.toml" || base === "setup.py") {
6899
7227
  phases.add("services");
6900
7228
  phases.add("aliases");
@@ -6909,6 +7237,7 @@ function classifyChange(relPath) {
6909
7237
  phases.add("aliases");
6910
7238
  }
6911
7239
  if (/\.(?:js|jsx|mjs|cjs|ts|tsx|py)$/.test(base)) {
7240
+ phases.add("imports");
6912
7241
  phases.add("calls");
6913
7242
  }
6914
7243
  if (/\.ya?ml$/.test(base) && !/^docker-compose.*\.ya?ml$/.test(base)) {
@@ -6930,6 +7259,11 @@ async function runExtractPhases(graph, scanPath, phases, project = DEFAULT_PROJE
6930
7259
  if (phases.has("aliases")) {
6931
7260
  await addServiceAliases(graph, scanPath, services);
6932
7261
  }
7262
+ if (phases.has("imports")) {
7263
+ const r = await addImports(graph, services);
7264
+ nodesAdded += r.nodesAdded;
7265
+ edgesAdded += r.edgesAdded;
7266
+ }
6933
7267
  if (phases.has("databases")) {
6934
7268
  const r = await addDatabasesAndCompat(graph, services, scanPath);
6935
7269
  nodesAdded += r.nodesAdded;
@@ -7002,16 +7336,16 @@ function countWatchableDirs(scanPath, limit) {
7002
7336
  if (count >= limit) return;
7003
7337
  let entries;
7004
7338
  try {
7005
- entries = import_node_fs25.default.readdirSync(dir, { withFileTypes: true });
7339
+ entries = import_node_fs26.default.readdirSync(dir, { withFileTypes: true });
7006
7340
  } catch {
7007
7341
  return;
7008
7342
  }
7009
7343
  for (const e of entries) {
7010
7344
  if (count >= limit) return;
7011
7345
  if (!e.isDirectory()) continue;
7012
- if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path40.default.join(dir, e.name) + import_node_path40.default.sep))) continue;
7346
+ if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path42.default.join(dir, e.name) + import_node_path42.default.sep))) continue;
7013
7347
  count++;
7014
- if (depth < 2) visit(import_node_path40.default.join(dir, e.name), depth + 1);
7348
+ if (depth < 2) visit(import_node_path42.default.join(dir, e.name), depth + 1);
7015
7349
  }
7016
7350
  };
7017
7351
  visit(scanPath, 0);
@@ -7029,8 +7363,8 @@ async function startWatch(graph, opts) {
7029
7363
  const projectName = opts.project ?? DEFAULT_PROJECT;
7030
7364
  await loadGraphFromDisk(graph, opts.outPath);
7031
7365
  const detachEventBus = attachGraphToEventBus(graph, { project: projectName });
7032
- const policyFilePath = import_node_path40.default.join(opts.scanPath, "policy.json");
7033
- const policyViolationsPath = import_node_path40.default.join(import_node_path40.default.dirname(opts.outPath), "policy-violations.ndjson");
7366
+ const policyFilePath = import_node_path42.default.join(opts.scanPath, "policy.json");
7367
+ const policyViolationsPath = import_node_path42.default.join(import_node_path42.default.dirname(opts.outPath), "policy-violations.ndjson");
7034
7368
  let policies = [];
7035
7369
  try {
7036
7370
  policies = await loadPolicyFile(policyFilePath);
@@ -7081,7 +7415,7 @@ async function startWatch(graph, opts) {
7081
7415
  assertBindAuthority(host, auth.authToken);
7082
7416
  const port = opts.port ?? 8080;
7083
7417
  const otelPort = opts.otelPort ?? 4318;
7084
- const cachePath = opts.embeddingsCachePath ?? import_node_path40.default.join(import_node_path40.default.dirname(opts.outPath), "embeddings.json");
7418
+ const cachePath = opts.embeddingsCachePath ?? import_node_path42.default.join(import_node_path42.default.dirname(opts.outPath), "embeddings.json");
7085
7419
  let searchIndex;
7086
7420
  try {
7087
7421
  searchIndex = await buildSearchIndex(graph, { cachePath });
@@ -7099,7 +7433,7 @@ async function startWatch(graph, opts) {
7099
7433
  // Paths are derived from the explicit options the watch caller passes
7100
7434
  // — pathsForProject is only used to fill in the embeddings/snapshot
7101
7435
  // fields so the registry shape is complete.
7102
- ...pathsForProject(projectName, import_node_path40.default.dirname(opts.outPath)),
7436
+ ...pathsForProject(projectName, import_node_path42.default.dirname(opts.outPath)),
7103
7437
  snapshotPath: opts.outPath,
7104
7438
  errorsPath: opts.errorsPath,
7105
7439
  staleEventsPath: opts.staleEventsPath
@@ -7186,9 +7520,9 @@ async function startWatch(graph, opts) {
7186
7520
  };
7187
7521
  const onPath = (absPath) => {
7188
7522
  if (shouldIgnore(absPath)) return;
7189
- const rel = import_node_path40.default.relative(opts.scanPath, absPath);
7523
+ const rel = import_node_path42.default.relative(opts.scanPath, absPath);
7190
7524
  if (!rel || rel.startsWith("..")) return;
7191
- pendingPaths.add(rel.split(import_node_path40.default.sep).join("/"));
7525
+ pendingPaths.add(rel.split(import_node_path42.default.sep).join("/"));
7192
7526
  const phases = classifyChange(rel);
7193
7527
  if (phases.size === 0) {
7194
7528
  for (const p of ALL_PHASES) pending.add(p);
@@ -7242,8 +7576,8 @@ async function startWatch(graph, opts) {
7242
7576
 
7243
7577
  // src/deploy/detect.ts
7244
7578
  init_cjs_shims();
7245
- var import_node_fs26 = require("fs");
7246
- var import_node_path41 = __toESM(require("path"), 1);
7579
+ var import_node_fs27 = require("fs");
7580
+ var import_node_path43 = __toESM(require("path"), 1);
7247
7581
  var import_node_child_process2 = require("child_process");
7248
7582
  var import_node_crypto4 = require("crypto");
7249
7583
  function generateToken() {
@@ -7343,21 +7677,21 @@ async function runDeploy(opts = {}) {
7343
7677
  const token = generateToken();
7344
7678
  switch (substrate) {
7345
7679
  case "docker-compose": {
7346
- const artifactPath = import_node_path41.default.join(cwd, "docker-compose.neat.yml");
7680
+ const artifactPath = import_node_path43.default.join(cwd, "docker-compose.neat.yml");
7347
7681
  const contents = emitDockerCompose(cwd);
7348
- await import_node_fs26.promises.writeFile(artifactPath, contents, "utf8");
7682
+ await import_node_fs27.promises.writeFile(artifactPath, contents, "utf8");
7349
7683
  return {
7350
7684
  substrate,
7351
7685
  artifactPath,
7352
7686
  token,
7353
7687
  contents,
7354
- startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path41.default.basename(artifactPath)} up -d`
7688
+ startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path43.default.basename(artifactPath)} up -d`
7355
7689
  };
7356
7690
  }
7357
7691
  case "systemd": {
7358
- const artifactPath = import_node_path41.default.join(cwd, "neat.service");
7692
+ const artifactPath = import_node_path43.default.join(cwd, "neat.service");
7359
7693
  const contents = emitSystemdUnit(cwd);
7360
- await import_node_fs26.promises.writeFile(artifactPath, contents, "utf8");
7694
+ await import_node_fs27.promises.writeFile(artifactPath, contents, "utf8");
7361
7695
  return {
7362
7696
  substrate,
7363
7697
  artifactPath,
@@ -7390,15 +7724,16 @@ init_cjs_shims();
7390
7724
 
7391
7725
  // src/installers/javascript.ts
7392
7726
  init_cjs_shims();
7393
- var import_node_fs27 = require("fs");
7394
- var import_node_path42 = __toESM(require("path"), 1);
7727
+ var import_node_fs28 = require("fs");
7728
+ var import_node_path44 = __toESM(require("path"), 1);
7395
7729
  var import_semver2 = __toESM(require("semver"), 1);
7396
7730
 
7397
7731
  // src/installers/templates.ts
7398
7732
  init_cjs_shims();
7399
7733
  var OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-069). OpenTelemetry auto-instrumentation hook.";
7400
- var OTEL_INIT_STAMP = "// neat-template-version: 4 \u2014 layered file-first capture (ADR-090): stack walk + handler-entry + off-stack facades.";
7734
+ var OTEL_INIT_STAMP = "// neat-template-version: 5 \u2014 layered file-first capture (ADR-090) + http/json protocol pin (#468).";
7401
7735
  var OTEL_OTLP_HEADERS_JS = "if (process.env.NEAT_OTEL_TOKEN) process.env.OTEL_EXPORTER_OTLP_HEADERS ||= 'Authorization=Bearer ' + process.env.NEAT_OTEL_TOKEN";
7736
+ var OTEL_OTLP_PROTOCOL_JS = "process.env.OTEL_EXPORTER_OTLP_PROTOCOL ||= 'http/json'";
7402
7737
  function neatCaptureSource(ts) {
7403
7738
  const spanT = ts ? ": any" : "";
7404
7739
  const strOpt = ts ? ": string | undefined" : "";
@@ -7688,6 +8023,7 @@ var OTEL_INIT_CJS = `${OTEL_INIT_HEADER}
7688
8023
  ${OTEL_INIT_STAMP}
7689
8024
  process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
7690
8025
  process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
8026
+ ${OTEL_OTLP_PROTOCOL_JS}
7691
8027
  ${OTEL_OTLP_HEADERS_JS}
7692
8028
 
7693
8029
  const { NodeSDK } = require('@opentelemetry/sdk-node')
@@ -7706,6 +8042,7 @@ var OTEL_INIT_ESM = `${OTEL_INIT_HEADER}
7706
8042
  ${OTEL_INIT_STAMP}
7707
8043
  process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
7708
8044
  process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
8045
+ ${OTEL_OTLP_PROTOCOL_JS}
7709
8046
  ${OTEL_OTLP_HEADERS_JS}
7710
8047
 
7711
8048
  import { NodeSDK } from '@opentelemetry/sdk-node'
@@ -7729,6 +8066,7 @@ ${OTEL_INIT_STAMP}
7729
8066
  // constraining the runtime logic. The file is regenerated, never hand-edited.
7730
8067
  process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
7731
8068
  process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
8069
+ ${OTEL_OTLP_PROTOCOL_JS}
7732
8070
  ${OTEL_OTLP_HEADERS_JS}
7733
8071
 
7734
8072
  import { NodeSDK } from '@opentelemetry/sdk-node'
@@ -7754,6 +8092,7 @@ function renderEnvNeat(serviceName, projectName) {
7754
8092
  "# Generated by `neat init --apply` (ADR-069).",
7755
8093
  `OTEL_SERVICE_NAME=${serviceName}`,
7756
8094
  `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4318/projects/${projectName}/v1/traces`,
8095
+ "OTEL_EXPORTER_OTLP_PROTOCOL=http/json",
7757
8096
  "# Set NEAT_OTEL_TOKEN to the daemon's OTLP secret to authenticate exported spans (#410).",
7758
8097
  "# NEAT_OTEL_TOKEN=",
7759
8098
  ""
@@ -7777,6 +8116,7 @@ export async function register() {
7777
8116
  var NEXT_INSTRUMENTATION_NODE_TS = `${NEXT_INSTRUMENTATION_HEADER}
7778
8117
  process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
7779
8118
  process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
8119
+ ${OTEL_OTLP_PROTOCOL_JS}
7780
8120
  ${OTEL_OTLP_HEADERS_JS}
7781
8121
 
7782
8122
  import { NodeSDK } from '@opentelemetry/sdk-node'
@@ -7789,6 +8129,7 @@ new NodeSDK({ instrumentations }).start()
7789
8129
  var NEXT_INSTRUMENTATION_NODE_JS = `${NEXT_INSTRUMENTATION_HEADER}
7790
8130
  process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
7791
8131
  process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
8132
+ ${OTEL_OTLP_PROTOCOL_JS}
7792
8133
  ${OTEL_OTLP_HEADERS_JS}
7793
8134
 
7794
8135
  const { NodeSDK } = require('@opentelemetry/sdk-node')
@@ -7808,6 +8149,7 @@ var FRAMEWORK_OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-074).
7808
8149
  var FRAMEWORK_OTEL_INIT_TS_BODY = `${FRAMEWORK_OTEL_INIT_HEADER}
7809
8150
  process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
7810
8151
  process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
8152
+ ${OTEL_OTLP_PROTOCOL_JS}
7811
8153
  ${OTEL_OTLP_HEADERS_JS}
7812
8154
 
7813
8155
  import { NodeSDK } from '@opentelemetry/sdk-node'
@@ -7822,6 +8164,7 @@ sdk.start()
7822
8164
  var FRAMEWORK_OTEL_INIT_JS_BODY = `${FRAMEWORK_OTEL_INIT_HEADER}
7823
8165
  process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
7824
8166
  process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
8167
+ ${OTEL_OTLP_PROTOCOL_JS}
7825
8168
  ${OTEL_OTLP_HEADERS_JS}
7826
8169
 
7827
8170
  const { NodeSDK } = require('@opentelemetry/sdk-node')
@@ -7929,15 +8272,15 @@ var OTEL_ENV = {
7929
8272
  value: "http://localhost:4318/projects/<project>/v1/traces"
7930
8273
  };
7931
8274
  function serviceNodeName(pkg, serviceDir) {
7932
- return pkg.name ?? import_node_path42.default.basename(serviceDir);
8275
+ return pkg.name ?? import_node_path44.default.basename(serviceDir);
7933
8276
  }
7934
8277
  function projectToken(pkg, serviceDir, project) {
7935
8278
  if (project && project.length > 0) return project;
7936
- return pkg.name ?? import_node_path42.default.basename(serviceDir);
8279
+ return pkg.name ?? import_node_path44.default.basename(serviceDir);
7937
8280
  }
7938
8281
  async function readJsonFile(p) {
7939
8282
  try {
7940
- const raw = await import_node_fs27.promises.readFile(p, "utf8");
8283
+ const raw = await import_node_fs28.promises.readFile(p, "utf8");
7941
8284
  return JSON.parse(raw);
7942
8285
  } catch {
7943
8286
  return null;
@@ -7946,16 +8289,16 @@ async function readJsonFile(p) {
7946
8289
  async function detectRuntimeKind(pkgRoot, pkg) {
7947
8290
  const deps = allDeps(pkg);
7948
8291
  if ("react-native" in deps || "expo" in deps) return "react-native";
7949
- const appJson = await readJsonFile(import_node_path42.default.join(pkgRoot, "app.json"));
8292
+ const appJson = await readJsonFile(import_node_path44.default.join(pkgRoot, "app.json"));
7950
8293
  if (appJson && typeof appJson === "object" && "expo" in appJson) {
7951
8294
  return "react-native";
7952
8295
  }
7953
- if (await exists3(import_node_path42.default.join(pkgRoot, "vite.config.js")) || await exists3(import_node_path42.default.join(pkgRoot, "vite.config.ts")) || await exists3(import_node_path42.default.join(pkgRoot, "vite.config.mjs")) || "vite" in deps) {
8296
+ if (await exists3(import_node_path44.default.join(pkgRoot, "vite.config.js")) || await exists3(import_node_path44.default.join(pkgRoot, "vite.config.ts")) || await exists3(import_node_path44.default.join(pkgRoot, "vite.config.mjs")) || "vite" in deps) {
7954
8297
  return "browser-bundle";
7955
8298
  }
7956
- if (await exists3(import_node_path42.default.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
7957
- if (await exists3(import_node_path42.default.join(pkgRoot, "bun.lockb"))) return "bun";
7958
- if (await exists3(import_node_path42.default.join(pkgRoot, "deno.json")) || await exists3(import_node_path42.default.join(pkgRoot, "deno.lock"))) {
8299
+ if (await exists3(import_node_path44.default.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
8300
+ if (await exists3(import_node_path44.default.join(pkgRoot, "bun.lockb"))) return "bun";
8301
+ if (await exists3(import_node_path44.default.join(pkgRoot, "deno.json")) || await exists3(import_node_path44.default.join(pkgRoot, "deno.lock"))) {
7959
8302
  return "deno";
7960
8303
  }
7961
8304
  const engines = pkg.engines ?? {};
@@ -7964,7 +8307,7 @@ async function detectRuntimeKind(pkgRoot, pkg) {
7964
8307
  }
7965
8308
  async function readPackageJson2(serviceDir) {
7966
8309
  try {
7967
- const raw = await import_node_fs27.promises.readFile(import_node_path42.default.join(serviceDir, "package.json"), "utf8");
8310
+ const raw = await import_node_fs28.promises.readFile(import_node_path44.default.join(serviceDir, "package.json"), "utf8");
7968
8311
  return JSON.parse(raw);
7969
8312
  } catch {
7970
8313
  return null;
@@ -7972,7 +8315,7 @@ async function readPackageJson2(serviceDir) {
7972
8315
  }
7973
8316
  async function exists3(p) {
7974
8317
  try {
7975
- await import_node_fs27.promises.stat(p);
8318
+ await import_node_fs28.promises.stat(p);
7976
8319
  return true;
7977
8320
  } catch {
7978
8321
  return false;
@@ -7980,7 +8323,7 @@ async function exists3(p) {
7980
8323
  }
7981
8324
  async function readFileMaybe(p) {
7982
8325
  try {
7983
- return await import_node_fs27.promises.readFile(p, "utf8");
8326
+ return await import_node_fs28.promises.readFile(p, "utf8");
7984
8327
  } catch {
7985
8328
  return null;
7986
8329
  }
@@ -8008,7 +8351,7 @@ function needsVersionUpgrade(installed, expected) {
8008
8351
  var NEXT_CONFIG_CANDIDATES = ["next.config.js", "next.config.ts", "next.config.mjs"];
8009
8352
  async function findNextConfig(serviceDir) {
8010
8353
  for (const name of NEXT_CONFIG_CANDIDATES) {
8011
- const candidate = import_node_path42.default.join(serviceDir, name);
8354
+ const candidate = import_node_path44.default.join(serviceDir, name);
8012
8355
  if (await exists3(candidate)) return candidate;
8013
8356
  }
8014
8357
  return null;
@@ -8035,7 +8378,7 @@ function hasRemixDependency(pkg) {
8035
8378
  }
8036
8379
  async function findRemixEntry(serviceDir) {
8037
8380
  for (const rel of REMIX_ENTRY_CANDIDATES) {
8038
- const candidate = import_node_path42.default.join(serviceDir, rel);
8381
+ const candidate = import_node_path44.default.join(serviceDir, rel);
8039
8382
  if (await exists3(candidate)) return candidate;
8040
8383
  }
8041
8384
  return null;
@@ -8047,14 +8390,14 @@ function hasSvelteKitDependency(pkg) {
8047
8390
  }
8048
8391
  async function findSvelteKitHooks(serviceDir) {
8049
8392
  for (const rel of SVELTEKIT_HOOKS_CANDIDATES) {
8050
- const candidate = import_node_path42.default.join(serviceDir, rel);
8393
+ const candidate = import_node_path44.default.join(serviceDir, rel);
8051
8394
  if (await exists3(candidate)) return candidate;
8052
8395
  }
8053
8396
  return null;
8054
8397
  }
8055
8398
  async function findSvelteKitConfig(serviceDir) {
8056
8399
  for (const rel of SVELTEKIT_CONFIG_CANDIDATES) {
8057
- const candidate = import_node_path42.default.join(serviceDir, rel);
8400
+ const candidate = import_node_path44.default.join(serviceDir, rel);
8058
8401
  if (await exists3(candidate)) return candidate;
8059
8402
  }
8060
8403
  return null;
@@ -8065,7 +8408,7 @@ function hasNuxtDependency(pkg) {
8065
8408
  }
8066
8409
  async function findNuxtConfig(serviceDir) {
8067
8410
  for (const name of NUXT_CONFIG_CANDIDATES) {
8068
- const candidate = import_node_path42.default.join(serviceDir, name);
8411
+ const candidate = import_node_path44.default.join(serviceDir, name);
8069
8412
  if (await exists3(candidate)) return candidate;
8070
8413
  }
8071
8414
  return null;
@@ -8076,7 +8419,7 @@ function hasAstroDependency(pkg) {
8076
8419
  }
8077
8420
  async function findAstroConfig(serviceDir) {
8078
8421
  for (const name of ASTRO_CONFIG_CANDIDATES) {
8079
- const candidate = import_node_path42.default.join(serviceDir, name);
8422
+ const candidate = import_node_path44.default.join(serviceDir, name);
8080
8423
  if (await exists3(candidate)) return candidate;
8081
8424
  }
8082
8425
  return null;
@@ -8090,7 +8433,7 @@ function parseNextMajor(range) {
8090
8433
  return Number.isFinite(n) ? n : null;
8091
8434
  }
8092
8435
  async function isTypeScriptProject(serviceDir) {
8093
- return exists3(import_node_path42.default.join(serviceDir, "tsconfig.json"));
8436
+ return exists3(import_node_path44.default.join(serviceDir, "tsconfig.json"));
8094
8437
  }
8095
8438
  var INDEX_EXTENSIONS = [".ts", ".tsx", ".js", ".mjs", ".cjs"];
8096
8439
  var INDEX_CANDIDATES = INDEX_EXTENSIONS.map((ext) => `index${ext}`);
@@ -8136,7 +8479,7 @@ function entryFromScript(script) {
8136
8479
  }
8137
8480
  async function resolveEntry(serviceDir, pkg) {
8138
8481
  if (typeof pkg.main === "string" && pkg.main.length > 0) {
8139
- const candidate = import_node_path42.default.resolve(serviceDir, pkg.main);
8482
+ const candidate = import_node_path44.default.resolve(serviceDir, pkg.main);
8140
8483
  if (await exists3(candidate)) return candidate;
8141
8484
  }
8142
8485
  if (pkg.bin) {
@@ -8150,36 +8493,36 @@ async function resolveEntry(serviceDir, pkg) {
8150
8493
  if (typeof first === "string") binEntry = first;
8151
8494
  }
8152
8495
  if (binEntry) {
8153
- const candidate = import_node_path42.default.resolve(serviceDir, binEntry);
8496
+ const candidate = import_node_path44.default.resolve(serviceDir, binEntry);
8154
8497
  if (await exists3(candidate)) return candidate;
8155
8498
  }
8156
8499
  }
8157
8500
  const startEntry = entryFromScript(pkg.scripts?.start);
8158
8501
  if (startEntry) {
8159
- const candidate = import_node_path42.default.resolve(serviceDir, startEntry);
8502
+ const candidate = import_node_path44.default.resolve(serviceDir, startEntry);
8160
8503
  if (await exists3(candidate)) return candidate;
8161
8504
  }
8162
8505
  const devEntry = entryFromScript(pkg.scripts?.dev);
8163
8506
  if (devEntry) {
8164
- const candidate = import_node_path42.default.resolve(serviceDir, devEntry);
8507
+ const candidate = import_node_path44.default.resolve(serviceDir, devEntry);
8165
8508
  if (await exists3(candidate)) return candidate;
8166
8509
  }
8167
8510
  for (const rel of SRC_INDEX_CANDIDATES) {
8168
- const candidate = import_node_path42.default.join(serviceDir, rel);
8511
+ const candidate = import_node_path44.default.join(serviceDir, rel);
8169
8512
  if (await exists3(candidate)) return candidate;
8170
8513
  }
8171
8514
  for (const rel of SRC_NAMED_CANDIDATES) {
8172
- const candidate = import_node_path42.default.join(serviceDir, rel);
8515
+ const candidate = import_node_path44.default.join(serviceDir, rel);
8173
8516
  if (await exists3(candidate)) return candidate;
8174
8517
  }
8175
8518
  for (const name of INDEX_CANDIDATES) {
8176
- const candidate = import_node_path42.default.join(serviceDir, name);
8519
+ const candidate = import_node_path44.default.join(serviceDir, name);
8177
8520
  if (await exists3(candidate)) return candidate;
8178
8521
  }
8179
8522
  return null;
8180
8523
  }
8181
8524
  function dispatchEntry(entryFile, pkg) {
8182
- const ext = import_node_path42.default.extname(entryFile).toLowerCase();
8525
+ const ext = import_node_path44.default.extname(entryFile).toLowerCase();
8183
8526
  if (ext === ".ts" || ext === ".tsx") return "ts";
8184
8527
  if (ext === ".mjs") return "esm";
8185
8528
  if (ext === ".cjs") return "cjs";
@@ -8196,9 +8539,9 @@ function otelInitContents(flavor) {
8196
8539
  return OTEL_INIT_CJS;
8197
8540
  }
8198
8541
  function injectionLine(flavor, entryFile, otelInitFile) {
8199
- let rel = import_node_path42.default.relative(import_node_path42.default.dirname(entryFile), otelInitFile);
8542
+ let rel = import_node_path44.default.relative(import_node_path44.default.dirname(entryFile), otelInitFile);
8200
8543
  if (!rel.startsWith(".")) rel = `./${rel}`;
8201
- rel = rel.split(import_node_path42.default.sep).join("/");
8544
+ rel = rel.split(import_node_path44.default.sep).join("/");
8202
8545
  if (flavor === "cjs") return `require('${rel}')`;
8203
8546
  if (flavor === "esm") return `import '${rel}'`;
8204
8547
  const tsRel = rel.replace(/\.ts$/, "");
@@ -8211,23 +8554,23 @@ function lineIsOtelInjection(line) {
8211
8554
  }
8212
8555
  async function detectsSrcLayout(serviceDir) {
8213
8556
  const [hasSrcApp, hasSrcPages, hasRootApp, hasRootPages] = await Promise.all([
8214
- exists3(import_node_path42.default.join(serviceDir, "src", "app")),
8215
- exists3(import_node_path42.default.join(serviceDir, "src", "pages")),
8216
- exists3(import_node_path42.default.join(serviceDir, "app")),
8217
- exists3(import_node_path42.default.join(serviceDir, "pages"))
8557
+ exists3(import_node_path44.default.join(serviceDir, "src", "app")),
8558
+ exists3(import_node_path44.default.join(serviceDir, "src", "pages")),
8559
+ exists3(import_node_path44.default.join(serviceDir, "app")),
8560
+ exists3(import_node_path44.default.join(serviceDir, "pages"))
8218
8561
  ]);
8219
8562
  return (hasSrcApp || hasSrcPages) && !hasRootApp && !hasRootPages;
8220
8563
  }
8221
8564
  async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project) {
8222
8565
  const useTs = await isTypeScriptProject(serviceDir);
8223
8566
  const srcLayout = await detectsSrcLayout(serviceDir);
8224
- const baseDir = srcLayout ? import_node_path42.default.join(serviceDir, "src") : serviceDir;
8225
- const instrumentationFile = import_node_path42.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
8226
- const instrumentationNodeFile = import_node_path42.default.join(
8567
+ const baseDir = srcLayout ? import_node_path44.default.join(serviceDir, "src") : serviceDir;
8568
+ const instrumentationFile = import_node_path44.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
8569
+ const instrumentationNodeFile = import_node_path44.default.join(
8227
8570
  baseDir,
8228
8571
  useTs ? "instrumentation.node.ts" : "instrumentation.node.js"
8229
8572
  );
8230
- const envNeatFile = import_node_path42.default.join(baseDir, ".env.neat");
8573
+ const envNeatFile = import_node_path44.default.join(baseDir, ".env.neat");
8231
8574
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
8232
8575
  const dependencyEdits = [];
8233
8576
  for (const sdk of SDK_PACKAGES) {
@@ -8284,7 +8627,7 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
8284
8627
  const nextMajor = parseNextMajor(nextRange);
8285
8628
  if (nextMajor !== null && nextMajor < 15) {
8286
8629
  try {
8287
- const raw = await import_node_fs27.promises.readFile(nextConfigPath, "utf8");
8630
+ const raw = await import_node_fs28.promises.readFile(nextConfigPath, "utf8");
8288
8631
  if (!raw.includes("instrumentationHook")) {
8289
8632
  nextConfigEdit = {
8290
8633
  file: nextConfigPath,
@@ -8332,7 +8675,7 @@ function buildDependencyEdits(pkg, manifestPath) {
8332
8675
  return edits;
8333
8676
  }
8334
8677
  async function queueEnvNeat(serviceDir, pkg, project, generatedFiles) {
8335
- const envNeatFile = import_node_path42.default.join(serviceDir, ".env.neat");
8678
+ const envNeatFile = import_node_path44.default.join(serviceDir, ".env.neat");
8336
8679
  if (!await exists3(envNeatFile)) {
8337
8680
  generatedFiles.push({
8338
8681
  file: envNeatFile,
@@ -8367,7 +8710,7 @@ function fileImportsOtelHook(raw, specifiers) {
8367
8710
  }
8368
8711
  async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
8369
8712
  const useTs = await isTypeScriptProject(serviceDir);
8370
- const otelServerFile = import_node_path42.default.join(
8713
+ const otelServerFile = import_node_path44.default.join(
8371
8714
  serviceDir,
8372
8715
  useTs ? "app/otel.server.ts" : "app/otel.server.js"
8373
8716
  );
@@ -8388,7 +8731,7 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
8388
8731
  await queueEnvNeat(serviceDir, pkg, project, generatedFiles);
8389
8732
  const entrypointEdits = [];
8390
8733
  try {
8391
- const raw = await import_node_fs27.promises.readFile(entryFile, "utf8");
8734
+ const raw = await import_node_fs28.promises.readFile(entryFile, "utf8");
8392
8735
  if (!fileImportsOtelHook(raw, ["./otel.server"])) {
8393
8736
  const lines = raw.split(/\r?\n/);
8394
8737
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
@@ -8424,11 +8767,11 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
8424
8767
  }
8425
8768
  async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project) {
8426
8769
  const useTs = await isTypeScriptProject(serviceDir);
8427
- const otelInitFile = import_node_path42.default.join(
8770
+ const otelInitFile = import_node_path44.default.join(
8428
8771
  serviceDir,
8429
8772
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
8430
8773
  );
8431
- const resolvedHooksFile = hooksFile ?? import_node_path42.default.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
8774
+ const resolvedHooksFile = hooksFile ?? import_node_path44.default.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
8432
8775
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
8433
8776
  const generatedFiles = [];
8434
8777
  const entrypointEdits = [];
@@ -8453,7 +8796,7 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
8453
8796
  });
8454
8797
  } else {
8455
8798
  try {
8456
- const raw = await import_node_fs27.promises.readFile(hooksFile, "utf8");
8799
+ const raw = await import_node_fs28.promises.readFile(hooksFile, "utf8");
8457
8800
  if (!fileImportsOtelHook(raw, ["./otel-init"])) {
8458
8801
  const lines = raw.split(/\r?\n/);
8459
8802
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
@@ -8490,11 +8833,11 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
8490
8833
  }
8491
8834
  async function planNuxt(serviceDir, pkg, manifestPath, project) {
8492
8835
  const useTs = await isTypeScriptProject(serviceDir);
8493
- const otelPluginFile = import_node_path42.default.join(
8836
+ const otelPluginFile = import_node_path44.default.join(
8494
8837
  serviceDir,
8495
8838
  useTs ? "server/plugins/otel.ts" : "server/plugins/otel.js"
8496
8839
  );
8497
- const otelInitFile = import_node_path42.default.join(
8840
+ const otelInitFile = import_node_path44.default.join(
8498
8841
  serviceDir,
8499
8842
  useTs ? "server/plugins/otel-init.ts" : "server/plugins/otel-init.js"
8500
8843
  );
@@ -8545,19 +8888,19 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
8545
8888
  var ASTRO_MIDDLEWARE_CANDIDATES = ["src/middleware.ts", "src/middleware.js"];
8546
8889
  async function findAstroMiddleware(serviceDir) {
8547
8890
  for (const rel of ASTRO_MIDDLEWARE_CANDIDATES) {
8548
- const candidate = import_node_path42.default.join(serviceDir, rel);
8891
+ const candidate = import_node_path44.default.join(serviceDir, rel);
8549
8892
  if (await exists3(candidate)) return candidate;
8550
8893
  }
8551
8894
  return null;
8552
8895
  }
8553
8896
  async function planAstro(serviceDir, pkg, manifestPath, project) {
8554
8897
  const useTs = await isTypeScriptProject(serviceDir);
8555
- const otelInitFile = import_node_path42.default.join(
8898
+ const otelInitFile = import_node_path44.default.join(
8556
8899
  serviceDir,
8557
8900
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
8558
8901
  );
8559
8902
  const existingMiddleware = await findAstroMiddleware(serviceDir);
8560
- const middlewareFile = existingMiddleware ?? import_node_path42.default.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
8903
+ const middlewareFile = existingMiddleware ?? import_node_path44.default.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
8561
8904
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
8562
8905
  const generatedFiles = [];
8563
8906
  const entrypointEdits = [];
@@ -8582,7 +8925,7 @@ async function planAstro(serviceDir, pkg, manifestPath, project) {
8582
8925
  });
8583
8926
  } else {
8584
8927
  try {
8585
- const raw = await import_node_fs27.promises.readFile(existingMiddleware, "utf8");
8928
+ const raw = await import_node_fs28.promises.readFile(existingMiddleware, "utf8");
8586
8929
  if (!fileImportsOtelHook(raw, ["./otel-init"])) {
8587
8930
  const lines = raw.split(/\r?\n/);
8588
8931
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
@@ -8653,7 +8996,7 @@ async function findFrameworkDispatch(serviceDir, pkg, manifestPath, project) {
8653
8996
  }
8654
8997
  async function plan(serviceDir, opts) {
8655
8998
  const pkg = await readPackageJson2(serviceDir);
8656
- const manifestPath = import_node_path42.default.join(serviceDir, "package.json");
8999
+ const manifestPath = import_node_path44.default.join(serviceDir, "package.json");
8657
9000
  const project = opts?.project;
8658
9001
  const empty = {
8659
9002
  language: "javascript",
@@ -8688,8 +9031,8 @@ async function plan(serviceDir, opts) {
8688
9031
  return { ...empty, libOnly: true };
8689
9032
  }
8690
9033
  const flavor = dispatchEntry(entryFile, pkg);
8691
- const otelInitFile = import_node_path42.default.join(import_node_path42.default.dirname(entryFile), otelInitFilename(flavor));
8692
- const envNeatFile = import_node_path42.default.join(serviceDir, ".env.neat");
9034
+ const otelInitFile = import_node_path44.default.join(import_node_path44.default.dirname(entryFile), otelInitFilename(flavor));
9035
+ const envNeatFile = import_node_path44.default.join(serviceDir, ".env.neat");
8693
9036
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
8694
9037
  const dependencyEdits = [];
8695
9038
  for (const sdk of SDK_PACKAGES) {
@@ -8713,7 +9056,7 @@ async function plan(serviceDir, opts) {
8713
9056
  }
8714
9057
  const entrypointEdits = [];
8715
9058
  try {
8716
- const raw = await import_node_fs27.promises.readFile(entryFile, "utf8");
9059
+ const raw = await import_node_fs28.promises.readFile(entryFile, "utf8");
8717
9060
  const lines = raw.split(/\r?\n/);
8718
9061
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
8719
9062
  if (!lineIsOtelInjection(firstReal)) {
@@ -8758,13 +9101,13 @@ async function plan(serviceDir, opts) {
8758
9101
  };
8759
9102
  }
8760
9103
  function isAllowedWritePath(serviceDir, target) {
8761
- const rel = import_node_path42.default.relative(serviceDir, target);
9104
+ const rel = import_node_path44.default.relative(serviceDir, target);
8762
9105
  if (rel.startsWith("..")) return false;
8763
- const base = import_node_path42.default.basename(target);
9106
+ const base = import_node_path44.default.basename(target);
8764
9107
  if (base === "package.json") return true;
8765
9108
  if (base === ".env.neat") return true;
8766
9109
  if (/^otel-init\.(?:js|cjs|mjs|ts)$/.test(base)) return true;
8767
- const relPosix = rel.split(import_node_path42.default.sep).join("/");
9110
+ const relPosix = rel.split(import_node_path44.default.sep).join("/");
8768
9111
  if (/^instrumentation(?:\.node)?\.(?:js|cjs|mjs|ts)$/.test(base)) {
8769
9112
  if (relPosix === base) return true;
8770
9113
  if (relPosix === `src/${base}`) return true;
@@ -8781,10 +9124,10 @@ function isAllowedWritePath(serviceDir, target) {
8781
9124
  return false;
8782
9125
  }
8783
9126
  async function writeAtomic(file, contents) {
8784
- await import_node_fs27.promises.mkdir(import_node_path42.default.dirname(file), { recursive: true });
9127
+ await import_node_fs28.promises.mkdir(import_node_path44.default.dirname(file), { recursive: true });
8785
9128
  const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
8786
- await import_node_fs27.promises.writeFile(tmp, contents, "utf8");
8787
- await import_node_fs27.promises.rename(tmp, file);
9129
+ await import_node_fs28.promises.writeFile(tmp, contents, "utf8");
9130
+ await import_node_fs28.promises.rename(tmp, file);
8788
9131
  }
8789
9132
  async function apply(installPlan) {
8790
9133
  const { serviceDir } = installPlan;
@@ -8860,7 +9203,7 @@ async function apply(installPlan) {
8860
9203
  for (const target of allTargets) {
8861
9204
  if (await exists3(target)) {
8862
9205
  try {
8863
- originals.set(target, await import_node_fs27.promises.readFile(target, "utf8"));
9206
+ originals.set(target, await import_node_fs28.promises.readFile(target, "utf8"));
8864
9207
  } catch {
8865
9208
  }
8866
9209
  }
@@ -8943,14 +9286,14 @@ async function rollback(installPlan, originals, createdFiles) {
8943
9286
  const removed = [];
8944
9287
  for (const [file, raw] of originals.entries()) {
8945
9288
  try {
8946
- await import_node_fs27.promises.writeFile(file, raw, "utf8");
9289
+ await import_node_fs28.promises.writeFile(file, raw, "utf8");
8947
9290
  restored.push(file);
8948
9291
  } catch {
8949
9292
  }
8950
9293
  }
8951
9294
  for (const file of createdFiles) {
8952
9295
  try {
8953
- await import_node_fs27.promises.unlink(file);
9296
+ await import_node_fs28.promises.unlink(file);
8954
9297
  removed.push(file);
8955
9298
  } catch {
8956
9299
  }
@@ -8965,8 +9308,8 @@ async function rollback(installPlan, originals, createdFiles) {
8965
9308
  ...removed.map((f) => `removed: ${f}`),
8966
9309
  ""
8967
9310
  ];
8968
- const rollbackPath = import_node_path42.default.join(installPlan.serviceDir, "neat-rollback.patch");
8969
- await import_node_fs27.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
9311
+ const rollbackPath = import_node_path44.default.join(installPlan.serviceDir, "neat-rollback.patch");
9312
+ await import_node_fs28.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
8970
9313
  }
8971
9314
  function injectInstrumentationHook(raw) {
8972
9315
  if (raw.includes("instrumentationHook")) return raw;
@@ -8995,8 +9338,8 @@ var javascriptInstaller = {
8995
9338
 
8996
9339
  // src/installers/python.ts
8997
9340
  init_cjs_shims();
8998
- var import_node_fs28 = require("fs");
8999
- var import_node_path43 = __toESM(require("path"), 1);
9341
+ var import_node_fs29 = require("fs");
9342
+ var import_node_path45 = __toESM(require("path"), 1);
9000
9343
  var SDK_PACKAGES2 = [
9001
9344
  { name: "opentelemetry-distro", version: ">=0.49b0" },
9002
9345
  { name: "opentelemetry-exporter-otlp", version: ">=1.28.0" }
@@ -9008,7 +9351,7 @@ var OTEL_ENV2 = {
9008
9351
  };
9009
9352
  async function exists4(p) {
9010
9353
  try {
9011
- await import_node_fs28.promises.stat(p);
9354
+ await import_node_fs29.promises.stat(p);
9012
9355
  return true;
9013
9356
  } catch {
9014
9357
  return false;
@@ -9017,7 +9360,7 @@ async function exists4(p) {
9017
9360
  async function detect2(serviceDir) {
9018
9361
  const markers = ["requirements.txt", "pyproject.toml", "setup.py"];
9019
9362
  for (const m of markers) {
9020
- if (await exists4(import_node_path43.default.join(serviceDir, m))) return true;
9363
+ if (await exists4(import_node_path45.default.join(serviceDir, m))) return true;
9021
9364
  }
9022
9365
  return false;
9023
9366
  }
@@ -9027,9 +9370,9 @@ function reqPackageName(line) {
9027
9370
  return head.replace(/[<>=!~].*$/, "").toLowerCase();
9028
9371
  }
9029
9372
  async function planRequirementsTxtEdits(serviceDir) {
9030
- const file = import_node_path43.default.join(serviceDir, "requirements.txt");
9373
+ const file = import_node_path45.default.join(serviceDir, "requirements.txt");
9031
9374
  if (!await exists4(file)) return null;
9032
- const raw = await import_node_fs28.promises.readFile(file, "utf8");
9375
+ const raw = await import_node_fs29.promises.readFile(file, "utf8");
9033
9376
  const presentNames = new Set(
9034
9377
  raw.split(/\r?\n/).map(reqPackageName).filter((n) => n.length > 0)
9035
9378
  );
@@ -9037,9 +9380,9 @@ async function planRequirementsTxtEdits(serviceDir) {
9037
9380
  return { manifest: file, missing: [...missing] };
9038
9381
  }
9039
9382
  async function planProcfileEdits(serviceDir) {
9040
- const procfile = import_node_path43.default.join(serviceDir, "Procfile");
9383
+ const procfile = import_node_path45.default.join(serviceDir, "Procfile");
9041
9384
  if (!await exists4(procfile)) return [];
9042
- const raw = await import_node_fs28.promises.readFile(procfile, "utf8");
9385
+ const raw = await import_node_fs29.promises.readFile(procfile, "utf8");
9043
9386
  const edits = [];
9044
9387
  for (const line of raw.split(/\r?\n/)) {
9045
9388
  if (line.length === 0) continue;
@@ -9091,8 +9434,8 @@ async function applyRequirementsTxt(manifest, edits, original) {
9091
9434
  const next = `${original}${trailing}${newlines.join("\n")}
9092
9435
  `;
9093
9436
  const tmp = `${manifest}.${process.pid}.${Date.now()}.tmp`;
9094
- await import_node_fs28.promises.writeFile(tmp, next, "utf8");
9095
- await import_node_fs28.promises.rename(tmp, manifest);
9437
+ await import_node_fs29.promises.writeFile(tmp, next, "utf8");
9438
+ await import_node_fs29.promises.rename(tmp, manifest);
9096
9439
  }
9097
9440
  async function applyProcfile(procfile, edits, original) {
9098
9441
  let next = original;
@@ -9101,8 +9444,8 @@ async function applyProcfile(procfile, edits, original) {
9101
9444
  next = next.replace(e.before, e.after);
9102
9445
  }
9103
9446
  const tmp = `${procfile}.${process.pid}.${Date.now()}.tmp`;
9104
- await import_node_fs28.promises.writeFile(tmp, next, "utf8");
9105
- await import_node_fs28.promises.rename(tmp, procfile);
9447
+ await import_node_fs29.promises.writeFile(tmp, next, "utf8");
9448
+ await import_node_fs29.promises.rename(tmp, procfile);
9106
9449
  }
9107
9450
  async function apply2(installPlan) {
9108
9451
  const { serviceDir } = installPlan;
@@ -9115,7 +9458,7 @@ async function apply2(installPlan) {
9115
9458
  const originals = /* @__PURE__ */ new Map();
9116
9459
  for (const file of touched) {
9117
9460
  try {
9118
- originals.set(file, await import_node_fs28.promises.readFile(file, "utf8"));
9461
+ originals.set(file, await import_node_fs29.promises.readFile(file, "utf8"));
9119
9462
  } catch {
9120
9463
  }
9121
9464
  }
@@ -9126,7 +9469,7 @@ async function apply2(installPlan) {
9126
9469
  if (raw === void 0) {
9127
9470
  throw new Error(`python installer: cannot read ${file} during apply`);
9128
9471
  }
9129
- const base = import_node_path43.default.basename(file);
9472
+ const base = import_node_path45.default.basename(file);
9130
9473
  if (base === "requirements.txt") {
9131
9474
  const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
9132
9475
  if (edits.length > 0) {
@@ -9151,7 +9494,7 @@ async function rollback2(installPlan, originals) {
9151
9494
  const restored = [];
9152
9495
  for (const [file, raw] of originals.entries()) {
9153
9496
  try {
9154
- await import_node_fs28.promises.writeFile(file, raw, "utf8");
9497
+ await import_node_fs29.promises.writeFile(file, raw, "utf8");
9155
9498
  restored.push(file);
9156
9499
  } catch {
9157
9500
  }
@@ -9165,8 +9508,8 @@ async function rollback2(installPlan, originals) {
9165
9508
  ...restored.map((f) => `restored: ${f}`),
9166
9509
  ""
9167
9510
  ];
9168
- const rollbackPath = import_node_path43.default.join(installPlan.serviceDir, "neat-rollback.patch");
9169
- await import_node_fs28.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
9511
+ const rollbackPath = import_node_path45.default.join(installPlan.serviceDir, "neat-rollback.patch");
9512
+ await import_node_fs29.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
9170
9513
  }
9171
9514
  var pythonInstaller = {
9172
9515
  name: "python",
@@ -9289,10 +9632,10 @@ function renderPatch(sections) {
9289
9632
 
9290
9633
  // src/orchestrator.ts
9291
9634
  init_cjs_shims();
9292
- var import_node_fs29 = require("fs");
9635
+ var import_node_fs30 = require("fs");
9293
9636
  var import_node_http = __toESM(require("http"), 1);
9294
9637
  var import_node_net = __toESM(require("net"), 1);
9295
- var import_node_path44 = __toESM(require("path"), 1);
9638
+ var import_node_path46 = __toESM(require("path"), 1);
9296
9639
  var import_node_child_process3 = require("child_process");
9297
9640
  var import_node_readline = __toESM(require("readline"), 1);
9298
9641
  async function extractAndPersist(opts) {
@@ -9301,7 +9644,7 @@ async function extractAndPersist(opts) {
9301
9644
  const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
9302
9645
  resetGraph(graphKey);
9303
9646
  const graph = getGraph(graphKey);
9304
- const projectPaths = pathsForProject(graphKey, import_node_path44.default.join(opts.scanPath, "neat-out"));
9647
+ const projectPaths = pathsForProject(graphKey, import_node_path46.default.join(opts.scanPath, "neat-out"));
9305
9648
  const extraction = await extractFromDirectory(graph, opts.scanPath, {
9306
9649
  errorsPath: projectPaths.errorsPath
9307
9650
  });
@@ -9355,7 +9698,7 @@ async function applyInstallersOver(services, project, options = {}) {
9355
9698
  console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
9356
9699
  } else if (outcome.outcome === "react-native") {
9357
9700
  reactNative++;
9358
- const svcName = import_node_path44.default.basename(svc.dir);
9701
+ const svcName = import_node_path46.default.basename(svc.dir);
9359
9702
  console.log(
9360
9703
  `neat: ${svc.dir} detected as React Native / Expo
9361
9704
  The installer doesn't cover this runtime deterministically.
@@ -9366,7 +9709,7 @@ async function applyInstallersOver(services, project, options = {}) {
9366
9709
  );
9367
9710
  } else if (outcome.outcome === "bun") {
9368
9711
  bun++;
9369
- const svcName = import_node_path44.default.basename(svc.dir);
9712
+ const svcName = import_node_path46.default.basename(svc.dir);
9370
9713
  console.log(
9371
9714
  `neat: ${svc.dir} detected as Bun
9372
9715
  The installer doesn't cover this runtime deterministically.
@@ -9377,7 +9720,7 @@ async function applyInstallersOver(services, project, options = {}) {
9377
9720
  );
9378
9721
  } else if (outcome.outcome === "deno") {
9379
9722
  deno++;
9380
- const svcName = import_node_path44.default.basename(svc.dir);
9723
+ const svcName = import_node_path46.default.basename(svc.dir);
9381
9724
  console.log(
9382
9725
  `neat: ${svc.dir} detected as Deno
9383
9726
  The installer doesn't cover this runtime deterministically.
@@ -9388,7 +9731,7 @@ async function applyInstallersOver(services, project, options = {}) {
9388
9731
  );
9389
9732
  } else if (outcome.outcome === "cloudflare-workers") {
9390
9733
  cloudflareWorkers++;
9391
- const svcName = import_node_path44.default.basename(svc.dir);
9734
+ const svcName = import_node_path46.default.basename(svc.dir);
9392
9735
  console.log(
9393
9736
  `neat: ${svc.dir} detected as Cloudflare Workers
9394
9737
  The installer doesn't cover this runtime deterministically.
@@ -9399,7 +9742,7 @@ async function applyInstallersOver(services, project, options = {}) {
9399
9742
  );
9400
9743
  } else if (outcome.outcome === "electron") {
9401
9744
  electron++;
9402
- const svcName = import_node_path44.default.basename(svc.dir);
9745
+ const svcName = import_node_path46.default.basename(svc.dir);
9403
9746
  console.log(
9404
9747
  `neat: ${svc.dir} detected as Electron
9405
9748
  The installer doesn't cover this runtime deterministically.
@@ -9573,10 +9916,10 @@ function formatPortCollisionMessage(port) {
9573
9916
  ];
9574
9917
  }
9575
9918
  function spawnDaemonDetached() {
9576
- const here = import_node_path44.default.dirname(new URL(importMetaUrl).pathname);
9919
+ const here = import_node_path46.default.dirname(new URL(importMetaUrl).pathname);
9577
9920
  const candidates = [
9578
- import_node_path44.default.join(here, "neatd.cjs"),
9579
- import_node_path44.default.join(here, "neatd.js")
9921
+ import_node_path46.default.join(here, "neatd.cjs"),
9922
+ import_node_path46.default.join(here, "neatd.js")
9580
9923
  ];
9581
9924
  let entry2 = null;
9582
9925
  const fsSync = require("fs");
@@ -9634,7 +9977,7 @@ async function runOrchestrator(opts) {
9634
9977
  browser: "skipped"
9635
9978
  }
9636
9979
  };
9637
- const stat = await import_node_fs29.promises.stat(opts.scanPath).catch(() => null);
9980
+ const stat = await import_node_fs30.promises.stat(opts.scanPath).catch(() => null);
9638
9981
  if (!stat || !stat.isDirectory()) {
9639
9982
  console.error(`neat: ${opts.scanPath} is not a directory`);
9640
9983
  result.exitCode = 2;
@@ -9777,11 +10120,11 @@ function printSummary(result, graph, dashboardUrl) {
9777
10120
 
9778
10121
  // src/cli-verbs.ts
9779
10122
  init_cjs_shims();
9780
- var import_node_path45 = __toESM(require("path"), 1);
10123
+ var import_node_path47 = __toESM(require("path"), 1);
9781
10124
 
9782
10125
  // src/cli-client.ts
9783
10126
  init_cjs_shims();
9784
- var import_types25 = require("@neat.is/types");
10127
+ var import_types26 = require("@neat.is/types");
9785
10128
  var HttpError = class extends Error {
9786
10129
  constructor(status2, message, responseBody = "") {
9787
10130
  super(message);
@@ -9806,10 +10149,10 @@ function createHttpClient(baseUrl, bearerToken) {
9806
10149
  const root = baseUrl.replace(/\/$/, "");
9807
10150
  const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
9808
10151
  return {
9809
- async get(path47) {
10152
+ async get(path49) {
9810
10153
  let res;
9811
10154
  try {
9812
- res = await fetch(`${root}${path47}`, {
10155
+ res = await fetch(`${root}${path49}`, {
9813
10156
  headers: { ...authHeader }
9814
10157
  });
9815
10158
  } catch (err) {
@@ -9821,16 +10164,16 @@ function createHttpClient(baseUrl, bearerToken) {
9821
10164
  const body = await res.text().catch(() => "");
9822
10165
  throw new HttpError(
9823
10166
  res.status,
9824
- `${res.status} ${res.statusText} on GET ${path47}: ${body}`,
10167
+ `${res.status} ${res.statusText} on GET ${path49}: ${body}`,
9825
10168
  body
9826
10169
  );
9827
10170
  }
9828
10171
  return await res.json();
9829
10172
  },
9830
- async post(path47, body) {
10173
+ async post(path49, body) {
9831
10174
  let res;
9832
10175
  try {
9833
- res = await fetch(`${root}${path47}`, {
10176
+ res = await fetch(`${root}${path49}`, {
9834
10177
  method: "POST",
9835
10178
  headers: { "content-type": "application/json", ...authHeader },
9836
10179
  body: JSON.stringify(body)
@@ -9844,7 +10187,7 @@ function createHttpClient(baseUrl, bearerToken) {
9844
10187
  const text = await res.text().catch(() => "");
9845
10188
  throw new HttpError(
9846
10189
  res.status,
9847
- `${res.status} ${res.statusText} on POST ${path47}: ${text}`,
10190
+ `${res.status} ${res.statusText} on POST ${path49}: ${text}`,
9848
10191
  text
9849
10192
  );
9850
10193
  }
@@ -9858,12 +10201,12 @@ function projectPath(project, suffix) {
9858
10201
  }
9859
10202
  async function runRootCause(client, input) {
9860
10203
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
9861
- const path47 = projectPath(
10204
+ const path49 = projectPath(
9862
10205
  input.project,
9863
10206
  `/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
9864
10207
  );
9865
10208
  try {
9866
- const result = await client.get(path47);
10209
+ const result = await client.get(path49);
9867
10210
  const arrowPath = result.traversalPath.join(" \u2190 ");
9868
10211
  const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
9869
10212
  const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
@@ -9889,12 +10232,12 @@ async function runRootCause(client, input) {
9889
10232
  }
9890
10233
  async function runBlastRadius(client, input) {
9891
10234
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
9892
- const path47 = projectPath(
10235
+ const path49 = projectPath(
9893
10236
  input.project,
9894
10237
  `/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
9895
10238
  );
9896
10239
  try {
9897
- const result = await client.get(path47);
10240
+ const result = await client.get(path49);
9898
10241
  if (result.totalAffected === 0) {
9899
10242
  return {
9900
10243
  summary: `${result.origin} has no downstream dependencies. Nothing else would break if it failed.`
@@ -9923,17 +10266,17 @@ async function runBlastRadius(client, input) {
9923
10266
  }
9924
10267
  }
9925
10268
  function formatBlastEntry(n) {
9926
- const tag = n.edgeProvenance === import_types25.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
10269
+ const tag = n.edgeProvenance === import_types26.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
9927
10270
  return ` \u2022 ${n.nodeId} (distance ${n.distance}, ${n.edgeProvenance})${tag}`;
9928
10271
  }
9929
10272
  async function runDependencies(client, input) {
9930
10273
  const depth = input.depth ?? 3;
9931
- const path47 = projectPath(
10274
+ const path49 = projectPath(
9932
10275
  input.project,
9933
10276
  `/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
9934
10277
  );
9935
10278
  try {
9936
- const result = await client.get(path47);
10279
+ const result = await client.get(path49);
9937
10280
  if (result.total === 0) {
9938
10281
  return {
9939
10282
  summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
@@ -9969,9 +10312,9 @@ async function runObservedDependencies(client, input) {
9969
10312
  const edges = await client.get(
9970
10313
  projectPath(input.project, `/graph/edges/${encodeURIComponent(input.nodeId)}`)
9971
10314
  );
9972
- const observed = edges.outbound.filter((e) => e.provenance === import_types25.Provenance.OBSERVED);
10315
+ const observed = edges.outbound.filter((e) => e.provenance === import_types26.Provenance.OBSERVED);
9973
10316
  if (observed.length === 0) {
9974
- const hasExtracted = edges.outbound.some((e) => e.provenance === import_types25.Provenance.EXTRACTED);
10317
+ const hasExtracted = edges.outbound.some((e) => e.provenance === import_types26.Provenance.EXTRACTED);
9975
10318
  const note = hasExtracted ? " Static (EXTRACTED) dependencies exist but no runtime traffic has been seen \u2014 is OTel running?" : "";
9976
10319
  return { summary: `No OBSERVED dependencies for ${input.nodeId}.${note}` };
9977
10320
  }
@@ -9979,7 +10322,7 @@ async function runObservedDependencies(client, input) {
9979
10322
  return {
9980
10323
  summary: `${input.nodeId} has ${observed.length} runtime dependenc${observed.length === 1 ? "y" : "ies"} confirmed by OTel.`,
9981
10324
  block: blockLines.join("\n"),
9982
- provenance: import_types25.Provenance.OBSERVED
10325
+ provenance: import_types26.Provenance.OBSERVED
9983
10326
  };
9984
10327
  } catch (err) {
9985
10328
  if (err instanceof HttpError && err.status === 404) {
@@ -10014,9 +10357,9 @@ function formatDuration(ms) {
10014
10357
  return `${Math.round(h / 24)}d`;
10015
10358
  }
10016
10359
  async function runIncidents(client, input) {
10017
- const path47 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
10360
+ const path49 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
10018
10361
  try {
10019
- const body = await client.get(path47);
10362
+ const body = await client.get(path49);
10020
10363
  const events = body.events;
10021
10364
  if (events.length === 0) {
10022
10365
  return {
@@ -10033,7 +10376,7 @@ async function runIncidents(client, input) {
10033
10376
  return {
10034
10377
  summary: `${target} has ${body.total} recorded incident${body.total === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
10035
10378
  block: blockLines.join("\n"),
10036
- provenance: import_types25.Provenance.OBSERVED
10379
+ provenance: import_types26.Provenance.OBSERVED
10037
10380
  };
10038
10381
  } catch (err) {
10039
10382
  if (err instanceof HttpError && err.status === 404) {
@@ -10142,7 +10485,7 @@ async function runStaleEdges(client, input) {
10142
10485
  return {
10143
10486
  summary: `${events.length} stale-edge transition${events.length === 1 ? "" : "s"} recorded${input.edgeType ? ` for ${input.edgeType}` : ""}.`,
10144
10487
  block: blockLines.join("\n"),
10145
- provenance: import_types25.Provenance.STALE
10488
+ provenance: import_types26.Provenance.STALE
10146
10489
  };
10147
10490
  }
10148
10491
  async function runPolicies(client, input) {
@@ -10306,7 +10649,7 @@ async function resolveProjectEntry(opts) {
10306
10649
  const cwd = opts.cwd ?? process.cwd();
10307
10650
  const resolvedCwd = await normalizeProjectPath(cwd);
10308
10651
  for (const entry2 of entries) {
10309
- if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path45.default.sep}`)) {
10652
+ if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path47.default.sep}`)) {
10310
10653
  return entry2;
10311
10654
  }
10312
10655
  }
@@ -10456,7 +10799,7 @@ async function runSync(opts) {
10456
10799
  }
10457
10800
 
10458
10801
  // src/cli.ts
10459
- var import_types26 = require("@neat.is/types");
10802
+ var import_types27 = require("@neat.is/types");
10460
10803
  function usage() {
10461
10804
  console.log("usage: neat <command> [args] [--project <name>]");
10462
10805
  console.log("");
@@ -10666,14 +11009,14 @@ function assignFlag(out, field, value) {
10666
11009
  out[field] = value;
10667
11010
  }
10668
11011
  function readPackageVersion() {
10669
- const here = typeof __dirname !== "undefined" ? __dirname : import_node_path46.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
11012
+ const here = typeof __dirname !== "undefined" ? __dirname : import_node_path48.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
10670
11013
  const candidates = [
10671
- import_node_path46.default.resolve(here, "../package.json"),
10672
- import_node_path46.default.resolve(here, "../../package.json")
11014
+ import_node_path48.default.resolve(here, "../package.json"),
11015
+ import_node_path48.default.resolve(here, "../../package.json")
10673
11016
  ];
10674
11017
  for (const candidate of candidates) {
10675
11018
  try {
10676
- const raw = (0, import_node_fs30.readFileSync)(candidate, "utf8");
11019
+ const raw = (0, import_node_fs31.readFileSync)(candidate, "utf8");
10677
11020
  const parsed = JSON.parse(raw);
10678
11021
  if (parsed.name === "@neat.is/core" && typeof parsed.version === "string") {
10679
11022
  return parsed.version;
@@ -10737,7 +11080,7 @@ async function buildPatchSections(services, project) {
10737
11080
  }
10738
11081
  async function runInit(opts) {
10739
11082
  const written = [];
10740
- const stat = await import_node_fs30.promises.stat(opts.scanPath).catch(() => null);
11083
+ const stat = await import_node_fs31.promises.stat(opts.scanPath).catch(() => null);
10741
11084
  if (!stat || !stat.isDirectory()) {
10742
11085
  console.error(`neat init: ${opts.scanPath} is not a directory`);
10743
11086
  return { exitCode: 2, writtenFiles: written };
@@ -10746,13 +11089,13 @@ async function runInit(opts) {
10746
11089
  printDiscoveryReport(opts, services);
10747
11090
  const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
10748
11091
  const patch = renderPatch(sections);
10749
- const patchPath = import_node_path46.default.join(opts.scanPath, "neat.patch");
11092
+ const patchPath = import_node_path48.default.join(opts.scanPath, "neat.patch");
10750
11093
  if (opts.dryRun) {
10751
- await import_node_fs30.promises.writeFile(patchPath, patch, "utf8");
11094
+ await import_node_fs31.promises.writeFile(patchPath, patch, "utf8");
10752
11095
  written.push(patchPath);
10753
11096
  console.log(`dry-run: patch written to ${patchPath}`);
10754
- const gitignorePath = import_node_path46.default.join(opts.scanPath, ".gitignore");
10755
- const gitignoreExists = await import_node_fs30.promises.stat(gitignorePath).then(() => true).catch(() => false);
11097
+ const gitignorePath = import_node_path48.default.join(opts.scanPath, ".gitignore");
11098
+ const gitignoreExists = await import_node_fs31.promises.stat(gitignorePath).then(() => true).catch(() => false);
10756
11099
  const verb = gitignoreExists ? "append" : "create";
10757
11100
  console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
10758
11101
  console.log("rerun without --dry-run to register and snapshot.");
@@ -10763,9 +11106,9 @@ async function runInit(opts) {
10763
11106
  const graph = getGraph(graphKey);
10764
11107
  const projectPaths = pathsForProject(
10765
11108
  graphKey,
10766
- import_node_path46.default.join(opts.scanPath, "neat-out")
11109
+ import_node_path48.default.join(opts.scanPath, "neat-out")
10767
11110
  );
10768
- const errorsPath = import_node_path46.default.join(import_node_path46.default.dirname(opts.outPath), import_node_path46.default.basename(projectPaths.errorsPath));
11111
+ const errorsPath = import_node_path48.default.join(import_node_path48.default.dirname(opts.outPath), import_node_path48.default.basename(projectPaths.errorsPath));
10769
11112
  const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
10770
11113
  await saveGraphToDisk(graph, opts.outPath);
10771
11114
  written.push(opts.outPath);
@@ -10844,7 +11187,7 @@ async function runInit(opts) {
10844
11187
  console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
10845
11188
  }
10846
11189
  } else {
10847
- await import_node_fs30.promises.writeFile(patchPath, patch, "utf8");
11190
+ await import_node_fs31.promises.writeFile(patchPath, patch, "utf8");
10848
11191
  written.push(patchPath);
10849
11192
  }
10850
11193
  }
@@ -10884,9 +11227,9 @@ var CLAUDE_SKILL_CONFIG = {
10884
11227
  };
10885
11228
  function claudeConfigPath() {
10886
11229
  const override = process.env.NEAT_CLAUDE_CONFIG;
10887
- if (override && override.length > 0) return import_node_path46.default.resolve(override);
11230
+ if (override && override.length > 0) return import_node_path48.default.resolve(override);
10888
11231
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
10889
- return import_node_path46.default.join(home, ".claude.json");
11232
+ return import_node_path48.default.join(home, ".claude.json");
10890
11233
  }
10891
11234
  async function runSkill(opts) {
10892
11235
  const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
@@ -10898,7 +11241,7 @@ async function runSkill(opts) {
10898
11241
  const target = claudeConfigPath();
10899
11242
  let existing = {};
10900
11243
  try {
10901
- existing = JSON.parse(await import_node_fs30.promises.readFile(target, "utf8"));
11244
+ existing = JSON.parse(await import_node_fs31.promises.readFile(target, "utf8"));
10902
11245
  } catch (err) {
10903
11246
  if (err.code !== "ENOENT") {
10904
11247
  console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
@@ -10910,8 +11253,8 @@ async function runSkill(opts) {
10910
11253
  ...existing,
10911
11254
  mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
10912
11255
  };
10913
- await import_node_fs30.promises.mkdir(import_node_path46.default.dirname(target), { recursive: true });
10914
- await import_node_fs30.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
11256
+ await import_node_fs31.promises.mkdir(import_node_path48.default.dirname(target), { recursive: true });
11257
+ await import_node_fs31.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
10915
11258
  console.log(`neat skill: wrote mcpServers.neat to ${target}`);
10916
11259
  console.log("restart Claude Code to pick up the new MCP server.");
10917
11260
  return { exitCode: 0 };
@@ -10949,12 +11292,12 @@ async function main() {
10949
11292
  console.error("neat init: --apply and --dry-run are mutually exclusive");
10950
11293
  process.exit(2);
10951
11294
  }
10952
- const scanPath = import_node_path46.default.resolve(target);
11295
+ const scanPath = import_node_path48.default.resolve(target);
10953
11296
  const projectExplicit = parsed.project !== null;
10954
- const projectName = projectExplicit ? project : import_node_path46.default.basename(scanPath);
11297
+ const projectName = projectExplicit ? project : import_node_path48.default.basename(scanPath);
10955
11298
  const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
10956
- const fallback = pathsForProject(projectKey, import_node_path46.default.join(scanPath, "neat-out")).snapshotPath;
10957
- const outPath = import_node_path46.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
11299
+ const fallback = pathsForProject(projectKey, import_node_path48.default.join(scanPath, "neat-out")).snapshotPath;
11300
+ const outPath = import_node_path48.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
10958
11301
  const result = await runInit({
10959
11302
  scanPath,
10960
11303
  outPath,
@@ -10975,21 +11318,21 @@ async function main() {
10975
11318
  usage();
10976
11319
  process.exit(2);
10977
11320
  }
10978
- const scanPath = import_node_path46.default.resolve(target);
10979
- const stat = await import_node_fs30.promises.stat(scanPath).catch(() => null);
11321
+ const scanPath = import_node_path48.default.resolve(target);
11322
+ const stat = await import_node_fs31.promises.stat(scanPath).catch(() => null);
10980
11323
  if (!stat || !stat.isDirectory()) {
10981
11324
  console.error(`neat watch: ${scanPath} is not a directory`);
10982
11325
  process.exit(2);
10983
11326
  }
10984
- const projectPaths = pathsForProject(project, import_node_path46.default.join(scanPath, "neat-out"));
10985
- const outPath = import_node_path46.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
10986
- const errorsPath = import_node_path46.default.resolve(
10987
- process.env.NEAT_ERRORS_PATH ?? import_node_path46.default.join(import_node_path46.default.dirname(outPath), import_node_path46.default.basename(projectPaths.errorsPath))
11327
+ const projectPaths = pathsForProject(project, import_node_path48.default.join(scanPath, "neat-out"));
11328
+ const outPath = import_node_path48.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
11329
+ const errorsPath = import_node_path48.default.resolve(
11330
+ process.env.NEAT_ERRORS_PATH ?? import_node_path48.default.join(import_node_path48.default.dirname(outPath), import_node_path48.default.basename(projectPaths.errorsPath))
10988
11331
  );
10989
- const staleEventsPath = import_node_path46.default.resolve(
10990
- process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path46.default.join(import_node_path46.default.dirname(outPath), import_node_path46.default.basename(projectPaths.staleEventsPath))
11332
+ const staleEventsPath = import_node_path48.default.resolve(
11333
+ process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path48.default.join(import_node_path48.default.dirname(outPath), import_node_path48.default.basename(projectPaths.staleEventsPath))
10991
11334
  );
10992
- const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path46.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
11335
+ const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path48.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
10993
11336
  const handle = await startWatch(getGraph(project), {
10994
11337
  scanPath,
10995
11338
  outPath,
@@ -11135,11 +11478,11 @@ async function main() {
11135
11478
  process.exit(1);
11136
11479
  }
11137
11480
  async function tryOrchestrator(cmd, parsed) {
11138
- const scanPath = import_node_path46.default.resolve(cmd);
11139
- const stat = await import_node_fs30.promises.stat(scanPath).catch(() => null);
11481
+ const scanPath = import_node_path48.default.resolve(cmd);
11482
+ const stat = await import_node_fs31.promises.stat(scanPath).catch(() => null);
11140
11483
  if (!stat || !stat.isDirectory()) return null;
11141
11484
  const projectExplicit = parsed.project !== null;
11142
- const projectName = projectExplicit ? parsed.project : import_node_path46.default.basename(scanPath);
11485
+ const projectName = projectExplicit ? parsed.project : import_node_path48.default.basename(scanPath);
11143
11486
  const result = await runOrchestrator({
11144
11487
  scanPath,
11145
11488
  project: projectName,
@@ -11289,10 +11632,10 @@ async function runQueryVerb(cmd, parsed) {
11289
11632
  const parts = parsed.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
11290
11633
  const out = [];
11291
11634
  for (const p of parts) {
11292
- const r = import_types26.DivergenceTypeSchema.safeParse(p);
11635
+ const r = import_types27.DivergenceTypeSchema.safeParse(p);
11293
11636
  if (!r.success) {
11294
11637
  console.error(
11295
- `neat divergences: unknown --type "${p}". allowed: ${import_types26.DivergenceTypeSchema.options.join(", ")}`
11638
+ `neat divergences: unknown --type "${p}". allowed: ${import_types27.DivergenceTypeSchema.options.join(", ")}`
11296
11639
  );
11297
11640
  return 2;
11298
11641
  }