@neat.is/core 0.4.11 → 0.4.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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 path46 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
61
+ const path49 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
62
62
  for (const suffix of suffixes) {
63
- if (path46 === suffix || path46.endsWith(suffix)) {
63
+ if (path49 === suffix || path49.endsWith(suffix)) {
64
64
  done();
65
65
  return;
66
66
  }
@@ -186,8 +186,8 @@ function reshapeGrpcRequest(req) {
186
186
  };
187
187
  }
188
188
  function resolveProtoRoot() {
189
- const here = import_node_path35.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
190
- return import_node_path35.default.resolve(here, "..", "proto");
189
+ const here = import_node_path39.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
190
+ return import_node_path39.default.resolve(here, "..", "proto");
191
191
  }
192
192
  function loadTraceService() {
193
193
  const protoRoot = resolveProtoRoot();
@@ -255,13 +255,13 @@ async function startOtelGrpcReceiver(opts) {
255
255
  })
256
256
  };
257
257
  }
258
- var import_node_url, import_node_path35, import_node_crypto2, grpc, protoLoader;
258
+ var import_node_url, import_node_path39, import_node_crypto2, grpc, protoLoader;
259
259
  var init_otel_grpc = __esm({
260
260
  "src/otel-grpc.ts"() {
261
261
  "use strict";
262
262
  init_cjs_shims();
263
263
  import_node_url = require("url");
264
- import_node_path35 = __toESM(require("path"), 1);
264
+ import_node_path39 = __toESM(require("path"), 1);
265
265
  import_node_crypto2 = require("crypto");
266
266
  grpc = __toESM(require("@grpc/grpc-js"), 1);
267
267
  protoLoader = __toESM(require("@grpc/proto-loader"), 1);
@@ -371,10 +371,10 @@ function parseOtlpRequest(body) {
371
371
  return out;
372
372
  }
373
373
  function loadProtoRoot() {
374
- const here = import_node_path36.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
375
- const protoRoot = import_node_path36.default.resolve(here, "..", "proto");
374
+ const here = import_node_path40.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
375
+ const protoRoot = import_node_path40.default.resolve(here, "..", "proto");
376
376
  const root = new import_protobufjs.default.Root();
377
- root.resolvePath = (_origin, target) => import_node_path36.default.resolve(protoRoot, target);
377
+ root.resolvePath = (_origin, target) => import_node_path40.default.resolve(protoRoot, target);
378
378
  root.loadSync(
379
379
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
380
380
  { keepCase: true }
@@ -567,12 +567,12 @@ async function buildOtelReceiver(opts) {
567
567
  };
568
568
  return decorated;
569
569
  }
570
- var import_node_path36, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
570
+ var import_node_path40, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
571
571
  var init_otel = __esm({
572
572
  "src/otel.ts"() {
573
573
  "use strict";
574
574
  init_cjs_shims();
575
- import_node_path36 = __toESM(require("path"), 1);
575
+ import_node_path40 = __toESM(require("path"), 1);
576
576
  import_node_url2 = require("url");
577
577
  import_fastify2 = __toESM(require("fastify"), 1);
578
578
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -600,8 +600,8 @@ __export(cli_exports, {
600
600
  });
601
601
  module.exports = __toCommonJS(cli_exports);
602
602
  init_cjs_shims();
603
- var import_node_path45 = __toESM(require("path"), 1);
604
- var import_node_fs29 = require("fs");
603
+ var import_node_path48 = __toESM(require("path"), 1);
604
+ var import_node_fs31 = require("fs");
605
605
  var import_node_url3 = require("url");
606
606
 
607
607
  // src/graph.ts
@@ -1131,19 +1131,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1131
1131
  function longestIncomingWalk(graph, start, maxDepth) {
1132
1132
  let best = { path: [start], edges: [] };
1133
1133
  const visited = /* @__PURE__ */ new Set([start]);
1134
- function step(node, path46, edges) {
1135
- if (path46.length > best.path.length) {
1136
- best = { path: [...path46], edges: [...edges] };
1134
+ function step(node, path49, edges) {
1135
+ if (path49.length > best.path.length) {
1136
+ best = { path: [...path49], edges: [...edges] };
1137
1137
  }
1138
- if (path46.length - 1 >= maxDepth) return;
1138
+ if (path49.length - 1 >= maxDepth) return;
1139
1139
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1140
1140
  for (const [srcId, edge] of incoming) {
1141
1141
  if (visited.has(srcId)) continue;
1142
1142
  visited.add(srcId);
1143
- path46.push(srcId);
1143
+ path49.push(srcId);
1144
1144
  edges.push(edge);
1145
- step(srcId, path46, edges);
1146
- path46.pop();
1145
+ step(srcId, path49, edges);
1146
+ path49.pop();
1147
1147
  edges.pop();
1148
1148
  visited.delete(srcId);
1149
1149
  }
@@ -1975,7 +1975,7 @@ function ensureFrontierNode(graph, host, ts) {
1975
1975
  graph.addNode(id, node);
1976
1976
  return id;
1977
1977
  }
1978
- function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
1978
+ function upsertObservedEdge(graph, type, source, target, ts, isError = false, evidence) {
1979
1979
  if (!graph.hasNode(source) || !graph.hasNode(target)) return null;
1980
1980
  const id = makeObservedEdgeId(type, source, target);
1981
1981
  if (graph.hasEdge(id)) {
@@ -2012,7 +2012,10 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
2012
2012
  confidence: (0, import_types3.confidenceForObservedSignal)(signal),
2013
2013
  lastObserved: ts,
2014
2014
  callCount: 1,
2015
- signal
2015
+ signal,
2016
+ // Call-site evidence from span code.* semconv (file-awareness.md §4 + §6).
2017
+ // Only set when code.filepath was present on the span — never fabricated.
2018
+ ...evidence ? { evidence } : {}
2016
2019
  };
2017
2020
  graph.addEdgeWithKey(id, source, target, edge);
2018
2021
  return { edge, created: true };
@@ -2106,6 +2109,7 @@ async function handleSpan(ctx, span) {
2106
2109
  const sourceServiceNode = ctx.graph.getNodeAttributes(sourceId);
2107
2110
  const callSite = callSiteFromSpan(span, sourceServiceNode, ctx.scanPath);
2108
2111
  const observedSource = () => callSite ? ensureObservedFileNode(ctx.graph, span.service, sourceId, callSite) : sourceId;
2112
+ const callSiteEvidence = callSite ? { file: callSite.relPath, ...callSite.line !== void 0 ? { line: callSite.line } : {} } : void 0;
2109
2113
  let affectedNode = sourceId;
2110
2114
  const mintsFromCallerSide = spanMintsObservedEdge(span.kind);
2111
2115
  if (span.dbSystem) {
@@ -2119,7 +2123,8 @@ async function handleSpan(ctx, span) {
2119
2123
  observedSource(),
2120
2124
  targetId,
2121
2125
  ts,
2122
- isError
2126
+ isError,
2127
+ callSiteEvidence
2123
2128
  );
2124
2129
  if (result) affectedNode = targetId;
2125
2130
  }
@@ -2135,7 +2140,8 @@ async function handleSpan(ctx, span) {
2135
2140
  observedSource(),
2136
2141
  targetId,
2137
2142
  ts,
2138
- isError
2143
+ isError,
2144
+ callSiteEvidence
2139
2145
  );
2140
2146
  affectedNode = targetId;
2141
2147
  resolvedViaAddress = true;
@@ -2147,7 +2153,8 @@ async function handleSpan(ctx, span) {
2147
2153
  observedSource(),
2148
2154
  frontierNodeId,
2149
2155
  ts,
2150
- isError
2156
+ isError,
2157
+ callSiteEvidence
2151
2158
  );
2152
2159
  affectedNode = frontierNodeId;
2153
2160
  resolvedViaAddress = true;
@@ -3108,16 +3115,433 @@ async function addServiceAliases(graph, scanPath, services) {
3108
3115
  await collectK8sAliases(graph, scanPath, byName);
3109
3116
  }
3110
3117
 
3111
- // src/extract/databases/index.ts
3118
+ // src/extract/files.ts
3112
3119
  init_cjs_shims();
3113
- var import_node_path17 = __toESM(require("path"), 1);
3120
+
3121
+ // src/extract/calls/shared.ts
3122
+ init_cjs_shims();
3123
+ var import_node_fs10 = require("fs");
3124
+ var import_node_path10 = __toESM(require("path"), 1);
3114
3125
  var import_types7 = require("@neat.is/types");
3126
+ async function walkSourceFiles(dir) {
3127
+ const out = [];
3128
+ async function walk(current) {
3129
+ const entries = await import_node_fs10.promises.readdir(current, { withFileTypes: true }).catch(() => []);
3130
+ for (const entry2 of entries) {
3131
+ const full = import_node_path10.default.join(current, entry2.name);
3132
+ if (entry2.isDirectory()) {
3133
+ if (IGNORED_DIRS.has(entry2.name)) continue;
3134
+ if (await isPythonVenvDir(full)) continue;
3135
+ await walk(full);
3136
+ } else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path10.default.extname(entry2.name))) {
3137
+ out.push(full);
3138
+ }
3139
+ }
3140
+ }
3141
+ await walk(dir);
3142
+ return out;
3143
+ }
3144
+ async function loadSourceFiles(dir) {
3145
+ const paths = await walkSourceFiles(dir);
3146
+ const out = [];
3147
+ for (const p of paths) {
3148
+ try {
3149
+ const content = await import_node_fs10.promises.readFile(p, "utf8");
3150
+ out.push({ path: p, content });
3151
+ } catch {
3152
+ }
3153
+ }
3154
+ return out;
3155
+ }
3156
+ function lineOf(text, needle) {
3157
+ const idx = text.indexOf(needle);
3158
+ if (idx < 0) return 1;
3159
+ return text.slice(0, idx).split("\n").length;
3160
+ }
3161
+ function snippet(text, line) {
3162
+ const lines = text.split("\n");
3163
+ return (lines[line - 1] ?? "").trim();
3164
+ }
3165
+ function toPosix2(p) {
3166
+ return p.split("\\").join("/");
3167
+ }
3168
+ function languageForPath(relPath) {
3169
+ switch (import_node_path10.default.extname(relPath).toLowerCase()) {
3170
+ case ".py":
3171
+ return "python";
3172
+ case ".ts":
3173
+ case ".tsx":
3174
+ return "typescript";
3175
+ case ".js":
3176
+ case ".jsx":
3177
+ case ".mjs":
3178
+ case ".cjs":
3179
+ return "javascript";
3180
+ default:
3181
+ return void 0;
3182
+ }
3183
+ }
3184
+ function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
3185
+ let nodesAdded = 0;
3186
+ let edgesAdded = 0;
3187
+ const fileNodeId = (0, import_types7.fileId)(serviceName, relPath);
3188
+ if (!graph.hasNode(fileNodeId)) {
3189
+ const language = languageForPath(relPath);
3190
+ const node = {
3191
+ id: fileNodeId,
3192
+ type: import_types7.NodeType.FileNode,
3193
+ service: serviceName,
3194
+ path: relPath,
3195
+ ...language ? { language } : {},
3196
+ discoveredVia: "static"
3197
+ };
3198
+ graph.addNode(fileNodeId, node);
3199
+ nodesAdded++;
3200
+ }
3201
+ const containsId = (0, import_types7.extractedEdgeId)(serviceNodeId, fileNodeId, import_types7.EdgeType.CONTAINS);
3202
+ if (!graph.hasEdge(containsId)) {
3203
+ const edge = {
3204
+ id: containsId,
3205
+ source: serviceNodeId,
3206
+ target: fileNodeId,
3207
+ type: import_types7.EdgeType.CONTAINS,
3208
+ provenance: import_types7.Provenance.EXTRACTED,
3209
+ confidence: (0, import_types7.confidenceForExtracted)("structural"),
3210
+ evidence: { file: relPath }
3211
+ };
3212
+ graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
3213
+ edgesAdded++;
3214
+ }
3215
+ return { fileNodeId, nodesAdded, edgesAdded };
3216
+ }
3217
+
3218
+ // src/extract/files.ts
3219
+ var import_node_path11 = __toESM(require("path"), 1);
3220
+ async function addFiles(graph, services) {
3221
+ let nodesAdded = 0;
3222
+ let edgesAdded = 0;
3223
+ for (const service of services) {
3224
+ const filePaths = await walkSourceFiles(service.dir);
3225
+ for (const filePath of filePaths) {
3226
+ const relPath = toPosix2(import_node_path11.default.relative(service.dir, filePath));
3227
+ const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
3228
+ graph,
3229
+ service.pkg.name,
3230
+ service.node.id,
3231
+ relPath
3232
+ );
3233
+ nodesAdded += n;
3234
+ edgesAdded += e;
3235
+ }
3236
+ }
3237
+ return { nodesAdded, edgesAdded };
3238
+ }
3239
+
3240
+ // src/extract/imports.ts
3241
+ init_cjs_shims();
3242
+ var import_node_path12 = __toESM(require("path"), 1);
3243
+ var import_node_fs11 = require("fs");
3244
+ var import_tree_sitter = __toESM(require("tree-sitter"), 1);
3245
+ var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
3246
+ var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
3247
+ var import_types8 = require("@neat.is/types");
3248
+ var PARSE_CHUNK = 16384;
3249
+ function parseSource(parser, source) {
3250
+ return parser.parse(
3251
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK)
3252
+ );
3253
+ }
3254
+ function makeJsParser() {
3255
+ const p = new import_tree_sitter.default();
3256
+ p.setLanguage(import_tree_sitter_javascript.default);
3257
+ return p;
3258
+ }
3259
+ function makePyParser() {
3260
+ const p = new import_tree_sitter.default();
3261
+ p.setLanguage(import_tree_sitter_python.default);
3262
+ return p;
3263
+ }
3264
+ function stringLiteralText(node) {
3265
+ for (let i = 0; i < node.childCount; i++) {
3266
+ const child = node.child(i);
3267
+ if (child?.type === "string_fragment") return child.text;
3268
+ }
3269
+ const raw = node.text;
3270
+ if (raw.length >= 2) return raw.slice(1, -1);
3271
+ return raw.length === 0 ? null : "";
3272
+ }
3273
+ function clipSnippet(text) {
3274
+ const oneLine = text.split("\n")[0] ?? text;
3275
+ return oneLine.length > 120 ? oneLine.slice(0, 120) : oneLine;
3276
+ }
3277
+ function collectJsImports(node, out) {
3278
+ if (node.type === "import_statement") {
3279
+ const source = node.childForFieldName("source");
3280
+ if (source) {
3281
+ const specifier = stringLiteralText(source);
3282
+ if (specifier) {
3283
+ out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
3284
+ }
3285
+ }
3286
+ return;
3287
+ }
3288
+ if (node.type === "call_expression") {
3289
+ const fn = node.childForFieldName("function");
3290
+ if (fn?.type === "identifier" && fn.text === "require") {
3291
+ const args = node.childForFieldName("arguments");
3292
+ const firstArg = args?.namedChild(0);
3293
+ if (firstArg?.type === "string") {
3294
+ const specifier = stringLiteralText(firstArg);
3295
+ if (specifier) {
3296
+ out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
3297
+ }
3298
+ }
3299
+ }
3300
+ }
3301
+ for (let i = 0; i < node.namedChildCount; i++) {
3302
+ const child = node.namedChild(i);
3303
+ if (child) collectJsImports(child, out);
3304
+ }
3305
+ }
3306
+ function collectPyImports(node, out) {
3307
+ if (node.type === "import_from_statement") {
3308
+ let level = 0;
3309
+ let modulePath = "";
3310
+ let pastFrom = false;
3311
+ for (let i = 0; i < node.childCount; i++) {
3312
+ const child = node.child(i);
3313
+ if (!child) continue;
3314
+ if (!pastFrom) {
3315
+ if (child.type === "from") pastFrom = true;
3316
+ continue;
3317
+ }
3318
+ if (child.type === "import") break;
3319
+ if (child.type === "relative_import") {
3320
+ for (let j = 0; j < child.childCount; j++) {
3321
+ const rc = child.child(j);
3322
+ if (!rc) continue;
3323
+ if (rc.type === "import_prefix") {
3324
+ for (let k = 0; k < rc.childCount; k++) {
3325
+ if (rc.child(k)?.type === ".") level++;
3326
+ }
3327
+ } else if (rc.type === "dotted_name") modulePath = rc.text;
3328
+ }
3329
+ break;
3330
+ }
3331
+ if (child.type === "dotted_name") {
3332
+ modulePath = child.text;
3333
+ break;
3334
+ }
3335
+ }
3336
+ if (level > 0 || modulePath) {
3337
+ out.push({ modulePath, level, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
3338
+ }
3339
+ }
3340
+ for (let i = 0; i < node.namedChildCount; i++) {
3341
+ const child = node.namedChild(i);
3342
+ if (child) collectPyImports(child, out);
3343
+ }
3344
+ }
3345
+ async function fileExists(p) {
3346
+ try {
3347
+ await import_node_fs11.promises.access(p);
3348
+ return true;
3349
+ } catch {
3350
+ return false;
3351
+ }
3352
+ }
3353
+ function isWithinServiceDir(candidate, serviceDir) {
3354
+ const rel = import_node_path12.default.relative(serviceDir, candidate);
3355
+ return rel !== "" && !rel.startsWith("..") && !import_node_path12.default.isAbsolute(rel);
3356
+ }
3357
+ var JS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
3358
+ var JS_INDEX_FILES = JS_EXTENSIONS.map((ext) => `index${ext}`);
3359
+ async function firstExistingCandidate(base, serviceDir) {
3360
+ for (const ext of JS_EXTENSIONS) {
3361
+ const candidate = base + ext;
3362
+ if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
3363
+ return toPosix2(import_node_path12.default.relative(serviceDir, candidate));
3364
+ }
3365
+ }
3366
+ for (const indexFile of JS_INDEX_FILES) {
3367
+ const candidate = import_node_path12.default.join(base, indexFile);
3368
+ if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
3369
+ return toPosix2(import_node_path12.default.relative(serviceDir, candidate));
3370
+ }
3371
+ }
3372
+ return null;
3373
+ }
3374
+ async function loadTsPathConfig(serviceDir) {
3375
+ const tsconfigPath = import_node_path12.default.join(serviceDir, "tsconfig.json");
3376
+ let raw;
3377
+ try {
3378
+ raw = await import_node_fs11.promises.readFile(tsconfigPath, "utf8");
3379
+ } catch {
3380
+ return null;
3381
+ }
3382
+ try {
3383
+ const parsed = JSON.parse(raw);
3384
+ const paths = parsed.compilerOptions?.paths;
3385
+ if (!paths || Object.keys(paths).length === 0) return null;
3386
+ const baseUrl = parsed.compilerOptions?.baseUrl;
3387
+ return { paths, baseDir: baseUrl ? import_node_path12.default.resolve(serviceDir, baseUrl) : serviceDir };
3388
+ } catch (err) {
3389
+ recordExtractionError("import alias resolution", tsconfigPath, err);
3390
+ return null;
3391
+ }
3392
+ }
3393
+ async function resolveTsAlias(specifier, config, serviceDir) {
3394
+ for (const [pattern, targets] of Object.entries(config.paths)) {
3395
+ let suffix = null;
3396
+ if (pattern === specifier) {
3397
+ suffix = "";
3398
+ } else if (pattern.endsWith("/*")) {
3399
+ const prefix = pattern.slice(0, -1);
3400
+ if (specifier.startsWith(prefix)) suffix = specifier.slice(prefix.length);
3401
+ }
3402
+ if (suffix === null) continue;
3403
+ for (const target of targets) {
3404
+ const targetBase = target.endsWith("/*") ? target.slice(0, -2) : target.replace(/\*$/, "");
3405
+ const resolvedBase = import_node_path12.default.resolve(config.baseDir, targetBase, suffix);
3406
+ const hit = await firstExistingCandidate(resolvedBase, serviceDir);
3407
+ if (hit) return hit;
3408
+ if (isWithinServiceDir(resolvedBase, serviceDir) && await fileExists(resolvedBase)) {
3409
+ return toPosix2(import_node_path12.default.relative(serviceDir, resolvedBase));
3410
+ }
3411
+ }
3412
+ }
3413
+ return null;
3414
+ }
3415
+ async function resolveJsImport(specifier, importerDir, serviceDir, tsPaths) {
3416
+ if (!specifier) return null;
3417
+ if (specifier.startsWith("./") || specifier.startsWith("../")) {
3418
+ const base = import_node_path12.default.resolve(importerDir, specifier);
3419
+ const ext = import_node_path12.default.extname(specifier);
3420
+ if (ext) {
3421
+ if (ext === ".js" || ext === ".jsx") {
3422
+ const tsExt = ext === ".jsx" ? ".tsx" : ".ts";
3423
+ const tsSibling = base.slice(0, -ext.length) + tsExt;
3424
+ if (isWithinServiceDir(tsSibling, serviceDir) && await fileExists(tsSibling)) {
3425
+ return toPosix2(import_node_path12.default.relative(serviceDir, tsSibling));
3426
+ }
3427
+ }
3428
+ if (isWithinServiceDir(base, serviceDir) && await fileExists(base)) {
3429
+ return toPosix2(import_node_path12.default.relative(serviceDir, base));
3430
+ }
3431
+ return null;
3432
+ }
3433
+ return firstExistingCandidate(base, serviceDir);
3434
+ }
3435
+ if (tsPaths) return resolveTsAlias(specifier, tsPaths, serviceDir);
3436
+ return null;
3437
+ }
3438
+ async function resolvePyImport(imp, importerPath, serviceDir) {
3439
+ if (!imp.modulePath) return null;
3440
+ const relPath = imp.modulePath.split(".").join("/");
3441
+ let baseDir;
3442
+ if (imp.level > 0) {
3443
+ baseDir = import_node_path12.default.dirname(importerPath);
3444
+ for (let i = 1; i < imp.level; i++) baseDir = import_node_path12.default.dirname(baseDir);
3445
+ } else {
3446
+ baseDir = serviceDir;
3447
+ }
3448
+ const candidates = [import_node_path12.default.join(baseDir, `${relPath}.py`), import_node_path12.default.join(baseDir, relPath, "__init__.py")];
3449
+ for (const candidate of candidates) {
3450
+ if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
3451
+ return toPosix2(import_node_path12.default.relative(serviceDir, candidate));
3452
+ }
3453
+ }
3454
+ return null;
3455
+ }
3456
+ function emitImportEdge(graph, serviceName, importerFileId, importerRelPath, importeeRelPath, line, snippet2) {
3457
+ const importeeFileId = (0, import_types8.fileId)(serviceName, importeeRelPath);
3458
+ if (!graph.hasNode(importeeFileId)) return 0;
3459
+ const edgeId = (0, import_types8.extractedEdgeId)(importerFileId, importeeFileId, import_types8.EdgeType.IMPORTS);
3460
+ if (graph.hasEdge(edgeId)) return 0;
3461
+ const edge = {
3462
+ id: edgeId,
3463
+ source: importerFileId,
3464
+ target: importeeFileId,
3465
+ type: import_types8.EdgeType.IMPORTS,
3466
+ provenance: import_types8.Provenance.EXTRACTED,
3467
+ confidence: (0, import_types8.confidenceForExtracted)("structural"),
3468
+ evidence: { file: importerRelPath, line, snippet: snippet2 }
3469
+ };
3470
+ graph.addEdgeWithKey(edgeId, importerFileId, importeeFileId, edge);
3471
+ return 1;
3472
+ }
3473
+ async function addImports(graph, services) {
3474
+ const jsParser = makeJsParser();
3475
+ const pyParser = makePyParser();
3476
+ let edgesAdded = 0;
3477
+ for (const service of services) {
3478
+ const tsPaths = await loadTsPathConfig(service.dir);
3479
+ const files = await loadSourceFiles(service.dir);
3480
+ for (const file of files) {
3481
+ if (isTestPath(file.path)) continue;
3482
+ const relFile = toPosix2(import_node_path12.default.relative(service.dir, file.path));
3483
+ const importerFileId = (0, import_types8.fileId)(service.pkg.name, relFile);
3484
+ const isPython = import_node_path12.default.extname(file.path) === ".py";
3485
+ if (isPython) {
3486
+ let pyImports = [];
3487
+ try {
3488
+ const tree = parseSource(pyParser, file.content);
3489
+ collectPyImports(tree.rootNode, pyImports);
3490
+ } catch (err) {
3491
+ recordExtractionError("import extraction", file.path, err);
3492
+ continue;
3493
+ }
3494
+ for (const imp of pyImports) {
3495
+ const resolved = await resolvePyImport(imp, file.path, service.dir);
3496
+ if (!resolved) continue;
3497
+ edgesAdded += emitImportEdge(
3498
+ graph,
3499
+ service.pkg.name,
3500
+ importerFileId,
3501
+ relFile,
3502
+ resolved,
3503
+ imp.line,
3504
+ imp.snippet
3505
+ );
3506
+ }
3507
+ continue;
3508
+ }
3509
+ let jsImports = [];
3510
+ try {
3511
+ const tree = parseSource(jsParser, file.content);
3512
+ collectJsImports(tree.rootNode, jsImports);
3513
+ } catch (err) {
3514
+ recordExtractionError("import extraction", file.path, err);
3515
+ continue;
3516
+ }
3517
+ for (const imp of jsImports) {
3518
+ const resolved = await resolveJsImport(imp.specifier, import_node_path12.default.dirname(file.path), service.dir, tsPaths);
3519
+ if (!resolved) continue;
3520
+ edgesAdded += emitImportEdge(
3521
+ graph,
3522
+ service.pkg.name,
3523
+ importerFileId,
3524
+ relFile,
3525
+ resolved,
3526
+ imp.line,
3527
+ imp.snippet
3528
+ );
3529
+ }
3530
+ }
3531
+ }
3532
+ return { nodesAdded: 0, edgesAdded };
3533
+ }
3534
+
3535
+ // src/extract/databases/index.ts
3536
+ init_cjs_shims();
3537
+ var import_node_path20 = __toESM(require("path"), 1);
3538
+ var import_types9 = require("@neat.is/types");
3115
3539
 
3116
3540
  // src/extract/databases/db-config-yaml.ts
3117
3541
  init_cjs_shims();
3118
- var import_node_path10 = __toESM(require("path"), 1);
3542
+ var import_node_path13 = __toESM(require("path"), 1);
3119
3543
  async function parse(serviceDir) {
3120
- const yamlPath = import_node_path10.default.join(serviceDir, "db-config.yaml");
3544
+ const yamlPath = import_node_path13.default.join(serviceDir, "db-config.yaml");
3121
3545
  if (!await exists(yamlPath)) return [];
3122
3546
  const raw = await readYaml(yamlPath);
3123
3547
  return [
@@ -3135,13 +3559,13 @@ var dbConfigYamlParser = { name: "db-config.yaml", parse };
3135
3559
 
3136
3560
  // src/extract/databases/dotenv.ts
3137
3561
  init_cjs_shims();
3138
- var import_node_fs11 = require("fs");
3139
- var import_node_path12 = __toESM(require("path"), 1);
3562
+ var import_node_fs13 = require("fs");
3563
+ var import_node_path15 = __toESM(require("path"), 1);
3140
3564
 
3141
3565
  // src/extract/databases/shared.ts
3142
3566
  init_cjs_shims();
3143
- var import_node_fs10 = require("fs");
3144
- var import_node_path11 = __toESM(require("path"), 1);
3567
+ var import_node_fs12 = require("fs");
3568
+ var import_node_path14 = __toESM(require("path"), 1);
3145
3569
  function schemeToEngine(scheme) {
3146
3570
  const s = scheme.toLowerCase().split("+")[0];
3147
3571
  switch (s) {
@@ -3180,14 +3604,14 @@ function parseConnectionString(url) {
3180
3604
  }
3181
3605
  async function readIfExists(filePath) {
3182
3606
  try {
3183
- return await import_node_fs10.promises.readFile(filePath, "utf8");
3607
+ return await import_node_fs12.promises.readFile(filePath, "utf8");
3184
3608
  } catch {
3185
3609
  return null;
3186
3610
  }
3187
3611
  }
3188
3612
  async function findFirst(serviceDir, candidates) {
3189
3613
  for (const rel of candidates) {
3190
- const abs = import_node_path11.default.join(serviceDir, rel);
3614
+ const abs = import_node_path14.default.join(serviceDir, rel);
3191
3615
  const content = await readIfExists(abs);
3192
3616
  if (content !== null) return abs;
3193
3617
  }
@@ -3238,15 +3662,15 @@ function parseDotenvLine(line) {
3238
3662
  return { key, value };
3239
3663
  }
3240
3664
  async function parse2(serviceDir) {
3241
- const entries = await import_node_fs11.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
3665
+ const entries = await import_node_fs13.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
3242
3666
  const configs = [];
3243
3667
  const seen = /* @__PURE__ */ new Set();
3244
3668
  for (const entry2 of entries) {
3245
3669
  if (!entry2.isFile()) continue;
3246
3670
  const match = isConfigFile(entry2.name);
3247
3671
  if (!match.match || match.fileType !== "env") continue;
3248
- const filePath = import_node_path12.default.join(serviceDir, entry2.name);
3249
- const content = await import_node_fs11.promises.readFile(filePath, "utf8");
3672
+ const filePath = import_node_path15.default.join(serviceDir, entry2.name);
3673
+ const content = await import_node_fs13.promises.readFile(filePath, "utf8");
3250
3674
  for (const line of content.split("\n")) {
3251
3675
  const parsed = parseDotenvLine(line);
3252
3676
  if (!parsed) continue;
@@ -3265,9 +3689,9 @@ var dotenvParser = { name: ".env", parse: parse2 };
3265
3689
 
3266
3690
  // src/extract/databases/prisma.ts
3267
3691
  init_cjs_shims();
3268
- var import_node_path13 = __toESM(require("path"), 1);
3692
+ var import_node_path16 = __toESM(require("path"), 1);
3269
3693
  async function parse3(serviceDir) {
3270
- const schemaPath = import_node_path13.default.join(serviceDir, "prisma", "schema.prisma");
3694
+ const schemaPath = import_node_path16.default.join(serviceDir, "prisma", "schema.prisma");
3271
3695
  const content = await readIfExists(schemaPath);
3272
3696
  if (!content) return [];
3273
3697
  const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
@@ -3399,10 +3823,10 @@ var knexParser = { name: "knex", parse: parse5 };
3399
3823
 
3400
3824
  // src/extract/databases/ormconfig.ts
3401
3825
  init_cjs_shims();
3402
- var import_node_path14 = __toESM(require("path"), 1);
3826
+ var import_node_path17 = __toESM(require("path"), 1);
3403
3827
  async function parse6(serviceDir) {
3404
3828
  for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
3405
- const abs = import_node_path14.default.join(serviceDir, candidate);
3829
+ const abs = import_node_path17.default.join(serviceDir, candidate);
3406
3830
  if (!await exists(abs)) continue;
3407
3831
  const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
3408
3832
  const entries = Array.isArray(raw) ? raw : [raw];
@@ -3462,9 +3886,9 @@ var typeormParser = { name: "typeorm", parse: parse7 };
3462
3886
 
3463
3887
  // src/extract/databases/sequelize.ts
3464
3888
  init_cjs_shims();
3465
- var import_node_path15 = __toESM(require("path"), 1);
3889
+ var import_node_path18 = __toESM(require("path"), 1);
3466
3890
  async function parse8(serviceDir) {
3467
- const configPath = import_node_path15.default.join(serviceDir, "config", "config.json");
3891
+ const configPath = import_node_path18.default.join(serviceDir, "config", "config.json");
3468
3892
  if (!await exists(configPath)) return [];
3469
3893
  const raw = await readJson(configPath);
3470
3894
  const out = [];
@@ -3491,7 +3915,7 @@ var sequelizeParser = { name: "sequelize", parse: parse8 };
3491
3915
 
3492
3916
  // src/extract/databases/docker-compose.ts
3493
3917
  init_cjs_shims();
3494
- var import_node_path16 = __toESM(require("path"), 1);
3918
+ var import_node_path19 = __toESM(require("path"), 1);
3495
3919
  function portFromService(svc) {
3496
3920
  for (const raw of svc.ports ?? []) {
3497
3921
  const str = String(raw);
@@ -3518,7 +3942,7 @@ function databaseFromEnv(svc) {
3518
3942
  }
3519
3943
  async function parse9(serviceDir) {
3520
3944
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
3521
- const abs = import_node_path16.default.join(serviceDir, name);
3945
+ const abs = import_node_path19.default.join(serviceDir, name);
3522
3946
  if (!await exists(abs)) continue;
3523
3947
  const raw = await readYaml(abs);
3524
3948
  if (!raw?.services) return [];
@@ -3559,8 +3983,8 @@ function compatibleDriversFor(engine) {
3559
3983
  }
3560
3984
  function toDatabaseNode(config) {
3561
3985
  return {
3562
- id: (0, import_types7.databaseId)(config.host),
3563
- type: import_types7.NodeType.DatabaseNode,
3986
+ id: (0, import_types9.databaseId)(config.host),
3987
+ type: import_types9.NodeType.DatabaseNode,
3564
3988
  name: config.database || config.host,
3565
3989
  engine: config.engine,
3566
3990
  engineVersion: config.engineVersion,
@@ -3690,19 +4114,24 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
3690
4114
  discoveredVia: mergedDiscoveredVia
3691
4115
  });
3692
4116
  }
4117
+ const relConfigFile = toPosix2(import_node_path20.default.relative(service.dir, config.sourceFile));
4118
+ const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
4119
+ graph,
4120
+ service.pkg.name,
4121
+ service.node.id,
4122
+ relConfigFile
4123
+ );
4124
+ nodesAdded += fn;
4125
+ edgesAdded += fe;
4126
+ const evidenceFile = toPosix2(import_node_path20.default.relative(scanPath, config.sourceFile));
3693
4127
  const edge = {
3694
- id: (0, import_types4.extractedEdgeId)(service.node.id, dbNode.id, import_types7.EdgeType.CONNECTS_TO),
3695
- source: service.node.id,
4128
+ id: (0, import_types4.extractedEdgeId)(fileNodeId, dbNode.id, import_types9.EdgeType.CONNECTS_TO),
4129
+ source: fileNodeId,
3696
4130
  target: dbNode.id,
3697
- type: import_types7.EdgeType.CONNECTS_TO,
3698
- provenance: import_types7.Provenance.EXTRACTED,
3699
- confidence: (0, import_types7.confidenceForExtracted)("structural"),
3700
- // ADR-032 / #140 — every EXTRACTED edge carries evidence.file.
3701
- // Ghost-edge cleanup keys retirement on this; the conditional
3702
- // sourceFile spread that used to live here was a v0.1.x leftover.
3703
- evidence: {
3704
- file: import_node_path17.default.relative(scanPath, config.sourceFile).split(import_node_path17.default.sep).join("/")
3705
- }
4131
+ type: import_types9.EdgeType.CONNECTS_TO,
4132
+ provenance: import_types9.Provenance.EXTRACTED,
4133
+ confidence: (0, import_types9.confidenceForExtracted)("structural"),
4134
+ evidence: { file: evidenceFile }
3706
4135
  };
3707
4136
  if (!graph.hasEdge(edge.id)) {
3708
4137
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -3728,15 +4157,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
3728
4157
 
3729
4158
  // src/extract/configs.ts
3730
4159
  init_cjs_shims();
3731
- var import_node_fs12 = require("fs");
3732
- var import_node_path18 = __toESM(require("path"), 1);
3733
- var import_types8 = require("@neat.is/types");
4160
+ var import_node_fs14 = require("fs");
4161
+ var import_node_path21 = __toESM(require("path"), 1);
4162
+ var import_types10 = require("@neat.is/types");
3734
4163
  async function walkConfigFiles(dir) {
3735
4164
  const out = [];
3736
4165
  async function walk(current) {
3737
- const entries = await import_node_fs12.promises.readdir(current, { withFileTypes: true });
4166
+ const entries = await import_node_fs14.promises.readdir(current, { withFileTypes: true });
3738
4167
  for (const entry2 of entries) {
3739
- const full = import_node_path18.default.join(current, entry2.name);
4168
+ const full = import_node_path21.default.join(current, entry2.name);
3740
4169
  if (entry2.isDirectory()) {
3741
4170
  if (IGNORED_DIRS.has(entry2.name)) continue;
3742
4171
  if (await isPythonVenvDir(full)) continue;
@@ -3755,26 +4184,35 @@ async function addConfigNodes(graph, services, scanPath) {
3755
4184
  for (const service of services) {
3756
4185
  const configFiles = await walkConfigFiles(service.dir);
3757
4186
  for (const file of configFiles) {
3758
- const relPath = import_node_path18.default.relative(scanPath, file);
4187
+ const relPath = import_node_path21.default.relative(scanPath, file);
3759
4188
  const node = {
3760
- id: (0, import_types8.configId)(relPath),
3761
- type: import_types8.NodeType.ConfigNode,
3762
- name: import_node_path18.default.basename(file),
4189
+ id: (0, import_types10.configId)(relPath),
4190
+ type: import_types10.NodeType.ConfigNode,
4191
+ name: import_node_path21.default.basename(file),
3763
4192
  path: relPath,
3764
- fileType: isConfigFile(import_node_path18.default.basename(file)).fileType
4193
+ fileType: isConfigFile(import_node_path21.default.basename(file)).fileType
3765
4194
  };
3766
4195
  if (!graph.hasNode(node.id)) {
3767
4196
  graph.addNode(node.id, node);
3768
4197
  nodesAdded++;
3769
4198
  }
4199
+ const relToService = toPosix2(import_node_path21.default.relative(service.dir, file));
4200
+ const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
4201
+ graph,
4202
+ service.pkg.name,
4203
+ service.node.id,
4204
+ relToService
4205
+ );
4206
+ nodesAdded += fn;
4207
+ edgesAdded += fe;
3770
4208
  const edge = {
3771
- id: (0, import_types4.extractedEdgeId)(service.node.id, node.id, import_types8.EdgeType.CONFIGURED_BY),
3772
- source: service.node.id,
4209
+ id: (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types10.EdgeType.CONFIGURED_BY),
4210
+ source: fileNodeId,
3773
4211
  target: node.id,
3774
- type: import_types8.EdgeType.CONFIGURED_BY,
3775
- provenance: import_types8.Provenance.EXTRACTED,
3776
- confidence: (0, import_types8.confidenceForExtracted)("structural"),
3777
- evidence: { file: relPath.split(import_node_path18.default.sep).join("/") }
4212
+ type: import_types10.EdgeType.CONFIGURED_BY,
4213
+ provenance: import_types10.Provenance.EXTRACTED,
4214
+ confidence: (0, import_types10.confidenceForExtracted)("structural"),
4215
+ evidence: { file: relPath.split(import_node_path21.default.sep).join("/") }
3778
4216
  };
3779
4217
  if (!graph.hasEdge(edge.id)) {
3780
4218
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -3787,114 +4225,15 @@ async function addConfigNodes(graph, services, scanPath) {
3787
4225
 
3788
4226
  // src/extract/calls/index.ts
3789
4227
  init_cjs_shims();
3790
- var import_types15 = require("@neat.is/types");
4228
+ var import_types16 = require("@neat.is/types");
3791
4229
 
3792
4230
  // src/extract/calls/http.ts
3793
4231
  init_cjs_shims();
3794
- var import_node_path20 = __toESM(require("path"), 1);
3795
- var import_tree_sitter = __toESM(require("tree-sitter"), 1);
3796
- var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
3797
- var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
3798
- var import_types10 = require("@neat.is/types");
3799
-
3800
- // src/extract/calls/shared.ts
3801
- init_cjs_shims();
3802
- var import_node_fs13 = require("fs");
3803
- var import_node_path19 = __toESM(require("path"), 1);
3804
- var import_types9 = require("@neat.is/types");
3805
- async function walkSourceFiles(dir) {
3806
- const out = [];
3807
- async function walk(current) {
3808
- const entries = await import_node_fs13.promises.readdir(current, { withFileTypes: true }).catch(() => []);
3809
- for (const entry2 of entries) {
3810
- const full = import_node_path19.default.join(current, entry2.name);
3811
- if (entry2.isDirectory()) {
3812
- if (IGNORED_DIRS.has(entry2.name)) continue;
3813
- if (await isPythonVenvDir(full)) continue;
3814
- await walk(full);
3815
- } else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path19.default.extname(entry2.name))) {
3816
- out.push(full);
3817
- }
3818
- }
3819
- }
3820
- await walk(dir);
3821
- return out;
3822
- }
3823
- async function loadSourceFiles(dir) {
3824
- const paths = await walkSourceFiles(dir);
3825
- const out = [];
3826
- for (const p of paths) {
3827
- try {
3828
- const content = await import_node_fs13.promises.readFile(p, "utf8");
3829
- out.push({ path: p, content });
3830
- } catch {
3831
- }
3832
- }
3833
- return out;
3834
- }
3835
- function lineOf(text, needle) {
3836
- const idx = text.indexOf(needle);
3837
- if (idx < 0) return 1;
3838
- return text.slice(0, idx).split("\n").length;
3839
- }
3840
- function snippet(text, line) {
3841
- const lines = text.split("\n");
3842
- return (lines[line - 1] ?? "").trim();
3843
- }
3844
- function toPosix2(p) {
3845
- return p.split("\\").join("/");
3846
- }
3847
- function languageForPath(relPath) {
3848
- switch (import_node_path19.default.extname(relPath).toLowerCase()) {
3849
- case ".py":
3850
- return "python";
3851
- case ".ts":
3852
- case ".tsx":
3853
- return "typescript";
3854
- case ".js":
3855
- case ".jsx":
3856
- case ".mjs":
3857
- case ".cjs":
3858
- return "javascript";
3859
- default:
3860
- return void 0;
3861
- }
3862
- }
3863
- function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
3864
- let nodesAdded = 0;
3865
- let edgesAdded = 0;
3866
- const fileNodeId = (0, import_types9.fileId)(serviceName, relPath);
3867
- if (!graph.hasNode(fileNodeId)) {
3868
- const language = languageForPath(relPath);
3869
- const node = {
3870
- id: fileNodeId,
3871
- type: import_types9.NodeType.FileNode,
3872
- service: serviceName,
3873
- path: relPath,
3874
- ...language ? { language } : {},
3875
- discoveredVia: "static"
3876
- };
3877
- graph.addNode(fileNodeId, node);
3878
- nodesAdded++;
3879
- }
3880
- const containsId = (0, import_types9.extractedEdgeId)(serviceNodeId, fileNodeId, import_types9.EdgeType.CONTAINS);
3881
- if (!graph.hasEdge(containsId)) {
3882
- const edge = {
3883
- id: containsId,
3884
- source: serviceNodeId,
3885
- target: fileNodeId,
3886
- type: import_types9.EdgeType.CONTAINS,
3887
- provenance: import_types9.Provenance.EXTRACTED,
3888
- confidence: (0, import_types9.confidenceForExtracted)("structural"),
3889
- evidence: { file: relPath }
3890
- };
3891
- graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
3892
- edgesAdded++;
3893
- }
3894
- return { fileNodeId, nodesAdded, edgesAdded };
3895
- }
3896
-
3897
- // src/extract/calls/http.ts
4232
+ var import_node_path22 = __toESM(require("path"), 1);
4233
+ var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
4234
+ var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
4235
+ var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
4236
+ var import_types11 = require("@neat.is/types");
3898
4237
  var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
3899
4238
  var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
3900
4239
  function isInsideJsxExternalLink(node) {
@@ -3922,14 +4261,14 @@ function collectStringLiterals(node, out) {
3922
4261
  if (child) collectStringLiterals(child, out);
3923
4262
  }
3924
4263
  }
3925
- var PARSE_CHUNK = 16384;
3926
- function parseSource(parser, source) {
4264
+ var PARSE_CHUNK2 = 16384;
4265
+ function parseSource2(parser, source) {
3927
4266
  return parser.parse(
3928
- (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK)
4267
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
3929
4268
  );
3930
4269
  }
3931
4270
  function callsFromSource(source, parser, knownHosts) {
3932
- const tree = parseSource(parser, source);
4271
+ const tree = parseSource2(parser, source);
3933
4272
  const literals = [];
3934
4273
  collectStringLiterals(tree.rootNode, literals);
3935
4274
  const out = [];
@@ -3943,25 +4282,25 @@ function callsFromSource(source, parser, knownHosts) {
3943
4282
  }
3944
4283
  return out;
3945
4284
  }
3946
- function makeJsParser() {
3947
- const p = new import_tree_sitter.default();
3948
- p.setLanguage(import_tree_sitter_javascript.default);
4285
+ function makeJsParser2() {
4286
+ const p = new import_tree_sitter2.default();
4287
+ p.setLanguage(import_tree_sitter_javascript2.default);
3949
4288
  return p;
3950
4289
  }
3951
- function makePyParser() {
3952
- const p = new import_tree_sitter.default();
3953
- p.setLanguage(import_tree_sitter_python.default);
4290
+ function makePyParser2() {
4291
+ const p = new import_tree_sitter2.default();
4292
+ p.setLanguage(import_tree_sitter_python2.default);
3954
4293
  return p;
3955
4294
  }
3956
4295
  async function addHttpCallEdges(graph, services) {
3957
- const jsParser = makeJsParser();
3958
- const pyParser = makePyParser();
4296
+ const jsParser = makeJsParser2();
4297
+ const pyParser = makePyParser2();
3959
4298
  const knownHosts = /* @__PURE__ */ new Set();
3960
4299
  const hostToNodeId = /* @__PURE__ */ new Map();
3961
4300
  for (const service of services) {
3962
- knownHosts.add(import_node_path20.default.basename(service.dir));
4301
+ knownHosts.add(import_node_path22.default.basename(service.dir));
3963
4302
  knownHosts.add(service.pkg.name);
3964
- hostToNodeId.set(import_node_path20.default.basename(service.dir), service.node.id);
4303
+ hostToNodeId.set(import_node_path22.default.basename(service.dir), service.node.id);
3965
4304
  hostToNodeId.set(service.pkg.name, service.node.id);
3966
4305
  }
3967
4306
  let nodesAdded = 0;
@@ -3971,7 +4310,7 @@ async function addHttpCallEdges(graph, services) {
3971
4310
  const seen = /* @__PURE__ */ new Set();
3972
4311
  for (const file of files) {
3973
4312
  if (isTestPath(file.path)) continue;
3974
- const parser = import_node_path20.default.extname(file.path) === ".py" ? pyParser : jsParser;
4313
+ const parser = import_node_path22.default.extname(file.path) === ".py" ? pyParser : jsParser;
3975
4314
  let sites;
3976
4315
  try {
3977
4316
  sites = callsFromSource(file.content, parser, knownHosts);
@@ -3980,14 +4319,14 @@ async function addHttpCallEdges(graph, services) {
3980
4319
  continue;
3981
4320
  }
3982
4321
  if (sites.length === 0) continue;
3983
- const relFile = toPosix2(import_node_path20.default.relative(service.dir, file.path));
4322
+ const relFile = toPosix2(import_node_path22.default.relative(service.dir, file.path));
3984
4323
  for (const site of sites) {
3985
4324
  const targetId = hostToNodeId.get(site.host);
3986
4325
  if (!targetId || targetId === service.node.id) continue;
3987
4326
  const dedupKey = `${relFile}|${targetId}`;
3988
4327
  if (seen.has(dedupKey)) continue;
3989
4328
  seen.add(dedupKey);
3990
- const confidence = (0, import_types10.confidenceForExtracted)("hostname-shape-match");
4329
+ const confidence = (0, import_types11.confidenceForExtracted)("hostname-shape-match");
3991
4330
  const ev = {
3992
4331
  file: relFile,
3993
4332
  line: site.line,
@@ -4001,25 +4340,25 @@ async function addHttpCallEdges(graph, services) {
4001
4340
  );
4002
4341
  nodesAdded += n;
4003
4342
  edgesAdded += e;
4004
- if (!(0, import_types10.passesExtractedFloor)(confidence)) {
4343
+ if (!(0, import_types11.passesExtractedFloor)(confidence)) {
4005
4344
  noteExtractedDropped({
4006
4345
  source: fileNodeId,
4007
4346
  target: targetId,
4008
- type: import_types10.EdgeType.CALLS,
4347
+ type: import_types11.EdgeType.CALLS,
4009
4348
  confidence,
4010
4349
  confidenceKind: "hostname-shape-match",
4011
4350
  evidence: ev
4012
4351
  });
4013
4352
  continue;
4014
4353
  }
4015
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types10.EdgeType.CALLS);
4354
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types11.EdgeType.CALLS);
4016
4355
  if (!graph.hasEdge(edgeId)) {
4017
4356
  const edge = {
4018
4357
  id: edgeId,
4019
4358
  source: fileNodeId,
4020
4359
  target: targetId,
4021
- type: import_types10.EdgeType.CALLS,
4022
- provenance: import_types10.Provenance.EXTRACTED,
4360
+ type: import_types11.EdgeType.CALLS,
4361
+ provenance: import_types11.Provenance.EXTRACTED,
4023
4362
  confidence,
4024
4363
  evidence: ev
4025
4364
  };
@@ -4034,8 +4373,8 @@ async function addHttpCallEdges(graph, services) {
4034
4373
 
4035
4374
  // src/extract/calls/kafka.ts
4036
4375
  init_cjs_shims();
4037
- var import_node_path21 = __toESM(require("path"), 1);
4038
- var import_types11 = require("@neat.is/types");
4376
+ var import_node_path23 = __toESM(require("path"), 1);
4377
+ var import_types12 = require("@neat.is/types");
4039
4378
  var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
4040
4379
  var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
4041
4380
  function findAll(re, text) {
@@ -4056,7 +4395,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
4056
4395
  seen.add(key);
4057
4396
  const line = lineOf(file.content, topic);
4058
4397
  out.push({
4059
- infraId: (0, import_types11.infraId)("kafka-topic", topic),
4398
+ infraId: (0, import_types12.infraId)("kafka-topic", topic),
4060
4399
  name: topic,
4061
4400
  kind: "kafka-topic",
4062
4401
  edgeType,
@@ -4065,7 +4404,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
4065
4404
  // tier (ADR-066).
4066
4405
  confidenceKind: "verified-call-site",
4067
4406
  evidence: {
4068
- file: import_node_path21.default.relative(serviceDir, file.path),
4407
+ file: import_node_path23.default.relative(serviceDir, file.path),
4069
4408
  line,
4070
4409
  snippet: snippet(file.content, line)
4071
4410
  }
@@ -4078,8 +4417,8 @@ function kafkaEndpointsFromFile(file, serviceDir) {
4078
4417
 
4079
4418
  // src/extract/calls/redis.ts
4080
4419
  init_cjs_shims();
4081
- var import_node_path22 = __toESM(require("path"), 1);
4082
- var import_types12 = require("@neat.is/types");
4420
+ var import_node_path24 = __toESM(require("path"), 1);
4421
+ var import_types13 = require("@neat.is/types");
4083
4422
  var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
4084
4423
  function redisEndpointsFromFile(file, serviceDir) {
4085
4424
  const out = [];
@@ -4092,7 +4431,7 @@ function redisEndpointsFromFile(file, serviceDir) {
4092
4431
  seen.add(host);
4093
4432
  const line = lineOf(file.content, host);
4094
4433
  out.push({
4095
- infraId: (0, import_types12.infraId)("redis", host),
4434
+ infraId: (0, import_types13.infraId)("redis", host),
4096
4435
  name: host,
4097
4436
  kind: "redis",
4098
4437
  edgeType: "CALLS",
@@ -4101,7 +4440,7 @@ function redisEndpointsFromFile(file, serviceDir) {
4101
4440
  // support tier (ADR-066).
4102
4441
  confidenceKind: "url-with-structural-support",
4103
4442
  evidence: {
4104
- file: import_node_path22.default.relative(serviceDir, file.path),
4443
+ file: import_node_path24.default.relative(serviceDir, file.path),
4105
4444
  line,
4106
4445
  snippet: snippet(file.content, line)
4107
4446
  }
@@ -4112,8 +4451,8 @@ function redisEndpointsFromFile(file, serviceDir) {
4112
4451
 
4113
4452
  // src/extract/calls/aws.ts
4114
4453
  init_cjs_shims();
4115
- var import_node_path23 = __toESM(require("path"), 1);
4116
- var import_types13 = require("@neat.is/types");
4454
+ var import_node_path25 = __toESM(require("path"), 1);
4455
+ var import_types14 = require("@neat.is/types");
4117
4456
  var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
4118
4457
  var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
4119
4458
  function hasMarker(text, markers) {
@@ -4137,7 +4476,7 @@ function awsEndpointsFromFile(file, serviceDir) {
4137
4476
  seen.add(key);
4138
4477
  const line = lineOf(file.content, name);
4139
4478
  out.push({
4140
- infraId: (0, import_types13.infraId)(kind, name),
4479
+ infraId: (0, import_types14.infraId)(kind, name),
4141
4480
  name,
4142
4481
  kind,
4143
4482
  edgeType: "CALLS",
@@ -4146,7 +4485,7 @@ function awsEndpointsFromFile(file, serviceDir) {
4146
4485
  // (ADR-066).
4147
4486
  confidenceKind: "verified-call-site",
4148
4487
  evidence: {
4149
- file: import_node_path23.default.relative(serviceDir, file.path),
4488
+ file: import_node_path25.default.relative(serviceDir, file.path),
4150
4489
  line,
4151
4490
  snippet: snippet(file.content, line)
4152
4491
  }
@@ -4171,13 +4510,14 @@ function awsEndpointsFromFile(file, serviceDir) {
4171
4510
 
4172
4511
  // src/extract/calls/grpc.ts
4173
4512
  init_cjs_shims();
4174
- var import_node_path24 = __toESM(require("path"), 1);
4175
- var import_types14 = require("@neat.is/types");
4513
+ var import_node_path26 = __toESM(require("path"), 1);
4514
+ var import_types15 = require("@neat.is/types");
4176
4515
  var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
4177
4516
  var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
4178
4517
  var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
4179
4518
  function isLikelyAddress(value) {
4180
4519
  if (!value) return false;
4520
+ if (/\s/.test(value) || value.startsWith("{")) return false;
4181
4521
  return /:\d{2,5}$/.test(value) || value.includes(".");
4182
4522
  }
4183
4523
  function normaliseForMatch(s) {
@@ -4220,7 +4560,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
4220
4560
  const { kind } = classified;
4221
4561
  const line = lineOf(file.content, m[0]);
4222
4562
  out.push({
4223
- infraId: (0, import_types14.infraId)(kind, name),
4563
+ infraId: (0, import_types15.infraId)(kind, name),
4224
4564
  name,
4225
4565
  kind,
4226
4566
  edgeType: "CALLS",
@@ -4229,7 +4569,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
4229
4569
  // tier (ADR-066).
4230
4570
  confidenceKind: "verified-call-site",
4231
4571
  evidence: {
4232
- file: import_node_path24.default.relative(serviceDir, file.path),
4572
+ file: import_node_path26.default.relative(serviceDir, file.path),
4233
4573
  line,
4234
4574
  snippet: snippet(file.content, line)
4235
4575
  }
@@ -4242,11 +4582,11 @@ function grpcEndpointsFromFile(file, serviceDir) {
4242
4582
  function edgeTypeFromEndpoint(ep) {
4243
4583
  switch (ep.edgeType) {
4244
4584
  case "PUBLISHES_TO":
4245
- return import_types15.EdgeType.PUBLISHES_TO;
4585
+ return import_types16.EdgeType.PUBLISHES_TO;
4246
4586
  case "CONSUMES_FROM":
4247
- return import_types15.EdgeType.CONSUMES_FROM;
4587
+ return import_types16.EdgeType.CONSUMES_FROM;
4248
4588
  default:
4249
- return import_types15.EdgeType.CALLS;
4589
+ return import_types16.EdgeType.CALLS;
4250
4590
  }
4251
4591
  }
4252
4592
  function isAwsKind(kind) {
@@ -4273,7 +4613,7 @@ async function addExternalEndpointEdges(graph, services) {
4273
4613
  if (!graph.hasNode(ep.infraId)) {
4274
4614
  const node = {
4275
4615
  id: ep.infraId,
4276
- type: import_types15.NodeType.InfraNode,
4616
+ type: import_types16.NodeType.InfraNode,
4277
4617
  name: ep.name,
4278
4618
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
4279
4619
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -4285,7 +4625,7 @@ async function addExternalEndpointEdges(graph, services) {
4285
4625
  nodesAdded++;
4286
4626
  }
4287
4627
  const edgeType = edgeTypeFromEndpoint(ep);
4288
- const confidence = (0, import_types15.confidenceForExtracted)(ep.confidenceKind);
4628
+ const confidence = (0, import_types16.confidenceForExtracted)(ep.confidenceKind);
4289
4629
  const relFile = toPosix2(ep.evidence.file);
4290
4630
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
4291
4631
  graph,
@@ -4295,7 +4635,7 @@ async function addExternalEndpointEdges(graph, services) {
4295
4635
  );
4296
4636
  nodesAdded += n;
4297
4637
  edgesAdded += e;
4298
- if (!(0, import_types15.passesExtractedFloor)(confidence)) {
4638
+ if (!(0, import_types16.passesExtractedFloor)(confidence)) {
4299
4639
  noteExtractedDropped({
4300
4640
  source: fileNodeId,
4301
4641
  target: ep.infraId,
@@ -4315,7 +4655,7 @@ async function addExternalEndpointEdges(graph, services) {
4315
4655
  source: fileNodeId,
4316
4656
  target: ep.infraId,
4317
4657
  type: edgeType,
4318
- provenance: import_types15.Provenance.EXTRACTED,
4658
+ provenance: import_types16.Provenance.EXTRACTED,
4319
4659
  confidence,
4320
4660
  evidence: ep.evidence
4321
4661
  };
@@ -4340,16 +4680,16 @@ init_cjs_shims();
4340
4680
 
4341
4681
  // src/extract/infra/docker-compose.ts
4342
4682
  init_cjs_shims();
4343
- var import_node_path25 = __toESM(require("path"), 1);
4344
- var import_types17 = require("@neat.is/types");
4683
+ var import_node_path27 = __toESM(require("path"), 1);
4684
+ var import_types18 = require("@neat.is/types");
4345
4685
 
4346
4686
  // src/extract/infra/shared.ts
4347
4687
  init_cjs_shims();
4348
- var import_types16 = require("@neat.is/types");
4688
+ var import_types17 = require("@neat.is/types");
4349
4689
  function makeInfraNode(kind, name, provider = "self", extras) {
4350
4690
  return {
4351
- id: (0, import_types16.infraId)(kind, name),
4352
- type: import_types16.NodeType.InfraNode,
4691
+ id: (0, import_types17.infraId)(kind, name),
4692
+ type: import_types17.NodeType.InfraNode,
4353
4693
  name,
4354
4694
  provider,
4355
4695
  kind,
@@ -4378,7 +4718,7 @@ function dependsOnList(value) {
4378
4718
  }
4379
4719
  function serviceNameToServiceNode(name, services) {
4380
4720
  for (const s of services) {
4381
- if (s.node.name === name || import_node_path25.default.basename(s.dir) === name) return s.node.id;
4721
+ if (s.node.name === name || import_node_path27.default.basename(s.dir) === name) return s.node.id;
4382
4722
  }
4383
4723
  return null;
4384
4724
  }
@@ -4387,7 +4727,7 @@ async function addComposeInfra(graph, scanPath, services) {
4387
4727
  let edgesAdded = 0;
4388
4728
  let composePath = null;
4389
4729
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
4390
- const abs = import_node_path25.default.join(scanPath, name);
4730
+ const abs = import_node_path27.default.join(scanPath, name);
4391
4731
  if (await exists(abs)) {
4392
4732
  composePath = abs;
4393
4733
  break;
@@ -4400,13 +4740,13 @@ async function addComposeInfra(graph, scanPath, services) {
4400
4740
  } catch (err) {
4401
4741
  recordExtractionError(
4402
4742
  "infra docker-compose",
4403
- import_node_path25.default.relative(scanPath, composePath),
4743
+ import_node_path27.default.relative(scanPath, composePath),
4404
4744
  err
4405
4745
  );
4406
4746
  return { nodesAdded, edgesAdded };
4407
4747
  }
4408
4748
  if (!compose?.services) return { nodesAdded, edgesAdded };
4409
- const evidenceFile = import_node_path25.default.relative(scanPath, composePath).split(import_node_path25.default.sep).join("/");
4749
+ const evidenceFile = import_node_path27.default.relative(scanPath, composePath).split(import_node_path27.default.sep).join("/");
4410
4750
  const composeNameToNodeId = /* @__PURE__ */ new Map();
4411
4751
  for (const [composeName, svc] of Object.entries(compose.services)) {
4412
4752
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -4428,15 +4768,15 @@ async function addComposeInfra(graph, scanPath, services) {
4428
4768
  for (const dep of dependsOnList(svc.depends_on)) {
4429
4769
  const targetId = composeNameToNodeId.get(dep);
4430
4770
  if (!targetId) continue;
4431
- const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types17.EdgeType.DEPENDS_ON);
4771
+ const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types18.EdgeType.DEPENDS_ON);
4432
4772
  if (graph.hasEdge(edgeId)) continue;
4433
4773
  const edge = {
4434
4774
  id: edgeId,
4435
4775
  source: sourceId,
4436
4776
  target: targetId,
4437
- type: import_types17.EdgeType.DEPENDS_ON,
4438
- provenance: import_types17.Provenance.EXTRACTED,
4439
- confidence: (0, import_types17.confidenceForExtracted)("structural"),
4777
+ type: import_types18.EdgeType.DEPENDS_ON,
4778
+ provenance: import_types18.Provenance.EXTRACTED,
4779
+ confidence: (0, import_types18.confidenceForExtracted)("structural"),
4440
4780
  evidence: { file: evidenceFile }
4441
4781
  };
4442
4782
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -4448,9 +4788,9 @@ async function addComposeInfra(graph, scanPath, services) {
4448
4788
 
4449
4789
  // src/extract/infra/dockerfile.ts
4450
4790
  init_cjs_shims();
4451
- var import_node_path26 = __toESM(require("path"), 1);
4452
- var import_node_fs14 = require("fs");
4453
- var import_types18 = require("@neat.is/types");
4791
+ var import_node_path28 = __toESM(require("path"), 1);
4792
+ var import_node_fs15 = require("fs");
4793
+ var import_types19 = require("@neat.is/types");
4454
4794
  function runtimeImage(content) {
4455
4795
  const lines = content.split("\n");
4456
4796
  let last = null;
@@ -4469,15 +4809,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4469
4809
  let nodesAdded = 0;
4470
4810
  let edgesAdded = 0;
4471
4811
  for (const service of services) {
4472
- const dockerfilePath = import_node_path26.default.join(service.dir, "Dockerfile");
4812
+ const dockerfilePath = import_node_path28.default.join(service.dir, "Dockerfile");
4473
4813
  if (!await exists(dockerfilePath)) continue;
4474
4814
  let content;
4475
4815
  try {
4476
- content = await import_node_fs14.promises.readFile(dockerfilePath, "utf8");
4816
+ content = await import_node_fs15.promises.readFile(dockerfilePath, "utf8");
4477
4817
  } catch (err) {
4478
4818
  recordExtractionError(
4479
4819
  "infra dockerfile",
4480
- import_node_path26.default.relative(scanPath, dockerfilePath),
4820
+ import_node_path28.default.relative(scanPath, dockerfilePath),
4481
4821
  err
4482
4822
  );
4483
4823
  continue;
@@ -4489,17 +4829,26 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4489
4829
  graph.addNode(node.id, node);
4490
4830
  nodesAdded++;
4491
4831
  }
4492
- const edgeId = (0, import_types4.extractedEdgeId)(service.node.id, node.id, import_types18.EdgeType.RUNS_ON);
4832
+ const relDockerfile = toPosix2(import_node_path28.default.relative(service.dir, dockerfilePath));
4833
+ const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
4834
+ graph,
4835
+ service.pkg.name,
4836
+ service.node.id,
4837
+ relDockerfile
4838
+ );
4839
+ nodesAdded += fn;
4840
+ edgesAdded += fe;
4841
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types19.EdgeType.RUNS_ON);
4493
4842
  if (!graph.hasEdge(edgeId)) {
4494
4843
  const edge = {
4495
4844
  id: edgeId,
4496
- source: service.node.id,
4845
+ source: fileNodeId,
4497
4846
  target: node.id,
4498
- type: import_types18.EdgeType.RUNS_ON,
4499
- provenance: import_types18.Provenance.EXTRACTED,
4500
- confidence: (0, import_types18.confidenceForExtracted)("structural"),
4847
+ type: import_types19.EdgeType.RUNS_ON,
4848
+ provenance: import_types19.Provenance.EXTRACTED,
4849
+ confidence: (0, import_types19.confidenceForExtracted)("structural"),
4501
4850
  evidence: {
4502
- file: import_node_path26.default.relative(scanPath, dockerfilePath).split(import_node_path26.default.sep).join("/")
4851
+ file: toPosix2(import_node_path28.default.relative(scanPath, dockerfilePath))
4503
4852
  }
4504
4853
  };
4505
4854
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -4511,21 +4860,21 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4511
4860
 
4512
4861
  // src/extract/infra/terraform.ts
4513
4862
  init_cjs_shims();
4514
- var import_node_fs15 = require("fs");
4515
- var import_node_path27 = __toESM(require("path"), 1);
4863
+ var import_node_fs16 = require("fs");
4864
+ var import_node_path29 = __toESM(require("path"), 1);
4516
4865
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
4517
4866
  async function walkTfFiles(start, depth = 0, max = 5) {
4518
4867
  if (depth > max) return [];
4519
4868
  const out = [];
4520
- const entries = await import_node_fs15.promises.readdir(start, { withFileTypes: true }).catch(() => []);
4869
+ const entries = await import_node_fs16.promises.readdir(start, { withFileTypes: true }).catch(() => []);
4521
4870
  for (const entry2 of entries) {
4522
4871
  if (entry2.isDirectory()) {
4523
4872
  if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
4524
- const child = import_node_path27.default.join(start, entry2.name);
4873
+ const child = import_node_path29.default.join(start, entry2.name);
4525
4874
  if (await isPythonVenvDir(child)) continue;
4526
4875
  out.push(...await walkTfFiles(child, depth + 1, max));
4527
4876
  } else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
4528
- out.push(import_node_path27.default.join(start, entry2.name));
4877
+ out.push(import_node_path29.default.join(start, entry2.name));
4529
4878
  }
4530
4879
  }
4531
4880
  return out;
@@ -4534,7 +4883,7 @@ async function addTerraformResources(graph, scanPath) {
4534
4883
  let nodesAdded = 0;
4535
4884
  const files = await walkTfFiles(scanPath);
4536
4885
  for (const file of files) {
4537
- const content = await import_node_fs15.promises.readFile(file, "utf8");
4886
+ const content = await import_node_fs16.promises.readFile(file, "utf8");
4538
4887
  RESOURCE_RE.lastIndex = 0;
4539
4888
  let m;
4540
4889
  while ((m = RESOURCE_RE.exec(content)) !== null) {
@@ -4552,8 +4901,8 @@ async function addTerraformResources(graph, scanPath) {
4552
4901
 
4553
4902
  // src/extract/infra/k8s.ts
4554
4903
  init_cjs_shims();
4555
- var import_node_fs16 = require("fs");
4556
- var import_node_path28 = __toESM(require("path"), 1);
4904
+ var import_node_fs17 = require("fs");
4905
+ var import_node_path30 = __toESM(require("path"), 1);
4557
4906
  var import_yaml3 = require("yaml");
4558
4907
  var K8S_KIND_TO_INFRA_KIND = {
4559
4908
  Service: "k8s-service",
@@ -4567,15 +4916,15 @@ var K8S_KIND_TO_INFRA_KIND = {
4567
4916
  async function walkYamlFiles2(start, depth = 0, max = 5) {
4568
4917
  if (depth > max) return [];
4569
4918
  const out = [];
4570
- const entries = await import_node_fs16.promises.readdir(start, { withFileTypes: true }).catch(() => []);
4919
+ const entries = await import_node_fs17.promises.readdir(start, { withFileTypes: true }).catch(() => []);
4571
4920
  for (const entry2 of entries) {
4572
4921
  if (entry2.isDirectory()) {
4573
4922
  if (IGNORED_DIRS.has(entry2.name)) continue;
4574
- const child = import_node_path28.default.join(start, entry2.name);
4923
+ const child = import_node_path30.default.join(start, entry2.name);
4575
4924
  if (await isPythonVenvDir(child)) continue;
4576
4925
  out.push(...await walkYamlFiles2(child, depth + 1, max));
4577
- } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path28.default.extname(entry2.name))) {
4578
- out.push(import_node_path28.default.join(start, entry2.name));
4926
+ } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path30.default.extname(entry2.name))) {
4927
+ out.push(import_node_path30.default.join(start, entry2.name));
4579
4928
  }
4580
4929
  }
4581
4930
  return out;
@@ -4584,7 +4933,7 @@ async function addK8sResources(graph, scanPath) {
4584
4933
  let nodesAdded = 0;
4585
4934
  const files = await walkYamlFiles2(scanPath);
4586
4935
  for (const file of files) {
4587
- const content = await import_node_fs16.promises.readFile(file, "utf8");
4936
+ const content = await import_node_fs17.promises.readFile(file, "utf8");
4588
4937
  let docs;
4589
4938
  try {
4590
4939
  docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
@@ -4619,17 +4968,17 @@ async function addInfra(graph, scanPath, services) {
4619
4968
  }
4620
4969
 
4621
4970
  // src/extract/index.ts
4622
- var import_node_path30 = __toESM(require("path"), 1);
4971
+ var import_node_path32 = __toESM(require("path"), 1);
4623
4972
 
4624
4973
  // src/extract/retire.ts
4625
4974
  init_cjs_shims();
4626
- var import_node_fs17 = require("fs");
4627
- var import_node_path29 = __toESM(require("path"), 1);
4628
- var import_types19 = require("@neat.is/types");
4975
+ var import_node_fs18 = require("fs");
4976
+ var import_node_path31 = __toESM(require("path"), 1);
4977
+ var import_types20 = require("@neat.is/types");
4629
4978
  function dropOrphanedFileNodes(graph) {
4630
4979
  const orphans = [];
4631
4980
  graph.forEachNode((id, attrs) => {
4632
- if (attrs.type !== import_types19.NodeType.FileNode) return;
4981
+ if (attrs.type !== import_types20.NodeType.FileNode) return;
4633
4982
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
4634
4983
  orphans.push(id);
4635
4984
  }
@@ -4642,7 +4991,7 @@ function retireEdgesByFile(graph, file) {
4642
4991
  const toDrop = [];
4643
4992
  graph.forEachEdge((id, attrs) => {
4644
4993
  const edge = attrs;
4645
- if (edge.provenance !== import_types19.Provenance.EXTRACTED) return;
4994
+ if (edge.provenance !== import_types20.Provenance.EXTRACTED) return;
4646
4995
  if (!edge.evidence?.file) return;
4647
4996
  if (edge.evidence.file === normalized) toDrop.push(id);
4648
4997
  });
@@ -4655,14 +5004,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
4655
5004
  const bases = [scanPath, ...serviceDirs];
4656
5005
  graph.forEachEdge((id, attrs) => {
4657
5006
  const edge = attrs;
4658
- if (edge.provenance !== import_types19.Provenance.EXTRACTED) return;
5007
+ if (edge.provenance !== import_types20.Provenance.EXTRACTED) return;
4659
5008
  const evidenceFile = edge.evidence?.file;
4660
5009
  if (!evidenceFile) return;
4661
- if (import_node_path29.default.isAbsolute(evidenceFile)) {
4662
- if (!(0, import_node_fs17.existsSync)(evidenceFile)) toDrop.push(id);
5010
+ if (import_node_path31.default.isAbsolute(evidenceFile)) {
5011
+ if (!(0, import_node_fs18.existsSync)(evidenceFile)) toDrop.push(id);
4663
5012
  return;
4664
5013
  }
4665
- const found = bases.some((base) => (0, import_node_fs17.existsSync)(import_node_path29.default.join(base, evidenceFile)));
5014
+ const found = bases.some((base) => (0, import_node_fs18.existsSync)(import_node_path31.default.join(base, evidenceFile)));
4666
5015
  if (!found) toDrop.push(id);
4667
5016
  });
4668
5017
  for (const id of toDrop) graph.dropEdge(id);
@@ -4677,6 +5026,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
4677
5026
  const services = await discoverServices(scanPath);
4678
5027
  const phase1Nodes = addServiceNodes(graph, services);
4679
5028
  await addServiceAliases(graph, scanPath, services);
5029
+ const fileEnum = await addFiles(graph, services);
5030
+ const importGraph = await addImports(graph, services);
4680
5031
  const phase2 = await addDatabasesAndCompat(graph, services, scanPath);
4681
5032
  const phase3 = await addConfigNodes(graph, services, scanPath);
4682
5033
  const phase4 = await addCallEdges(graph, services);
@@ -4700,7 +5051,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
4700
5051
  }
4701
5052
  const droppedEntries = drainDroppedExtracted();
4702
5053
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
4703
- const rejectedPath = import_node_path30.default.join(import_node_path30.default.dirname(opts.errorsPath), "rejected.ndjson");
5054
+ const rejectedPath = import_node_path32.default.join(import_node_path32.default.dirname(opts.errorsPath), "rejected.ndjson");
4704
5055
  try {
4705
5056
  await writeRejectedExtracted(droppedEntries, rejectedPath);
4706
5057
  } catch (err) {
@@ -4710,8 +5061,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
4710
5061
  }
4711
5062
  }
4712
5063
  const result = {
4713
- nodesAdded: phase1Nodes + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
4714
- edgesAdded: phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
5064
+ nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
5065
+ edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
4715
5066
  frontiersPromoted,
4716
5067
  extractionErrors: errorEntries.length,
4717
5068
  errorEntries,
@@ -4734,7 +5085,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
4734
5085
 
4735
5086
  // src/divergences.ts
4736
5087
  init_cjs_shims();
4737
- var import_types20 = require("@neat.is/types");
5088
+ var import_types21 = require("@neat.is/types");
4738
5089
  function bucketKey(source, target, type) {
4739
5090
  return `${type}|${source}|${target}`;
4740
5091
  }
@@ -4742,22 +5093,22 @@ function bucketEdges(graph) {
4742
5093
  const buckets = /* @__PURE__ */ new Map();
4743
5094
  graph.forEachEdge((id, attrs) => {
4744
5095
  const e = attrs;
4745
- const parsed = (0, import_types20.parseEdgeId)(id);
5096
+ const parsed = (0, import_types21.parseEdgeId)(id);
4746
5097
  const provenance = parsed?.provenance ?? e.provenance;
4747
5098
  const key = bucketKey(e.source, e.target, e.type);
4748
5099
  const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
4749
5100
  switch (provenance) {
4750
- case import_types20.Provenance.EXTRACTED:
5101
+ case import_types21.Provenance.EXTRACTED:
4751
5102
  cur.extracted = e;
4752
5103
  break;
4753
- case import_types20.Provenance.OBSERVED:
5104
+ case import_types21.Provenance.OBSERVED:
4754
5105
  cur.observed = e;
4755
5106
  break;
4756
- case import_types20.Provenance.INFERRED:
5107
+ case import_types21.Provenance.INFERRED:
4757
5108
  cur.inferred = e;
4758
5109
  break;
4759
5110
  default:
4760
- if (e.provenance === import_types20.Provenance.STALE) cur.stale = e;
5111
+ if (e.provenance === import_types21.Provenance.STALE) cur.stale = e;
4761
5112
  }
4762
5113
  buckets.set(key, cur);
4763
5114
  });
@@ -4766,7 +5117,7 @@ function bucketEdges(graph) {
4766
5117
  function nodeIsFrontier(graph, nodeId) {
4767
5118
  if (!graph.hasNode(nodeId)) return false;
4768
5119
  const attrs = graph.getNodeAttributes(nodeId);
4769
- return attrs.type === import_types20.NodeType.FrontierNode;
5120
+ return attrs.type === import_types21.NodeType.FrontierNode;
4770
5121
  }
4771
5122
  function clampConfidence(n) {
4772
5123
  if (!Number.isFinite(n)) return 0;
@@ -4787,7 +5138,7 @@ function gradedConfidence(edge) {
4787
5138
  }
4788
5139
  function detectMissingDivergences(graph, bucket) {
4789
5140
  const out = [];
4790
- if (bucket.type === import_types20.EdgeType.CONTAINS) return out;
5141
+ if (bucket.type === import_types21.EdgeType.CONTAINS) return out;
4791
5142
  if (bucket.extracted && !bucket.observed) {
4792
5143
  if (!nodeIsFrontier(graph, bucket.target)) {
4793
5144
  out.push({
@@ -4828,7 +5179,7 @@ function declaredHostFor(svc) {
4828
5179
  function hasExtractedConfiguredBy(graph, svcId) {
4829
5180
  for (const edgeId of graph.outboundEdges(svcId)) {
4830
5181
  const e = graph.getEdgeAttributes(edgeId);
4831
- if (e.type === import_types20.EdgeType.CONFIGURED_BY && e.provenance === import_types20.Provenance.EXTRACTED) {
5182
+ if (e.type === import_types21.EdgeType.CONFIGURED_BY && e.provenance === import_types21.Provenance.EXTRACTED) {
4832
5183
  return true;
4833
5184
  }
4834
5185
  }
@@ -4841,10 +5192,10 @@ function detectHostMismatch(graph, svcId, svc) {
4841
5192
  const out = [];
4842
5193
  for (const edgeId of graph.outboundEdges(svcId)) {
4843
5194
  const edge = graph.getEdgeAttributes(edgeId);
4844
- if (edge.type !== import_types20.EdgeType.CONNECTS_TO) continue;
4845
- if (edge.provenance !== import_types20.Provenance.OBSERVED) continue;
5195
+ if (edge.type !== import_types21.EdgeType.CONNECTS_TO) continue;
5196
+ if (edge.provenance !== import_types21.Provenance.OBSERVED) continue;
4846
5197
  const target = graph.getNodeAttributes(edge.target);
4847
- if (target.type !== import_types20.NodeType.DatabaseNode) continue;
5198
+ if (target.type !== import_types21.NodeType.DatabaseNode) continue;
4848
5199
  const observedHost = target.host?.trim();
4849
5200
  if (!observedHost) continue;
4850
5201
  if (observedHost === declaredHost) continue;
@@ -4866,10 +5217,10 @@ function detectCompatDivergences(graph, svcId, svc) {
4866
5217
  const deps = svc.dependencies ?? {};
4867
5218
  for (const edgeId of graph.outboundEdges(svcId)) {
4868
5219
  const edge = graph.getEdgeAttributes(edgeId);
4869
- if (edge.type !== import_types20.EdgeType.CONNECTS_TO) continue;
4870
- if (edge.provenance !== import_types20.Provenance.OBSERVED) continue;
5220
+ if (edge.type !== import_types21.EdgeType.CONNECTS_TO) continue;
5221
+ if (edge.provenance !== import_types21.Provenance.OBSERVED) continue;
4871
5222
  const target = graph.getNodeAttributes(edge.target);
4872
- if (target.type !== import_types20.NodeType.DatabaseNode) continue;
5223
+ if (target.type !== import_types21.NodeType.DatabaseNode) continue;
4873
5224
  for (const pair of compatPairs()) {
4874
5225
  if (pair.engine !== target.engine) continue;
4875
5226
  const declared = deps[pair.driver];
@@ -4930,7 +5281,7 @@ function computeDivergences(graph, opts = {}) {
4930
5281
  }
4931
5282
  graph.forEachNode((nodeId, attrs) => {
4932
5283
  const n = attrs;
4933
- if (n.type !== import_types20.NodeType.ServiceNode) return;
5284
+ if (n.type !== import_types21.NodeType.ServiceNode) return;
4934
5285
  const svc = n;
4935
5286
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
4936
5287
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
@@ -4963,7 +5314,7 @@ function computeDivergences(graph, opts = {}) {
4963
5314
  if (a.source !== b.source) return a.source.localeCompare(b.source);
4964
5315
  return a.target.localeCompare(b.target);
4965
5316
  });
4966
- return import_types20.DivergenceResultSchema.parse({
5317
+ return import_types21.DivergenceResultSchema.parse({
4967
5318
  divergences: filtered,
4968
5319
  totalAffected: filtered.length,
4969
5320
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -4972,9 +5323,9 @@ function computeDivergences(graph, opts = {}) {
4972
5323
 
4973
5324
  // src/persist.ts
4974
5325
  init_cjs_shims();
4975
- var import_node_fs18 = require("fs");
4976
- var import_node_path31 = __toESM(require("path"), 1);
4977
- var import_types21 = require("@neat.is/types");
5326
+ var import_node_fs19 = require("fs");
5327
+ var import_node_path33 = __toESM(require("path"), 1);
5328
+ var import_types22 = require("@neat.is/types");
4978
5329
  var SCHEMA_VERSION = 4;
4979
5330
  function migrateV1ToV2(payload) {
4980
5331
  const nodes = payload.graph.nodes;
@@ -4996,12 +5347,12 @@ function migrateV2ToV3(payload) {
4996
5347
  for (const edge of edges) {
4997
5348
  const attrs = edge.attributes;
4998
5349
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
4999
- attrs.provenance = import_types21.Provenance.OBSERVED;
5350
+ attrs.provenance = import_types22.Provenance.OBSERVED;
5000
5351
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
5001
5352
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
5002
5353
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
5003
5354
  if (type && source && target) {
5004
- const newId = (0, import_types21.observedEdgeId)(source, target, type);
5355
+ const newId = (0, import_types22.observedEdgeId)(source, target, type);
5005
5356
  attrs.id = newId;
5006
5357
  if (edge.key) edge.key = newId;
5007
5358
  }
@@ -5010,7 +5361,7 @@ function migrateV2ToV3(payload) {
5010
5361
  return { ...payload, schemaVersion: 3 };
5011
5362
  }
5012
5363
  async function ensureDir(filePath) {
5013
- await import_node_fs18.promises.mkdir(import_node_path31.default.dirname(filePath), { recursive: true });
5364
+ await import_node_fs19.promises.mkdir(import_node_path33.default.dirname(filePath), { recursive: true });
5014
5365
  }
5015
5366
  async function saveGraphToDisk(graph, outPath) {
5016
5367
  await ensureDir(outPath);
@@ -5020,13 +5371,13 @@ async function saveGraphToDisk(graph, outPath) {
5020
5371
  graph: graph.export()
5021
5372
  };
5022
5373
  const tmp = `${outPath}.tmp`;
5023
- await import_node_fs18.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
5024
- await import_node_fs18.promises.rename(tmp, outPath);
5374
+ await import_node_fs19.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
5375
+ await import_node_fs19.promises.rename(tmp, outPath);
5025
5376
  }
5026
5377
  async function loadGraphFromDisk(graph, outPath) {
5027
5378
  let raw;
5028
5379
  try {
5029
- raw = await import_node_fs18.promises.readFile(outPath, "utf8");
5380
+ raw = await import_node_fs19.promises.readFile(outPath, "utf8");
5030
5381
  } catch (err) {
5031
5382
  if (err.code === "ENOENT") return;
5032
5383
  throw err;
@@ -5085,8 +5436,8 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
5085
5436
 
5086
5437
  // src/gitignore.ts
5087
5438
  init_cjs_shims();
5088
- var import_node_fs19 = require("fs");
5089
- var import_node_path32 = __toESM(require("path"), 1);
5439
+ var import_node_fs20 = require("fs");
5440
+ var import_node_path34 = __toESM(require("path"), 1);
5090
5441
  var NEAT_OUT_LINE = "neat-out/";
5091
5442
  var NEAT_HEADER = "# NEAT \u2014 machine-local snapshots and events";
5092
5443
  function isNeatOutLine(line) {
@@ -5094,15 +5445,15 @@ function isNeatOutLine(line) {
5094
5445
  return trimmed === "neat-out/" || trimmed === "neat-out";
5095
5446
  }
5096
5447
  async function ensureNeatOutIgnored(projectDir) {
5097
- const file = import_node_path32.default.join(projectDir, ".gitignore");
5448
+ const file = import_node_path34.default.join(projectDir, ".gitignore");
5098
5449
  let existing = null;
5099
5450
  try {
5100
- existing = await import_node_fs19.promises.readFile(file, "utf8");
5451
+ existing = await import_node_fs20.promises.readFile(file, "utf8");
5101
5452
  } catch (err) {
5102
5453
  if (err.code !== "ENOENT") throw err;
5103
5454
  }
5104
5455
  if (existing === null) {
5105
- await import_node_fs19.promises.writeFile(file, `${NEAT_HEADER}
5456
+ await import_node_fs20.promises.writeFile(file, `${NEAT_HEADER}
5106
5457
  ${NEAT_OUT_LINE}
5107
5458
  `, "utf8");
5108
5459
  return { action: "created", file };
@@ -5115,13 +5466,13 @@ ${NEAT_OUT_LINE}
5115
5466
  ${NEAT_HEADER}
5116
5467
  ${NEAT_OUT_LINE}
5117
5468
  `;
5118
- await import_node_fs19.promises.writeFile(file, existing + appended, "utf8");
5469
+ await import_node_fs20.promises.writeFile(file, existing + appended, "utf8");
5119
5470
  return { action: "added", file };
5120
5471
  }
5121
5472
 
5122
5473
  // src/summary.ts
5123
5474
  init_cjs_shims();
5124
- var import_types22 = require("@neat.is/types");
5475
+ var import_types23 = require("@neat.is/types");
5125
5476
  function renderOtelEnvBlock() {
5126
5477
  return [
5127
5478
  "for prod OTel routing, set these in your deploy platform's env:",
@@ -5131,19 +5482,19 @@ function renderOtelEnvBlock() {
5131
5482
  }
5132
5483
  function findIncompatServices(nodes) {
5133
5484
  return nodes.filter(
5134
- (n) => n.type === import_types22.NodeType.ServiceNode && Array.isArray(n.incompatibilities) && (n.incompatibilities ?? []).length > 0
5485
+ (n) => n.type === import_types23.NodeType.ServiceNode && Array.isArray(n.incompatibilities) && (n.incompatibilities ?? []).length > 0
5135
5486
  );
5136
5487
  }
5137
5488
  function servicesWithoutObserved(nodes, edges) {
5138
5489
  const seen = /* @__PURE__ */ new Set();
5139
5490
  for (const e of edges) {
5140
- if (e.provenance === import_types22.Provenance.OBSERVED) {
5491
+ if (e.provenance === import_types23.Provenance.OBSERVED) {
5141
5492
  seen.add(e.source);
5142
5493
  seen.add(e.target);
5143
5494
  }
5144
5495
  }
5145
5496
  return nodes.filter(
5146
- (n) => n.type === import_types22.NodeType.ServiceNode && !seen.has(n.id)
5497
+ (n) => n.type === import_types23.NodeType.ServiceNode && !seen.has(n.id)
5147
5498
  );
5148
5499
  }
5149
5500
  function formatDivergence(d) {
@@ -5205,31 +5556,346 @@ function formatIncompat(inc) {
5205
5556
  const range = inc.declaredNodeEngine ? ` (engines.node="${inc.declaredNodeEngine}")` : "";
5206
5557
  return `${inc.package}@${inc.packageVersion ?? "?"} requires Node ${inc.requiredNodeVersion}${range} \u2014 ${inc.reason}`;
5207
5558
  }
5208
- if (inc.kind === "package-conflict") {
5209
- const found = inc.foundVersion ? `@${inc.foundVersion}` : " (missing)";
5210
- return `${inc.package}@${inc.packageVersion ?? "?"} requires ${inc.requires.name}>=${inc.requires.minVersion}; found ${inc.requires.name}${found} \u2014 ${inc.reason}`;
5559
+ if (inc.kind === "package-conflict") {
5560
+ const found = inc.foundVersion ? `@${inc.foundVersion}` : " (missing)";
5561
+ return `${inc.package}@${inc.packageVersion ?? "?"} requires ${inc.requires.name}>=${inc.requires.minVersion}; found ${inc.requires.name}${found} \u2014 ${inc.reason}`;
5562
+ }
5563
+ if (inc.kind === "deprecated-api") {
5564
+ return `${inc.package}@${inc.packageVersion ?? "?"} is deprecated \u2014 ${inc.reason}`;
5565
+ }
5566
+ return `${inc.driver}@${inc.driverVersion} vs ${inc.engine} ${inc.engineVersion} \u2014 ${inc.reason}`;
5567
+ }
5568
+
5569
+ // src/watch.ts
5570
+ init_cjs_shims();
5571
+ var import_node_fs26 = __toESM(require("fs"), 1);
5572
+ var import_node_path42 = __toESM(require("path"), 1);
5573
+ var import_chokidar = __toESM(require("chokidar"), 1);
5574
+
5575
+ // src/api.ts
5576
+ init_cjs_shims();
5577
+ var import_fastify = __toESM(require("fastify"), 1);
5578
+ var import_cors = __toESM(require("@fastify/cors"), 1);
5579
+ var import_types25 = require("@neat.is/types");
5580
+
5581
+ // src/extend/index.ts
5582
+ init_cjs_shims();
5583
+ var import_node_fs22 = require("fs");
5584
+ var import_node_path36 = __toESM(require("path"), 1);
5585
+ var import_node_os2 = __toESM(require("os"), 1);
5586
+ var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
5587
+
5588
+ // src/installers/package-manager.ts
5589
+ init_cjs_shims();
5590
+ var import_node_fs21 = require("fs");
5591
+ var import_node_path35 = __toESM(require("path"), 1);
5592
+ var import_node_child_process = require("child_process");
5593
+ var LOCKFILE_PRIORITY = [
5594
+ { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
5595
+ { lockfile: "pnpm-lock.yaml", pm: "pnpm", args: ["install", "--no-summary"] },
5596
+ { lockfile: "yarn.lock", pm: "yarn", args: ["install", "--silent"] },
5597
+ {
5598
+ lockfile: "package-lock.json",
5599
+ pm: "npm",
5600
+ args: ["install", "--no-audit", "--no-fund", "--prefer-offline"]
5601
+ }
5602
+ ];
5603
+ var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
5604
+ async function exists2(p) {
5605
+ try {
5606
+ await import_node_fs21.promises.access(p);
5607
+ return true;
5608
+ } catch {
5609
+ return false;
5610
+ }
5611
+ }
5612
+ async function detectPackageManager(serviceDir) {
5613
+ let dir = import_node_path35.default.resolve(serviceDir);
5614
+ const stops = /* @__PURE__ */ new Set();
5615
+ for (let i = 0; i < 64; i++) {
5616
+ if (stops.has(dir)) break;
5617
+ stops.add(dir);
5618
+ for (const candidate of LOCKFILE_PRIORITY) {
5619
+ const lockPath = import_node_path35.default.join(dir, candidate.lockfile);
5620
+ if (await exists2(lockPath)) {
5621
+ return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
5622
+ }
5623
+ }
5624
+ const parent = import_node_path35.default.dirname(dir);
5625
+ if (parent === dir) break;
5626
+ dir = parent;
5627
+ }
5628
+ return { pm: "npm", cwd: import_node_path35.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
5629
+ }
5630
+ async function runPackageManagerInstall(cmd) {
5631
+ return new Promise((resolve) => {
5632
+ const child = (0, import_node_child_process.spawn)(cmd.pm, cmd.args, {
5633
+ cwd: cmd.cwd,
5634
+ // Inherit PATH + HOME so the user's installed managers resolve.
5635
+ env: process.env,
5636
+ // `false` keeps the parent in control of cleanup if the orchestrator
5637
+ // exits before install finishes. Cross-platform-safe.
5638
+ shell: false,
5639
+ stdio: ["ignore", "ignore", "pipe"]
5640
+ });
5641
+ let stderr = "";
5642
+ child.stderr?.on("data", (chunk) => {
5643
+ stderr += chunk.toString("utf8");
5644
+ });
5645
+ child.on("error", (err) => {
5646
+ resolve({
5647
+ pm: cmd.pm,
5648
+ cwd: cmd.cwd,
5649
+ args: cmd.args,
5650
+ exitCode: 127,
5651
+ stderr: stderr + `
5652
+ ${err.message}`
5653
+ });
5654
+ });
5655
+ child.on("close", (code) => {
5656
+ resolve({
5657
+ pm: cmd.pm,
5658
+ cwd: cmd.cwd,
5659
+ args: cmd.args,
5660
+ exitCode: code ?? 1,
5661
+ stderr: stderr.trim()
5662
+ });
5663
+ });
5664
+ });
5665
+ }
5666
+
5667
+ // src/extend/index.ts
5668
+ async function fileExists2(p) {
5669
+ try {
5670
+ await import_node_fs22.promises.access(p);
5671
+ return true;
5672
+ } catch {
5673
+ return false;
5674
+ }
5675
+ }
5676
+ async function readPackageJson(scanPath) {
5677
+ const pkgPath = import_node_path36.default.join(scanPath, "package.json");
5678
+ const raw = await import_node_fs22.promises.readFile(pkgPath, "utf8");
5679
+ return JSON.parse(raw);
5680
+ }
5681
+ async function findHookFiles(scanPath) {
5682
+ const entries = await import_node_fs22.promises.readdir(scanPath);
5683
+ return entries.filter(
5684
+ (e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
5685
+ ).sort();
5686
+ }
5687
+ function extendLogPath() {
5688
+ return process.env.NEAT_EXTEND_LOG ?? import_node_path36.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
5689
+ }
5690
+ async function appendExtendLog(entry2) {
5691
+ const logPath = extendLogPath();
5692
+ await import_node_fs22.promises.mkdir(import_node_path36.default.dirname(logPath), { recursive: true });
5693
+ await import_node_fs22.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
5694
+ }
5695
+ function splicedContent(fileContent, snippet2) {
5696
+ if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
5697
+ return fileContent.replace("__INSTRUMENTATION_BLOCK__", `${snippet2}
5698
+ __INSTRUMENTATION_BLOCK__`);
5699
+ }
5700
+ const lines = fileContent.split("\n");
5701
+ let lastPushIdx = -1;
5702
+ for (let i = 0; i < lines.length; i++) {
5703
+ if (lines[i].includes("instrumentations.push(")) lastPushIdx = i;
5704
+ }
5705
+ if (lastPushIdx >= 0) {
5706
+ lines.splice(lastPushIdx + 1, 0, snippet2);
5707
+ return lines.join("\n");
5708
+ }
5709
+ for (let i = 0; i < lines.length; i++) {
5710
+ if (lines[i].includes("new NodeSDK(")) {
5711
+ lines.splice(i, 0, snippet2);
5712
+ return lines.join("\n");
5713
+ }
5714
+ }
5715
+ return null;
5716
+ }
5717
+ async function listUninstrumented(ctx) {
5718
+ const pkg = await readPackageJson(ctx.scanPath);
5719
+ const allDeps2 = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
5720
+ const results = [];
5721
+ for (const [library, installedVersion] of Object.entries(allDeps2)) {
5722
+ const entry2 = (0, import_instrumentation_registry.resolve)(library, installedVersion);
5723
+ if (!entry2) continue;
5724
+ if (entry2.coverage === "bundled" || entry2.coverage === "http-only") continue;
5725
+ results.push({
5726
+ library,
5727
+ coverage: entry2.coverage,
5728
+ installedVersion,
5729
+ instrumentation_package: entry2.instrumentation_package,
5730
+ package_version: entry2.package_version,
5731
+ registration: entry2.registration,
5732
+ notes: entry2.notes
5733
+ });
5734
+ }
5735
+ return results;
5736
+ }
5737
+ function lookupInstrumentation(library, installedVersion) {
5738
+ const entry2 = (0, import_instrumentation_registry.resolve)(library, installedVersion);
5739
+ if (!entry2) return null;
5740
+ return {
5741
+ library: entry2.library,
5742
+ coverage: entry2.coverage,
5743
+ instrumentation_package: entry2.instrumentation_package,
5744
+ package_version: entry2.package_version,
5745
+ registration: entry2.registration,
5746
+ notes: entry2.notes
5747
+ };
5748
+ }
5749
+ async function describeProjectInstrumentation(ctx) {
5750
+ const hookFiles = await findHookFiles(ctx.scanPath);
5751
+ const envNeat = await fileExists2(import_node_path36.default.join(ctx.scanPath, ".env.neat"));
5752
+ const registryInstrPackages = new Set(
5753
+ (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
5754
+ );
5755
+ const pkg = await readPackageJson(ctx.scanPath);
5756
+ const allDeps2 = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
5757
+ const installedDeps = {};
5758
+ for (const [key, version] of Object.entries(allDeps2)) {
5759
+ if (key.startsWith("@opentelemetry/") || registryInstrPackages.has(key)) {
5760
+ installedDeps[key] = version;
5761
+ }
5762
+ }
5763
+ return { hookFiles, envNeat, installedDeps };
5764
+ }
5765
+ async function applyExtension(ctx, args, options) {
5766
+ const hookFiles = await findHookFiles(ctx.scanPath);
5767
+ if (hookFiles.length === 0) {
5768
+ throw new Error(
5769
+ `No instrumentation hook files found in ${ctx.scanPath}. Run \`neat init\` first.`
5770
+ );
5771
+ }
5772
+ for (const file of hookFiles) {
5773
+ const content = await import_node_fs22.promises.readFile(import_node_path36.default.join(ctx.scanPath, file), "utf8");
5774
+ if (content.includes(args.registration_snippet)) {
5775
+ return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
5776
+ }
5777
+ }
5778
+ const primaryFile = hookFiles[0];
5779
+ const primaryPath = import_node_path36.default.join(ctx.scanPath, primaryFile);
5780
+ const filesTouched = [];
5781
+ const depsAdded = [];
5782
+ const pkgPath = import_node_path36.default.join(ctx.scanPath, "package.json");
5783
+ const pkg = await readPackageJson(ctx.scanPath);
5784
+ if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
5785
+ pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
5786
+ await import_node_fs22.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
5787
+ filesTouched.push("package.json");
5788
+ depsAdded.push(`${args.instrumentation_package}@${args.version}`);
5789
+ }
5790
+ const hookContent = await import_node_fs22.promises.readFile(primaryPath, "utf8");
5791
+ const patched = splicedContent(hookContent, args.registration_snippet);
5792
+ if (!patched) {
5793
+ throw new Error(
5794
+ `Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
5795
+ );
5796
+ }
5797
+ await import_node_fs22.promises.writeFile(primaryPath, patched, "utf8");
5798
+ filesTouched.push(primaryFile);
5799
+ const cmd = await detectPackageManager(ctx.scanPath);
5800
+ const installer = options?.runInstall ?? runPackageManagerInstall;
5801
+ const install = await installer(cmd);
5802
+ const installOutput = install.exitCode === 0 ? `${cmd.pm} install succeeded` : install.stderr || `${cmd.pm} install failed (exit ${install.exitCode})`;
5803
+ await appendExtendLog({
5804
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5805
+ project: ctx.project,
5806
+ library: args.library,
5807
+ instrumentation_package: args.instrumentation_package,
5808
+ version: args.version,
5809
+ registration_snippet: args.registration_snippet,
5810
+ filesTouched,
5811
+ depsAdded,
5812
+ installOutput
5813
+ });
5814
+ return { library: args.library, filesTouched, depsAdded, installOutput, alreadyApplied: false };
5815
+ }
5816
+ async function dryRunExtension(ctx, args) {
5817
+ const hookFiles = await findHookFiles(ctx.scanPath);
5818
+ if (hookFiles.length === 0) {
5819
+ return {
5820
+ library: args.library,
5821
+ filesTouched: [],
5822
+ depsToAdd: [],
5823
+ packageJsonPatch: {},
5824
+ templatePatch: "No hook files found. Run 'neat init' first."
5825
+ };
5211
5826
  }
5212
- if (inc.kind === "deprecated-api") {
5213
- return `${inc.package}@${inc.packageVersion ?? "?"} is deprecated \u2014 ${inc.reason}`;
5827
+ for (const file of hookFiles) {
5828
+ const content = await import_node_fs22.promises.readFile(import_node_path36.default.join(ctx.scanPath, file), "utf8");
5829
+ if (content.includes(args.registration_snippet)) {
5830
+ return {
5831
+ library: args.library,
5832
+ filesTouched: [],
5833
+ depsToAdd: [],
5834
+ packageJsonPatch: {},
5835
+ templatePatch: "Already applied \u2014 no changes would be made."
5836
+ };
5837
+ }
5214
5838
  }
5215
- return `${inc.driver}@${inc.driverVersion} vs ${inc.engine} ${inc.engineVersion} \u2014 ${inc.reason}`;
5839
+ const primaryFile = hookFiles[0];
5840
+ const filesTouched = [];
5841
+ const depsToAdd = [];
5842
+ let packageJsonPatch = {};
5843
+ let templatePatch = "";
5844
+ const pkg = await readPackageJson(ctx.scanPath);
5845
+ if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
5846
+ packageJsonPatch = { dependencies: { [args.instrumentation_package]: args.version } };
5847
+ depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
5848
+ filesTouched.push("package.json");
5849
+ }
5850
+ const hookContent = await import_node_fs22.promises.readFile(import_node_path36.default.join(ctx.scanPath, primaryFile), "utf8");
5851
+ const patched = splicedContent(hookContent, args.registration_snippet);
5852
+ if (patched) {
5853
+ filesTouched.push(primaryFile);
5854
+ templatePatch = `+ ${args.registration_snippet}`;
5855
+ } else {
5856
+ templatePatch = "Could not find insertion point in hook file.";
5857
+ }
5858
+ return { library: args.library, filesTouched, depsToAdd, packageJsonPatch, templatePatch };
5859
+ }
5860
+ async function rollbackExtension(ctx, args) {
5861
+ const logPath = extendLogPath();
5862
+ if (!await fileExists2(logPath)) {
5863
+ return { undone: false, message: "no apply found for library" };
5864
+ }
5865
+ const raw = await import_node_fs22.promises.readFile(logPath, "utf8");
5866
+ const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
5867
+ const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
5868
+ if (!match) {
5869
+ return { undone: false, message: "no apply found for library" };
5870
+ }
5871
+ const pkgPath = import_node_path36.default.join(ctx.scanPath, "package.json");
5872
+ if (await fileExists2(pkgPath)) {
5873
+ const pkg = await readPackageJson(ctx.scanPath);
5874
+ if (pkg.dependencies?.[match.instrumentation_package]) {
5875
+ const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
5876
+ pkg.dependencies = rest;
5877
+ await import_node_fs22.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
5878
+ }
5879
+ }
5880
+ const hookFiles = await findHookFiles(ctx.scanPath);
5881
+ for (const file of hookFiles) {
5882
+ const filePath = import_node_path36.default.join(ctx.scanPath, file);
5883
+ const content = await import_node_fs22.promises.readFile(filePath, "utf8");
5884
+ if (content.includes(match.registration_snippet)) {
5885
+ const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
5886
+ await import_node_fs22.promises.writeFile(filePath, filtered, "utf8");
5887
+ break;
5888
+ }
5889
+ }
5890
+ return {
5891
+ undone: true,
5892
+ message: `rolled back ${match.library} (${match.instrumentation_package})`
5893
+ };
5216
5894
  }
5217
5895
 
5218
- // src/watch.ts
5219
- init_cjs_shims();
5220
- var import_node_fs23 = __toESM(require("fs"), 1);
5221
- var import_node_path38 = __toESM(require("path"), 1);
5222
- var import_chokidar = __toESM(require("chokidar"), 1);
5223
-
5224
- // src/api.ts
5225
- init_cjs_shims();
5226
- var import_fastify = __toESM(require("fastify"), 1);
5227
- var import_cors = __toESM(require("@fastify/cors"), 1);
5228
- var import_types24 = require("@neat.is/types");
5229
-
5230
5896
  // src/diff.ts
5231
5897
  init_cjs_shims();
5232
- var import_node_fs20 = require("fs");
5898
+ var import_node_fs23 = require("fs");
5233
5899
  async function loadSnapshotForDiff(target) {
5234
5900
  if (/^https?:\/\//i.test(target)) {
5235
5901
  const res = await fetch(target);
@@ -5238,7 +5904,7 @@ async function loadSnapshotForDiff(target) {
5238
5904
  }
5239
5905
  return await res.json();
5240
5906
  }
5241
- const raw = await import_node_fs20.promises.readFile(target, "utf8");
5907
+ const raw = await import_node_fs23.promises.readFile(target, "utf8");
5242
5908
  return JSON.parse(raw);
5243
5909
  }
5244
5910
  function indexEntries(entries) {
@@ -5306,23 +5972,23 @@ function canonicalJson(value) {
5306
5972
 
5307
5973
  // src/projects.ts
5308
5974
  init_cjs_shims();
5309
- var import_node_path33 = __toESM(require("path"), 1);
5975
+ var import_node_path37 = __toESM(require("path"), 1);
5310
5976
  function pathsForProject(project, baseDir) {
5311
5977
  if (project === DEFAULT_PROJECT) {
5312
5978
  return {
5313
- snapshotPath: import_node_path33.default.join(baseDir, "graph.json"),
5314
- errorsPath: import_node_path33.default.join(baseDir, "errors.ndjson"),
5315
- staleEventsPath: import_node_path33.default.join(baseDir, "stale-events.ndjson"),
5316
- embeddingsCachePath: import_node_path33.default.join(baseDir, "embeddings.json"),
5317
- policyViolationsPath: import_node_path33.default.join(baseDir, "policy-violations.ndjson")
5979
+ snapshotPath: import_node_path37.default.join(baseDir, "graph.json"),
5980
+ errorsPath: import_node_path37.default.join(baseDir, "errors.ndjson"),
5981
+ staleEventsPath: import_node_path37.default.join(baseDir, "stale-events.ndjson"),
5982
+ embeddingsCachePath: import_node_path37.default.join(baseDir, "embeddings.json"),
5983
+ policyViolationsPath: import_node_path37.default.join(baseDir, "policy-violations.ndjson")
5318
5984
  };
5319
5985
  }
5320
5986
  return {
5321
- snapshotPath: import_node_path33.default.join(baseDir, `${project}.json`),
5322
- errorsPath: import_node_path33.default.join(baseDir, `errors.${project}.ndjson`),
5323
- staleEventsPath: import_node_path33.default.join(baseDir, `stale-events.${project}.ndjson`),
5324
- embeddingsCachePath: import_node_path33.default.join(baseDir, `embeddings.${project}.json`),
5325
- policyViolationsPath: import_node_path33.default.join(baseDir, `policy-violations.${project}.ndjson`)
5987
+ snapshotPath: import_node_path37.default.join(baseDir, `${project}.json`),
5988
+ errorsPath: import_node_path37.default.join(baseDir, `errors.${project}.ndjson`),
5989
+ staleEventsPath: import_node_path37.default.join(baseDir, `stale-events.${project}.ndjson`),
5990
+ embeddingsCachePath: import_node_path37.default.join(baseDir, `embeddings.${project}.json`),
5991
+ policyViolationsPath: import_node_path37.default.join(baseDir, `policy-violations.${project}.ndjson`)
5326
5992
  };
5327
5993
  }
5328
5994
  var Projects = class {
@@ -5358,25 +6024,25 @@ var Projects = class {
5358
6024
 
5359
6025
  // src/registry.ts
5360
6026
  init_cjs_shims();
5361
- var import_node_fs21 = require("fs");
5362
- var import_node_os2 = __toESM(require("os"), 1);
5363
- var import_node_path34 = __toESM(require("path"), 1);
5364
- var import_types23 = require("@neat.is/types");
6027
+ var import_node_fs24 = require("fs");
6028
+ var import_node_os3 = __toESM(require("os"), 1);
6029
+ var import_node_path38 = __toESM(require("path"), 1);
6030
+ var import_types24 = require("@neat.is/types");
5365
6031
  var LOCK_TIMEOUT_MS = 5e3;
5366
6032
  var LOCK_RETRY_MS = 50;
5367
6033
  function neatHome() {
5368
6034
  const override = process.env.NEAT_HOME;
5369
- if (override && override.length > 0) return import_node_path34.default.resolve(override);
5370
- return import_node_path34.default.join(import_node_os2.default.homedir(), ".neat");
6035
+ if (override && override.length > 0) return import_node_path38.default.resolve(override);
6036
+ return import_node_path38.default.join(import_node_os3.default.homedir(), ".neat");
5371
6037
  }
5372
6038
  function registryPath() {
5373
- return import_node_path34.default.join(neatHome(), "projects.json");
6039
+ return import_node_path38.default.join(neatHome(), "projects.json");
5374
6040
  }
5375
6041
  function registryLockPath() {
5376
- return import_node_path34.default.join(neatHome(), "projects.json.lock");
6042
+ return import_node_path38.default.join(neatHome(), "projects.json.lock");
5377
6043
  }
5378
6044
  function daemonPidPath() {
5379
- return import_node_path34.default.join(neatHome(), "neatd.pid");
6045
+ return import_node_path38.default.join(neatHome(), "neatd.pid");
5380
6046
  }
5381
6047
  function isPidAliveDefault(pid) {
5382
6048
  try {
@@ -5388,7 +6054,7 @@ function isPidAliveDefault(pid) {
5388
6054
  }
5389
6055
  async function readPidFile(file) {
5390
6056
  try {
5391
- const raw = await import_node_fs21.promises.readFile(file, "utf8");
6057
+ const raw = await import_node_fs24.promises.readFile(file, "utf8");
5392
6058
  const pid = Number.parseInt(raw.trim(), 10);
5393
6059
  return Number.isInteger(pid) && pid > 0 ? pid : void 0;
5394
6060
  } catch {
@@ -5436,32 +6102,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
5436
6102
  }
5437
6103
  }
5438
6104
  async function normalizeProjectPath(input) {
5439
- const resolved = import_node_path34.default.resolve(input);
6105
+ const resolved = import_node_path38.default.resolve(input);
5440
6106
  try {
5441
- return await import_node_fs21.promises.realpath(resolved);
6107
+ return await import_node_fs24.promises.realpath(resolved);
5442
6108
  } catch {
5443
6109
  return resolved;
5444
6110
  }
5445
6111
  }
5446
6112
  async function writeAtomically(target, contents) {
5447
- await import_node_fs21.promises.mkdir(import_node_path34.default.dirname(target), { recursive: true });
6113
+ await import_node_fs24.promises.mkdir(import_node_path38.default.dirname(target), { recursive: true });
5448
6114
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
5449
- const fd = await import_node_fs21.promises.open(tmp, "w");
6115
+ const fd = await import_node_fs24.promises.open(tmp, "w");
5450
6116
  try {
5451
6117
  await fd.writeFile(contents, "utf8");
5452
6118
  await fd.sync();
5453
6119
  } finally {
5454
6120
  await fd.close();
5455
6121
  }
5456
- await import_node_fs21.promises.rename(tmp, target);
6122
+ await import_node_fs24.promises.rename(tmp, target);
5457
6123
  }
5458
6124
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
5459
6125
  const deadline = Date.now() + timeoutMs;
5460
- await import_node_fs21.promises.mkdir(import_node_path34.default.dirname(lockPath), { recursive: true });
6126
+ await import_node_fs24.promises.mkdir(import_node_path38.default.dirname(lockPath), { recursive: true });
5461
6127
  let probedHolder = false;
5462
6128
  while (true) {
5463
6129
  try {
5464
- const fd = await import_node_fs21.promises.open(lockPath, "wx");
6130
+ const fd = await import_node_fs24.promises.open(lockPath, "wx");
5465
6131
  try {
5466
6132
  await fd.writeFile(`${process.pid}
5467
6133
  `, "utf8");
@@ -5486,7 +6152,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
5486
6152
  }
5487
6153
  }
5488
6154
  async function releaseLock(lockPath) {
5489
- await import_node_fs21.promises.unlink(lockPath).catch(() => {
6155
+ await import_node_fs24.promises.unlink(lockPath).catch(() => {
5490
6156
  });
5491
6157
  }
5492
6158
  async function withLock(fn) {
@@ -5502,7 +6168,7 @@ async function readRegistry() {
5502
6168
  const file = registryPath();
5503
6169
  let raw;
5504
6170
  try {
5505
- raw = await import_node_fs21.promises.readFile(file, "utf8");
6171
+ raw = await import_node_fs24.promises.readFile(file, "utf8");
5506
6172
  } catch (err) {
5507
6173
  if (err.code === "ENOENT") {
5508
6174
  return { version: 1, projects: [] };
@@ -5510,10 +6176,10 @@ async function readRegistry() {
5510
6176
  throw err;
5511
6177
  }
5512
6178
  const parsed = JSON.parse(raw);
5513
- return import_types23.RegistryFileSchema.parse(parsed);
6179
+ return import_types24.RegistryFileSchema.parse(parsed);
5514
6180
  }
5515
6181
  async function writeRegistry(reg) {
5516
- const validated = import_types23.RegistryFileSchema.parse(reg);
6182
+ const validated = import_types24.RegistryFileSchema.parse(reg);
5517
6183
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
5518
6184
  }
5519
6185
  var ProjectNameCollisionError = class extends Error {
@@ -5776,11 +6442,11 @@ function registerRoutes(scope, ctx) {
5776
6442
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
5777
6443
  const parsed = [];
5778
6444
  for (const c of candidates) {
5779
- const r = import_types24.DivergenceTypeSchema.safeParse(c);
6445
+ const r = import_types25.DivergenceTypeSchema.safeParse(c);
5780
6446
  if (!r.success) {
5781
6447
  return reply.code(400).send({
5782
6448
  error: `unknown divergence type "${c}"`,
5783
- allowed: import_types24.DivergenceTypeSchema.options
6449
+ allowed: import_types25.DivergenceTypeSchema.options
5784
6450
  });
5785
6451
  }
5786
6452
  parsed.push(r.data);
@@ -5993,7 +6659,7 @@ function registerRoutes(scope, ctx) {
5993
6659
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
5994
6660
  let violations = await log.readAll();
5995
6661
  if (req.query.severity) {
5996
- const sev = import_types24.PolicySeveritySchema.safeParse(req.query.severity);
6662
+ const sev = import_types25.PolicySeveritySchema.safeParse(req.query.severity);
5997
6663
  if (!sev.success) {
5998
6664
  return reply.code(400).send({
5999
6665
  error: "invalid severity",
@@ -6010,7 +6676,7 @@ function registerRoutes(scope, ctx) {
6010
6676
  scope.post("/policies/check", async (req, reply) => {
6011
6677
  const proj = resolveProject(registry, req, reply, ctx.bootstrap);
6012
6678
  if (!proj) return;
6013
- const parsed = import_types24.PoliciesCheckBodySchema.safeParse(req.body ?? {});
6679
+ const parsed = import_types25.PoliciesCheckBodySchema.safeParse(req.body ?? {});
6014
6680
  if (!parsed.success) {
6015
6681
  return reply.code(400).send({
6016
6682
  error: "invalid /policies/check body",
@@ -6043,6 +6709,105 @@ function registerRoutes(scope, ctx) {
6043
6709
  violations
6044
6710
  };
6045
6711
  });
6712
+ scope.get("/extend/list-uninstrumented", async (req, reply) => {
6713
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap);
6714
+ if (!proj) return;
6715
+ if (!proj.scanPath) {
6716
+ return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
6717
+ }
6718
+ try {
6719
+ const results = await listUninstrumented({ project: proj.name, scanPath: proj.scanPath });
6720
+ return { libraries: results };
6721
+ } catch (err) {
6722
+ return reply.code(500).send({ error: err.message });
6723
+ }
6724
+ });
6725
+ scope.get("/extend/lookup", async (req, reply) => {
6726
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap);
6727
+ if (!proj) return;
6728
+ const { library, version } = req.query;
6729
+ if (!library) {
6730
+ return reply.code(400).send({ error: "query parameter `library` is required" });
6731
+ }
6732
+ const result = lookupInstrumentation(library, version);
6733
+ if (!result) {
6734
+ return reply.code(404).send({ error: "library not found in registry", library });
6735
+ }
6736
+ return result;
6737
+ });
6738
+ scope.get("/extend/describe", async (req, reply) => {
6739
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap);
6740
+ if (!proj) return;
6741
+ if (!proj.scanPath) {
6742
+ return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
6743
+ }
6744
+ try {
6745
+ const state = await describeProjectInstrumentation({ project: proj.name, scanPath: proj.scanPath });
6746
+ return state;
6747
+ } catch (err) {
6748
+ return reply.code(500).send({ error: err.message });
6749
+ }
6750
+ });
6751
+ scope.post("/extend/apply", async (req, reply) => {
6752
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap);
6753
+ if (!proj) return;
6754
+ if (!proj.scanPath) {
6755
+ return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
6756
+ }
6757
+ const { library, instrumentation_package, version, registration_snippet } = req.body ?? {};
6758
+ if (!library || !instrumentation_package || !version || !registration_snippet) {
6759
+ return reply.code(400).send({ error: "body must include library, instrumentation_package, version, registration_snippet" });
6760
+ }
6761
+ try {
6762
+ const result = await applyExtension(
6763
+ { project: proj.name, scanPath: proj.scanPath },
6764
+ { library, instrumentation_package, version, registration_snippet }
6765
+ );
6766
+ return result;
6767
+ } catch (err) {
6768
+ return reply.code(500).send({ error: err.message });
6769
+ }
6770
+ });
6771
+ scope.post("/extend/dry-run", async (req, reply) => {
6772
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap);
6773
+ if (!proj) return;
6774
+ if (!proj.scanPath) {
6775
+ return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
6776
+ }
6777
+ const { library, instrumentation_package, version, registration_snippet } = req.body ?? {};
6778
+ if (!library || !instrumentation_package || !version || !registration_snippet) {
6779
+ return reply.code(400).send({ error: "body must include library, instrumentation_package, version, registration_snippet" });
6780
+ }
6781
+ try {
6782
+ const result = await dryRunExtension(
6783
+ { project: proj.name, scanPath: proj.scanPath },
6784
+ { library, instrumentation_package, version, registration_snippet }
6785
+ );
6786
+ return result;
6787
+ } catch (err) {
6788
+ return reply.code(500).send({ error: err.message });
6789
+ }
6790
+ });
6791
+ scope.post("/extend/rollback", async (req, reply) => {
6792
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap);
6793
+ if (!proj) return;
6794
+ if (!proj.scanPath) {
6795
+ return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
6796
+ }
6797
+ const { library } = req.body ?? {};
6798
+ if (!library) {
6799
+ return reply.code(400).send({ error: "body must include library" });
6800
+ }
6801
+ try {
6802
+ const result = await rollbackExtension(
6803
+ { project: proj.name, scanPath: proj.scanPath },
6804
+ { library }
6805
+ );
6806
+ return result;
6807
+ } catch (err) {
6808
+ return reply.code(500).send({ error: err.message });
6809
+ }
6810
+ });
6046
6811
  }
6047
6812
  async function buildApi(opts) {
6048
6813
  const app = (0, import_fastify.default)({ logger: false });
@@ -6154,8 +6919,8 @@ init_otel_grpc();
6154
6919
 
6155
6920
  // src/search.ts
6156
6921
  init_cjs_shims();
6157
- var import_node_fs22 = require("fs");
6158
- var import_node_path37 = __toESM(require("path"), 1);
6922
+ var import_node_fs25 = require("fs");
6923
+ var import_node_path41 = __toESM(require("path"), 1);
6159
6924
  var import_node_crypto3 = require("crypto");
6160
6925
  var DEFAULT_LIMIT = 10;
6161
6926
  var NOMIC_DIM = 768;
@@ -6285,7 +7050,7 @@ async function pickEmbedder() {
6285
7050
  }
6286
7051
  async function readCache(cachePath) {
6287
7052
  try {
6288
- const raw = await import_node_fs22.promises.readFile(cachePath, "utf8");
7053
+ const raw = await import_node_fs25.promises.readFile(cachePath, "utf8");
6289
7054
  const parsed = JSON.parse(raw);
6290
7055
  if (parsed.version !== 1) return null;
6291
7056
  return parsed;
@@ -6294,8 +7059,8 @@ async function readCache(cachePath) {
6294
7059
  }
6295
7060
  }
6296
7061
  async function writeCache(cachePath, cache) {
6297
- await import_node_fs22.promises.mkdir(import_node_path37.default.dirname(cachePath), { recursive: true });
6298
- await import_node_fs22.promises.writeFile(cachePath, JSON.stringify(cache));
7062
+ await import_node_fs25.promises.mkdir(import_node_path41.default.dirname(cachePath), { recursive: true });
7063
+ await import_node_fs25.promises.writeFile(cachePath, JSON.stringify(cache));
6299
7064
  }
6300
7065
  var VectorIndex = class {
6301
7066
  constructor(embedder, cachePath) {
@@ -6443,6 +7208,7 @@ async function buildSearchIndex(graph, options = {}) {
6443
7208
  var ALL_PHASES = [
6444
7209
  "services",
6445
7210
  "aliases",
7211
+ "imports",
6446
7212
  "databases",
6447
7213
  "configs",
6448
7214
  "calls",
@@ -6450,8 +7216,8 @@ var ALL_PHASES = [
6450
7216
  ];
6451
7217
  function classifyChange(relPath) {
6452
7218
  const phases = /* @__PURE__ */ new Set();
6453
- const base = import_node_path38.default.basename(relPath).toLowerCase();
6454
- const segments = relPath.split(import_node_path38.default.sep).map((s) => s.toLowerCase());
7219
+ const base = import_node_path42.default.basename(relPath).toLowerCase();
7220
+ const segments = relPath.split(import_node_path42.default.sep).map((s) => s.toLowerCase());
6455
7221
  if (base === "package.json" || base === "requirements.txt" || base === "pyproject.toml" || base === "setup.py") {
6456
7222
  phases.add("services");
6457
7223
  phases.add("aliases");
@@ -6466,6 +7232,7 @@ function classifyChange(relPath) {
6466
7232
  phases.add("aliases");
6467
7233
  }
6468
7234
  if (/\.(?:js|jsx|mjs|cjs|ts|tsx|py)$/.test(base)) {
7235
+ phases.add("imports");
6469
7236
  phases.add("calls");
6470
7237
  }
6471
7238
  if (/\.ya?ml$/.test(base) && !/^docker-compose.*\.ya?ml$/.test(base)) {
@@ -6487,6 +7254,11 @@ async function runExtractPhases(graph, scanPath, phases, project = DEFAULT_PROJE
6487
7254
  if (phases.has("aliases")) {
6488
7255
  await addServiceAliases(graph, scanPath, services);
6489
7256
  }
7257
+ if (phases.has("imports")) {
7258
+ const r = await addImports(graph, services);
7259
+ nodesAdded += r.nodesAdded;
7260
+ edgesAdded += r.edgesAdded;
7261
+ }
6490
7262
  if (phases.has("databases")) {
6491
7263
  const r = await addDatabasesAndCompat(graph, services, scanPath);
6492
7264
  nodesAdded += r.nodesAdded;
@@ -6559,16 +7331,16 @@ function countWatchableDirs(scanPath, limit) {
6559
7331
  if (count >= limit) return;
6560
7332
  let entries;
6561
7333
  try {
6562
- entries = import_node_fs23.default.readdirSync(dir, { withFileTypes: true });
7334
+ entries = import_node_fs26.default.readdirSync(dir, { withFileTypes: true });
6563
7335
  } catch {
6564
7336
  return;
6565
7337
  }
6566
7338
  for (const e of entries) {
6567
7339
  if (count >= limit) return;
6568
7340
  if (!e.isDirectory()) continue;
6569
- if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path38.default.join(dir, e.name) + import_node_path38.default.sep))) continue;
7341
+ if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path42.default.join(dir, e.name) + import_node_path42.default.sep))) continue;
6570
7342
  count++;
6571
- if (depth < 2) visit(import_node_path38.default.join(dir, e.name), depth + 1);
7343
+ if (depth < 2) visit(import_node_path42.default.join(dir, e.name), depth + 1);
6572
7344
  }
6573
7345
  };
6574
7346
  visit(scanPath, 0);
@@ -6586,8 +7358,8 @@ async function startWatch(graph, opts) {
6586
7358
  const projectName = opts.project ?? DEFAULT_PROJECT;
6587
7359
  await loadGraphFromDisk(graph, opts.outPath);
6588
7360
  const detachEventBus = attachGraphToEventBus(graph, { project: projectName });
6589
- const policyFilePath = import_node_path38.default.join(opts.scanPath, "policy.json");
6590
- const policyViolationsPath = import_node_path38.default.join(import_node_path38.default.dirname(opts.outPath), "policy-violations.ndjson");
7361
+ const policyFilePath = import_node_path42.default.join(opts.scanPath, "policy.json");
7362
+ const policyViolationsPath = import_node_path42.default.join(import_node_path42.default.dirname(opts.outPath), "policy-violations.ndjson");
6591
7363
  let policies = [];
6592
7364
  try {
6593
7365
  policies = await loadPolicyFile(policyFilePath);
@@ -6638,7 +7410,7 @@ async function startWatch(graph, opts) {
6638
7410
  assertBindAuthority(host, auth.authToken);
6639
7411
  const port = opts.port ?? 8080;
6640
7412
  const otelPort = opts.otelPort ?? 4318;
6641
- const cachePath = opts.embeddingsCachePath ?? import_node_path38.default.join(import_node_path38.default.dirname(opts.outPath), "embeddings.json");
7413
+ const cachePath = opts.embeddingsCachePath ?? import_node_path42.default.join(import_node_path42.default.dirname(opts.outPath), "embeddings.json");
6642
7414
  let searchIndex;
6643
7415
  try {
6644
7416
  searchIndex = await buildSearchIndex(graph, { cachePath });
@@ -6656,7 +7428,7 @@ async function startWatch(graph, opts) {
6656
7428
  // Paths are derived from the explicit options the watch caller passes
6657
7429
  // — pathsForProject is only used to fill in the embeddings/snapshot
6658
7430
  // fields so the registry shape is complete.
6659
- ...pathsForProject(projectName, import_node_path38.default.dirname(opts.outPath)),
7431
+ ...pathsForProject(projectName, import_node_path42.default.dirname(opts.outPath)),
6660
7432
  snapshotPath: opts.outPath,
6661
7433
  errorsPath: opts.errorsPath,
6662
7434
  staleEventsPath: opts.staleEventsPath
@@ -6743,9 +7515,9 @@ async function startWatch(graph, opts) {
6743
7515
  };
6744
7516
  const onPath = (absPath) => {
6745
7517
  if (shouldIgnore(absPath)) return;
6746
- const rel = import_node_path38.default.relative(opts.scanPath, absPath);
7518
+ const rel = import_node_path42.default.relative(opts.scanPath, absPath);
6747
7519
  if (!rel || rel.startsWith("..")) return;
6748
- pendingPaths.add(rel.split(import_node_path38.default.sep).join("/"));
7520
+ pendingPaths.add(rel.split(import_node_path42.default.sep).join("/"));
6749
7521
  const phases = classifyChange(rel);
6750
7522
  if (phases.size === 0) {
6751
7523
  for (const p of ALL_PHASES) pending.add(p);
@@ -6799,16 +7571,16 @@ async function startWatch(graph, opts) {
6799
7571
 
6800
7572
  // src/deploy/detect.ts
6801
7573
  init_cjs_shims();
6802
- var import_node_fs24 = require("fs");
6803
- var import_node_path39 = __toESM(require("path"), 1);
6804
- var import_node_child_process = require("child_process");
7574
+ var import_node_fs27 = require("fs");
7575
+ var import_node_path43 = __toESM(require("path"), 1);
7576
+ var import_node_child_process2 = require("child_process");
6805
7577
  var import_node_crypto4 = require("crypto");
6806
7578
  function generateToken() {
6807
7579
  return (0, import_node_crypto4.randomBytes)(32).toString("base64url");
6808
7580
  }
6809
7581
  async function probeBinary(binary, arg) {
6810
7582
  return new Promise((resolve) => {
6811
- const child = (0, import_node_child_process.spawn)(binary, [arg], { stdio: "ignore" });
7583
+ const child = (0, import_node_child_process2.spawn)(binary, [arg], { stdio: "ignore" });
6812
7584
  const timer = setTimeout(() => {
6813
7585
  child.kill("SIGKILL");
6814
7586
  resolve(false);
@@ -6900,21 +7672,21 @@ async function runDeploy(opts = {}) {
6900
7672
  const token = generateToken();
6901
7673
  switch (substrate) {
6902
7674
  case "docker-compose": {
6903
- const artifactPath = import_node_path39.default.join(cwd, "docker-compose.neat.yml");
7675
+ const artifactPath = import_node_path43.default.join(cwd, "docker-compose.neat.yml");
6904
7676
  const contents = emitDockerCompose(cwd);
6905
- await import_node_fs24.promises.writeFile(artifactPath, contents, "utf8");
7677
+ await import_node_fs27.promises.writeFile(artifactPath, contents, "utf8");
6906
7678
  return {
6907
7679
  substrate,
6908
7680
  artifactPath,
6909
7681
  token,
6910
7682
  contents,
6911
- startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path39.default.basename(artifactPath)} up -d`
7683
+ startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path43.default.basename(artifactPath)} up -d`
6912
7684
  };
6913
7685
  }
6914
7686
  case "systemd": {
6915
- const artifactPath = import_node_path39.default.join(cwd, "neat.service");
7687
+ const artifactPath = import_node_path43.default.join(cwd, "neat.service");
6916
7688
  const contents = emitSystemdUnit(cwd);
6917
- await import_node_fs24.promises.writeFile(artifactPath, contents, "utf8");
7689
+ await import_node_fs27.promises.writeFile(artifactPath, contents, "utf8");
6918
7690
  return {
6919
7691
  substrate,
6920
7692
  artifactPath,
@@ -6947,8 +7719,9 @@ init_cjs_shims();
6947
7719
 
6948
7720
  // src/installers/javascript.ts
6949
7721
  init_cjs_shims();
6950
- var import_node_fs25 = require("fs");
6951
- var import_node_path40 = __toESM(require("path"), 1);
7722
+ var import_node_fs28 = require("fs");
7723
+ var import_node_path44 = __toESM(require("path"), 1);
7724
+ var import_semver2 = __toESM(require("semver"), 1);
6952
7725
 
6953
7726
  // src/installers/templates.ts
6954
7727
  init_cjs_shims();
@@ -7485,15 +8258,15 @@ var OTEL_ENV = {
7485
8258
  value: "http://localhost:4318/projects/<project>/v1/traces"
7486
8259
  };
7487
8260
  function serviceNodeName(pkg, serviceDir) {
7488
- return pkg.name ?? import_node_path40.default.basename(serviceDir);
8261
+ return pkg.name ?? import_node_path44.default.basename(serviceDir);
7489
8262
  }
7490
8263
  function projectToken(pkg, serviceDir, project) {
7491
8264
  if (project && project.length > 0) return project;
7492
- return pkg.name ?? import_node_path40.default.basename(serviceDir);
8265
+ return pkg.name ?? import_node_path44.default.basename(serviceDir);
7493
8266
  }
7494
8267
  async function readJsonFile(p) {
7495
8268
  try {
7496
- const raw = await import_node_fs25.promises.readFile(p, "utf8");
8269
+ const raw = await import_node_fs28.promises.readFile(p, "utf8");
7497
8270
  return JSON.parse(raw);
7498
8271
  } catch {
7499
8272
  return null;
@@ -7502,26 +8275,33 @@ async function readJsonFile(p) {
7502
8275
  async function detectRuntimeKind(pkgRoot, pkg) {
7503
8276
  const deps = allDeps(pkg);
7504
8277
  if ("react-native" in deps || "expo" in deps) return "react-native";
7505
- const appJson = await readJsonFile(import_node_path40.default.join(pkgRoot, "app.json"));
8278
+ const appJson = await readJsonFile(import_node_path44.default.join(pkgRoot, "app.json"));
7506
8279
  if (appJson && typeof appJson === "object" && "expo" in appJson) {
7507
8280
  return "react-native";
7508
8281
  }
7509
- if (await exists2(import_node_path40.default.join(pkgRoot, "vite.config.js")) || await exists2(import_node_path40.default.join(pkgRoot, "vite.config.ts")) || await exists2(import_node_path40.default.join(pkgRoot, "vite.config.mjs")) || "vite" in deps) {
8282
+ 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) {
7510
8283
  return "browser-bundle";
7511
8284
  }
8285
+ if (await exists3(import_node_path44.default.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
8286
+ if (await exists3(import_node_path44.default.join(pkgRoot, "bun.lockb"))) return "bun";
8287
+ if (await exists3(import_node_path44.default.join(pkgRoot, "deno.json")) || await exists3(import_node_path44.default.join(pkgRoot, "deno.lock"))) {
8288
+ return "deno";
8289
+ }
8290
+ const engines = pkg.engines ?? {};
8291
+ if ("electron" in engines) return "electron";
7512
8292
  return "node";
7513
8293
  }
7514
- async function readPackageJson(serviceDir) {
8294
+ async function readPackageJson2(serviceDir) {
7515
8295
  try {
7516
- const raw = await import_node_fs25.promises.readFile(import_node_path40.default.join(serviceDir, "package.json"), "utf8");
8296
+ const raw = await import_node_fs28.promises.readFile(import_node_path44.default.join(serviceDir, "package.json"), "utf8");
7517
8297
  return JSON.parse(raw);
7518
8298
  } catch {
7519
8299
  return null;
7520
8300
  }
7521
8301
  }
7522
- async function exists2(p) {
8302
+ async function exists3(p) {
7523
8303
  try {
7524
- await import_node_fs25.promises.stat(p);
8304
+ await import_node_fs28.promises.stat(p);
7525
8305
  return true;
7526
8306
  } catch {
7527
8307
  return false;
@@ -7529,7 +8309,7 @@ async function exists2(p) {
7529
8309
  }
7530
8310
  async function readFileMaybe(p) {
7531
8311
  try {
7532
- return await import_node_fs25.promises.readFile(p, "utf8");
8312
+ return await import_node_fs28.promises.readFile(p, "utf8");
7533
8313
  } catch {
7534
8314
  return null;
7535
8315
  }
@@ -7545,14 +8325,20 @@ async function planOtelInitGeneration(file, contents) {
7545
8325
  return null;
7546
8326
  }
7547
8327
  async function detect(serviceDir) {
7548
- const pkg = await readPackageJson(serviceDir);
8328
+ const pkg = await readPackageJson2(serviceDir);
7549
8329
  return pkg !== null && typeof pkg.name === "string";
7550
8330
  }
8331
+ function needsVersionUpgrade(installed, expected) {
8332
+ return !import_semver2.default.satisfies(
8333
+ import_semver2.default.minVersion(installed)?.version ?? installed,
8334
+ expected
8335
+ ) && !import_semver2.default.intersects(installed, expected);
8336
+ }
7551
8337
  var NEXT_CONFIG_CANDIDATES = ["next.config.js", "next.config.ts", "next.config.mjs"];
7552
8338
  async function findNextConfig(serviceDir) {
7553
8339
  for (const name of NEXT_CONFIG_CANDIDATES) {
7554
- const candidate = import_node_path40.default.join(serviceDir, name);
7555
- if (await exists2(candidate)) return candidate;
8340
+ const candidate = import_node_path44.default.join(serviceDir, name);
8341
+ if (await exists3(candidate)) return candidate;
7556
8342
  }
7557
8343
  return null;
7558
8344
  }
@@ -7578,8 +8364,8 @@ function hasRemixDependency(pkg) {
7578
8364
  }
7579
8365
  async function findRemixEntry(serviceDir) {
7580
8366
  for (const rel of REMIX_ENTRY_CANDIDATES) {
7581
- const candidate = import_node_path40.default.join(serviceDir, rel);
7582
- if (await exists2(candidate)) return candidate;
8367
+ const candidate = import_node_path44.default.join(serviceDir, rel);
8368
+ if (await exists3(candidate)) return candidate;
7583
8369
  }
7584
8370
  return null;
7585
8371
  }
@@ -7590,15 +8376,15 @@ function hasSvelteKitDependency(pkg) {
7590
8376
  }
7591
8377
  async function findSvelteKitHooks(serviceDir) {
7592
8378
  for (const rel of SVELTEKIT_HOOKS_CANDIDATES) {
7593
- const candidate = import_node_path40.default.join(serviceDir, rel);
7594
- if (await exists2(candidate)) return candidate;
8379
+ const candidate = import_node_path44.default.join(serviceDir, rel);
8380
+ if (await exists3(candidate)) return candidate;
7595
8381
  }
7596
8382
  return null;
7597
8383
  }
7598
8384
  async function findSvelteKitConfig(serviceDir) {
7599
8385
  for (const rel of SVELTEKIT_CONFIG_CANDIDATES) {
7600
- const candidate = import_node_path40.default.join(serviceDir, rel);
7601
- if (await exists2(candidate)) return candidate;
8386
+ const candidate = import_node_path44.default.join(serviceDir, rel);
8387
+ if (await exists3(candidate)) return candidate;
7602
8388
  }
7603
8389
  return null;
7604
8390
  }
@@ -7608,8 +8394,8 @@ function hasNuxtDependency(pkg) {
7608
8394
  }
7609
8395
  async function findNuxtConfig(serviceDir) {
7610
8396
  for (const name of NUXT_CONFIG_CANDIDATES) {
7611
- const candidate = import_node_path40.default.join(serviceDir, name);
7612
- if (await exists2(candidate)) return candidate;
8397
+ const candidate = import_node_path44.default.join(serviceDir, name);
8398
+ if (await exists3(candidate)) return candidate;
7613
8399
  }
7614
8400
  return null;
7615
8401
  }
@@ -7619,8 +8405,8 @@ function hasAstroDependency(pkg) {
7619
8405
  }
7620
8406
  async function findAstroConfig(serviceDir) {
7621
8407
  for (const name of ASTRO_CONFIG_CANDIDATES) {
7622
- const candidate = import_node_path40.default.join(serviceDir, name);
7623
- if (await exists2(candidate)) return candidate;
8408
+ const candidate = import_node_path44.default.join(serviceDir, name);
8409
+ if (await exists3(candidate)) return candidate;
7624
8410
  }
7625
8411
  return null;
7626
8412
  }
@@ -7633,7 +8419,7 @@ function parseNextMajor(range) {
7633
8419
  return Number.isFinite(n) ? n : null;
7634
8420
  }
7635
8421
  async function isTypeScriptProject(serviceDir) {
7636
- return exists2(import_node_path40.default.join(serviceDir, "tsconfig.json"));
8422
+ return exists3(import_node_path44.default.join(serviceDir, "tsconfig.json"));
7637
8423
  }
7638
8424
  var INDEX_EXTENSIONS = [".ts", ".tsx", ".js", ".mjs", ".cjs"];
7639
8425
  var INDEX_CANDIDATES = INDEX_EXTENSIONS.map((ext) => `index${ext}`);
@@ -7679,8 +8465,8 @@ function entryFromScript(script) {
7679
8465
  }
7680
8466
  async function resolveEntry(serviceDir, pkg) {
7681
8467
  if (typeof pkg.main === "string" && pkg.main.length > 0) {
7682
- const candidate = import_node_path40.default.resolve(serviceDir, pkg.main);
7683
- if (await exists2(candidate)) return candidate;
8468
+ const candidate = import_node_path44.default.resolve(serviceDir, pkg.main);
8469
+ if (await exists3(candidate)) return candidate;
7684
8470
  }
7685
8471
  if (pkg.bin) {
7686
8472
  let binEntry;
@@ -7693,36 +8479,36 @@ async function resolveEntry(serviceDir, pkg) {
7693
8479
  if (typeof first === "string") binEntry = first;
7694
8480
  }
7695
8481
  if (binEntry) {
7696
- const candidate = import_node_path40.default.resolve(serviceDir, binEntry);
7697
- if (await exists2(candidate)) return candidate;
8482
+ const candidate = import_node_path44.default.resolve(serviceDir, binEntry);
8483
+ if (await exists3(candidate)) return candidate;
7698
8484
  }
7699
8485
  }
7700
8486
  const startEntry = entryFromScript(pkg.scripts?.start);
7701
8487
  if (startEntry) {
7702
- const candidate = import_node_path40.default.resolve(serviceDir, startEntry);
7703
- if (await exists2(candidate)) return candidate;
8488
+ const candidate = import_node_path44.default.resolve(serviceDir, startEntry);
8489
+ if (await exists3(candidate)) return candidate;
7704
8490
  }
7705
8491
  const devEntry = entryFromScript(pkg.scripts?.dev);
7706
8492
  if (devEntry) {
7707
- const candidate = import_node_path40.default.resolve(serviceDir, devEntry);
7708
- if (await exists2(candidate)) return candidate;
8493
+ const candidate = import_node_path44.default.resolve(serviceDir, devEntry);
8494
+ if (await exists3(candidate)) return candidate;
7709
8495
  }
7710
8496
  for (const rel of SRC_INDEX_CANDIDATES) {
7711
- const candidate = import_node_path40.default.join(serviceDir, rel);
7712
- if (await exists2(candidate)) return candidate;
8497
+ const candidate = import_node_path44.default.join(serviceDir, rel);
8498
+ if (await exists3(candidate)) return candidate;
7713
8499
  }
7714
8500
  for (const rel of SRC_NAMED_CANDIDATES) {
7715
- const candidate = import_node_path40.default.join(serviceDir, rel);
7716
- if (await exists2(candidate)) return candidate;
8501
+ const candidate = import_node_path44.default.join(serviceDir, rel);
8502
+ if (await exists3(candidate)) return candidate;
7717
8503
  }
7718
8504
  for (const name of INDEX_CANDIDATES) {
7719
- const candidate = import_node_path40.default.join(serviceDir, name);
7720
- if (await exists2(candidate)) return candidate;
8505
+ const candidate = import_node_path44.default.join(serviceDir, name);
8506
+ if (await exists3(candidate)) return candidate;
7721
8507
  }
7722
8508
  return null;
7723
8509
  }
7724
8510
  function dispatchEntry(entryFile, pkg) {
7725
- const ext = import_node_path40.default.extname(entryFile).toLowerCase();
8511
+ const ext = import_node_path44.default.extname(entryFile).toLowerCase();
7726
8512
  if (ext === ".ts" || ext === ".tsx") return "ts";
7727
8513
  if (ext === ".mjs") return "esm";
7728
8514
  if (ext === ".cjs") return "cjs";
@@ -7739,9 +8525,9 @@ function otelInitContents(flavor) {
7739
8525
  return OTEL_INIT_CJS;
7740
8526
  }
7741
8527
  function injectionLine(flavor, entryFile, otelInitFile) {
7742
- let rel = import_node_path40.default.relative(import_node_path40.default.dirname(entryFile), otelInitFile);
8528
+ let rel = import_node_path44.default.relative(import_node_path44.default.dirname(entryFile), otelInitFile);
7743
8529
  if (!rel.startsWith(".")) rel = `./${rel}`;
7744
- rel = rel.split(import_node_path40.default.sep).join("/");
8530
+ rel = rel.split(import_node_path44.default.sep).join("/");
7745
8531
  if (flavor === "cjs") return `require('${rel}')`;
7746
8532
  if (flavor === "esm") return `import '${rel}'`;
7747
8533
  const tsRel = rel.replace(/\.ts$/, "");
@@ -7754,56 +8540,56 @@ function lineIsOtelInjection(line) {
7754
8540
  }
7755
8541
  async function detectsSrcLayout(serviceDir) {
7756
8542
  const [hasSrcApp, hasSrcPages, hasRootApp, hasRootPages] = await Promise.all([
7757
- exists2(import_node_path40.default.join(serviceDir, "src", "app")),
7758
- exists2(import_node_path40.default.join(serviceDir, "src", "pages")),
7759
- exists2(import_node_path40.default.join(serviceDir, "app")),
7760
- exists2(import_node_path40.default.join(serviceDir, "pages"))
8543
+ exists3(import_node_path44.default.join(serviceDir, "src", "app")),
8544
+ exists3(import_node_path44.default.join(serviceDir, "src", "pages")),
8545
+ exists3(import_node_path44.default.join(serviceDir, "app")),
8546
+ exists3(import_node_path44.default.join(serviceDir, "pages"))
7761
8547
  ]);
7762
8548
  return (hasSrcApp || hasSrcPages) && !hasRootApp && !hasRootPages;
7763
8549
  }
7764
8550
  async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project) {
7765
8551
  const useTs = await isTypeScriptProject(serviceDir);
7766
8552
  const srcLayout = await detectsSrcLayout(serviceDir);
7767
- const baseDir = srcLayout ? import_node_path40.default.join(serviceDir, "src") : serviceDir;
7768
- const instrumentationFile = import_node_path40.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
7769
- const instrumentationNodeFile = import_node_path40.default.join(
8553
+ const baseDir = srcLayout ? import_node_path44.default.join(serviceDir, "src") : serviceDir;
8554
+ const instrumentationFile = import_node_path44.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
8555
+ const instrumentationNodeFile = import_node_path44.default.join(
7770
8556
  baseDir,
7771
8557
  useTs ? "instrumentation.node.ts" : "instrumentation.node.js"
7772
8558
  );
7773
- const envNeatFile = import_node_path40.default.join(baseDir, ".env.neat");
8559
+ const envNeatFile = import_node_path44.default.join(baseDir, ".env.neat");
7774
8560
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
7775
8561
  const dependencyEdits = [];
7776
8562
  for (const sdk of SDK_PACKAGES) {
7777
- if (sdk.name in existingDeps) continue;
7778
- dependencyEdits.push({
7779
- file: manifestPath,
7780
- kind: "add",
7781
- name: sdk.name,
7782
- version: sdk.version
7783
- });
8563
+ if (sdk.name in existingDeps) {
8564
+ if (needsVersionUpgrade(existingDeps[sdk.name], sdk.version)) {
8565
+ dependencyEdits.push({ file: manifestPath, kind: "upgrade", name: sdk.name, version: sdk.version, fromVersion: existingDeps[sdk.name] });
8566
+ }
8567
+ continue;
8568
+ }
8569
+ dependencyEdits.push({ file: manifestPath, kind: "add", name: sdk.name, version: sdk.version });
7784
8570
  }
7785
8571
  const nonBundled = detectNonBundledInstrumentations(pkg);
7786
8572
  for (const inst of nonBundled) {
7787
- if (inst.pkg in existingDeps) continue;
7788
- dependencyEdits.push({
7789
- file: manifestPath,
7790
- kind: "add",
7791
- name: inst.pkg,
7792
- version: inst.version
7793
- });
8573
+ if (inst.pkg in existingDeps) {
8574
+ if (needsVersionUpgrade(existingDeps[inst.pkg], inst.version)) {
8575
+ dependencyEdits.push({ file: manifestPath, kind: "upgrade", name: inst.pkg, version: inst.version, fromVersion: existingDeps[inst.pkg] });
8576
+ }
8577
+ continue;
8578
+ }
8579
+ dependencyEdits.push({ file: manifestPath, kind: "add", name: inst.pkg, version: inst.version });
7794
8580
  }
7795
8581
  const svcName = serviceNodeName(pkg, serviceDir);
7796
8582
  const projectName = projectToken(pkg, serviceDir, project);
7797
8583
  const registrations = nonBundled.map((i) => i.registration);
7798
8584
  const generatedFiles = [];
7799
- if (!await exists2(instrumentationFile)) {
8585
+ if (!await exists3(instrumentationFile)) {
7800
8586
  generatedFiles.push({
7801
8587
  file: instrumentationFile,
7802
8588
  contents: useTs ? NEXT_INSTRUMENTATION_TS : NEXT_INSTRUMENTATION_JS,
7803
8589
  skipIfExists: true
7804
8590
  });
7805
8591
  }
7806
- if (!await exists2(instrumentationNodeFile)) {
8592
+ if (!await exists3(instrumentationNodeFile)) {
7807
8593
  generatedFiles.push({
7808
8594
  file: instrumentationNodeFile,
7809
8595
  contents: renderNextInstrumentationNode(
@@ -7815,7 +8601,7 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
7815
8601
  skipIfExists: true
7816
8602
  });
7817
8603
  }
7818
- if (!await exists2(envNeatFile)) {
8604
+ if (!await exists3(envNeatFile)) {
7819
8605
  generatedFiles.push({
7820
8606
  file: envNeatFile,
7821
8607
  contents: renderEnvNeat(svcName, projectName),
@@ -7827,7 +8613,7 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
7827
8613
  const nextMajor = parseNextMajor(nextRange);
7828
8614
  if (nextMajor !== null && nextMajor < 15) {
7829
8615
  try {
7830
- const raw = await import_node_fs25.promises.readFile(nextConfigPath, "utf8");
8616
+ const raw = await import_node_fs28.promises.readFile(nextConfigPath, "utf8");
7831
8617
  if (!raw.includes("instrumentationHook")) {
7832
8618
  nextConfigEdit = {
7833
8619
  file: nextConfigPath,
@@ -7875,8 +8661,8 @@ function buildDependencyEdits(pkg, manifestPath) {
7875
8661
  return edits;
7876
8662
  }
7877
8663
  async function queueEnvNeat(serviceDir, pkg, project, generatedFiles) {
7878
- const envNeatFile = import_node_path40.default.join(serviceDir, ".env.neat");
7879
- if (!await exists2(envNeatFile)) {
8664
+ const envNeatFile = import_node_path44.default.join(serviceDir, ".env.neat");
8665
+ if (!await exists3(envNeatFile)) {
7880
8666
  generatedFiles.push({
7881
8667
  file: envNeatFile,
7882
8668
  contents: renderEnvNeat(
@@ -7910,13 +8696,13 @@ function fileImportsOtelHook(raw, specifiers) {
7910
8696
  }
7911
8697
  async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
7912
8698
  const useTs = await isTypeScriptProject(serviceDir);
7913
- const otelServerFile = import_node_path40.default.join(
8699
+ const otelServerFile = import_node_path44.default.join(
7914
8700
  serviceDir,
7915
8701
  useTs ? "app/otel.server.ts" : "app/otel.server.js"
7916
8702
  );
7917
8703
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
7918
8704
  const generatedFiles = [];
7919
- if (!await exists2(otelServerFile)) {
8705
+ if (!await exists3(otelServerFile)) {
7920
8706
  generatedFiles.push({
7921
8707
  file: otelServerFile,
7922
8708
  contents: renderFrameworkOtelInitForPkg(
@@ -7931,7 +8717,7 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
7931
8717
  await queueEnvNeat(serviceDir, pkg, project, generatedFiles);
7932
8718
  const entrypointEdits = [];
7933
8719
  try {
7934
- const raw = await import_node_fs25.promises.readFile(entryFile, "utf8");
8720
+ const raw = await import_node_fs28.promises.readFile(entryFile, "utf8");
7935
8721
  if (!fileImportsOtelHook(raw, ["./otel.server"])) {
7936
8722
  const lines = raw.split(/\r?\n/);
7937
8723
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
@@ -7967,15 +8753,15 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
7967
8753
  }
7968
8754
  async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project) {
7969
8755
  const useTs = await isTypeScriptProject(serviceDir);
7970
- const otelInitFile = import_node_path40.default.join(
8756
+ const otelInitFile = import_node_path44.default.join(
7971
8757
  serviceDir,
7972
8758
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
7973
8759
  );
7974
- const resolvedHooksFile = hooksFile ?? import_node_path40.default.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
8760
+ const resolvedHooksFile = hooksFile ?? import_node_path44.default.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
7975
8761
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
7976
8762
  const generatedFiles = [];
7977
8763
  const entrypointEdits = [];
7978
- if (!await exists2(otelInitFile)) {
8764
+ if (!await exists3(otelInitFile)) {
7979
8765
  generatedFiles.push({
7980
8766
  file: otelInitFile,
7981
8767
  contents: renderFrameworkOtelInitForPkg(
@@ -7996,7 +8782,7 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
7996
8782
  });
7997
8783
  } else {
7998
8784
  try {
7999
- const raw = await import_node_fs25.promises.readFile(hooksFile, "utf8");
8785
+ const raw = await import_node_fs28.promises.readFile(hooksFile, "utf8");
8000
8786
  if (!fileImportsOtelHook(raw, ["./otel-init"])) {
8001
8787
  const lines = raw.split(/\r?\n/);
8002
8788
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
@@ -8033,17 +8819,17 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
8033
8819
  }
8034
8820
  async function planNuxt(serviceDir, pkg, manifestPath, project) {
8035
8821
  const useTs = await isTypeScriptProject(serviceDir);
8036
- const otelPluginFile = import_node_path40.default.join(
8822
+ const otelPluginFile = import_node_path44.default.join(
8037
8823
  serviceDir,
8038
8824
  useTs ? "server/plugins/otel.ts" : "server/plugins/otel.js"
8039
8825
  );
8040
- const otelInitFile = import_node_path40.default.join(
8826
+ const otelInitFile = import_node_path44.default.join(
8041
8827
  serviceDir,
8042
8828
  useTs ? "server/plugins/otel-init.ts" : "server/plugins/otel-init.js"
8043
8829
  );
8044
8830
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
8045
8831
  const generatedFiles = [];
8046
- if (!await exists2(otelInitFile)) {
8832
+ if (!await exists3(otelInitFile)) {
8047
8833
  generatedFiles.push({
8048
8834
  file: otelInitFile,
8049
8835
  contents: renderFrameworkOtelInitForPkg(
@@ -8055,7 +8841,7 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
8055
8841
  skipIfExists: true
8056
8842
  });
8057
8843
  }
8058
- if (!await exists2(otelPluginFile)) {
8844
+ if (!await exists3(otelPluginFile)) {
8059
8845
  generatedFiles.push({
8060
8846
  file: otelPluginFile,
8061
8847
  contents: useTs ? NUXT_OTEL_PLUGIN_TS : NUXT_OTEL_PLUGIN_JS,
@@ -8088,23 +8874,23 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
8088
8874
  var ASTRO_MIDDLEWARE_CANDIDATES = ["src/middleware.ts", "src/middleware.js"];
8089
8875
  async function findAstroMiddleware(serviceDir) {
8090
8876
  for (const rel of ASTRO_MIDDLEWARE_CANDIDATES) {
8091
- const candidate = import_node_path40.default.join(serviceDir, rel);
8092
- if (await exists2(candidate)) return candidate;
8877
+ const candidate = import_node_path44.default.join(serviceDir, rel);
8878
+ if (await exists3(candidate)) return candidate;
8093
8879
  }
8094
8880
  return null;
8095
8881
  }
8096
8882
  async function planAstro(serviceDir, pkg, manifestPath, project) {
8097
8883
  const useTs = await isTypeScriptProject(serviceDir);
8098
- const otelInitFile = import_node_path40.default.join(
8884
+ const otelInitFile = import_node_path44.default.join(
8099
8885
  serviceDir,
8100
8886
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
8101
8887
  );
8102
8888
  const existingMiddleware = await findAstroMiddleware(serviceDir);
8103
- const middlewareFile = existingMiddleware ?? import_node_path40.default.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
8889
+ const middlewareFile = existingMiddleware ?? import_node_path44.default.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
8104
8890
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
8105
8891
  const generatedFiles = [];
8106
8892
  const entrypointEdits = [];
8107
- if (!await exists2(otelInitFile)) {
8893
+ if (!await exists3(otelInitFile)) {
8108
8894
  generatedFiles.push({
8109
8895
  file: otelInitFile,
8110
8896
  contents: renderFrameworkOtelInitForPkg(
@@ -8125,7 +8911,7 @@ async function planAstro(serviceDir, pkg, manifestPath, project) {
8125
8911
  });
8126
8912
  } else {
8127
8913
  try {
8128
- const raw = await import_node_fs25.promises.readFile(existingMiddleware, "utf8");
8914
+ const raw = await import_node_fs28.promises.readFile(existingMiddleware, "utf8");
8129
8915
  if (!fileImportsOtelHook(raw, ["./otel-init"])) {
8130
8916
  const lines = raw.split(/\r?\n/);
8131
8917
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
@@ -8195,8 +8981,8 @@ async function findFrameworkDispatch(serviceDir, pkg, manifestPath, project) {
8195
8981
  return null;
8196
8982
  }
8197
8983
  async function plan(serviceDir, opts) {
8198
- const pkg = await readPackageJson(serviceDir);
8199
- const manifestPath = import_node_path40.default.join(serviceDir, "package.json");
8984
+ const pkg = await readPackageJson2(serviceDir);
8985
+ const manifestPath = import_node_path44.default.join(serviceDir, "package.json");
8200
8986
  const project = opts?.project;
8201
8987
  const empty = {
8202
8988
  language: "javascript",
@@ -8231,32 +9017,32 @@ async function plan(serviceDir, opts) {
8231
9017
  return { ...empty, libOnly: true };
8232
9018
  }
8233
9019
  const flavor = dispatchEntry(entryFile, pkg);
8234
- const otelInitFile = import_node_path40.default.join(import_node_path40.default.dirname(entryFile), otelInitFilename(flavor));
8235
- const envNeatFile = import_node_path40.default.join(serviceDir, ".env.neat");
9020
+ const otelInitFile = import_node_path44.default.join(import_node_path44.default.dirname(entryFile), otelInitFilename(flavor));
9021
+ const envNeatFile = import_node_path44.default.join(serviceDir, ".env.neat");
8236
9022
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
8237
9023
  const dependencyEdits = [];
8238
9024
  for (const sdk of SDK_PACKAGES) {
8239
- if (sdk.name in existingDeps) continue;
8240
- dependencyEdits.push({
8241
- file: manifestPath,
8242
- kind: "add",
8243
- name: sdk.name,
8244
- version: sdk.version
8245
- });
9025
+ if (sdk.name in existingDeps) {
9026
+ if (needsVersionUpgrade(existingDeps[sdk.name], sdk.version)) {
9027
+ dependencyEdits.push({ file: manifestPath, kind: "upgrade", name: sdk.name, version: sdk.version, fromVersion: existingDeps[sdk.name] });
9028
+ }
9029
+ continue;
9030
+ }
9031
+ dependencyEdits.push({ file: manifestPath, kind: "add", name: sdk.name, version: sdk.version });
8246
9032
  }
8247
9033
  const nonBundled = detectNonBundledInstrumentations(pkg);
8248
9034
  for (const inst of nonBundled) {
8249
- if (inst.pkg in existingDeps) continue;
8250
- dependencyEdits.push({
8251
- file: manifestPath,
8252
- kind: "add",
8253
- name: inst.pkg,
8254
- version: inst.version
8255
- });
9035
+ if (inst.pkg in existingDeps) {
9036
+ if (needsVersionUpgrade(existingDeps[inst.pkg], inst.version)) {
9037
+ dependencyEdits.push({ file: manifestPath, kind: "upgrade", name: inst.pkg, version: inst.version, fromVersion: existingDeps[inst.pkg] });
9038
+ }
9039
+ continue;
9040
+ }
9041
+ dependencyEdits.push({ file: manifestPath, kind: "add", name: inst.pkg, version: inst.version });
8256
9042
  }
8257
9043
  const entrypointEdits = [];
8258
9044
  try {
8259
- const raw = await import_node_fs25.promises.readFile(entryFile, "utf8");
9045
+ const raw = await import_node_fs28.promises.readFile(entryFile, "utf8");
8260
9046
  const lines = raw.split(/\r?\n/);
8261
9047
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
8262
9048
  if (!lineIsOtelInjection(firstReal)) {
@@ -8279,7 +9065,7 @@ async function plan(serviceDir, opts) {
8279
9065
  renderNodeOtelInit(otelInitContents(flavor), svcName, projectName, registrations)
8280
9066
  );
8281
9067
  if (otelInitGen) generatedFiles.push(otelInitGen);
8282
- if (!await exists2(envNeatFile)) {
9068
+ if (!await exists3(envNeatFile)) {
8283
9069
  generatedFiles.push({
8284
9070
  file: envNeatFile,
8285
9071
  contents: renderEnvNeat(svcName, projectName),
@@ -8301,13 +9087,13 @@ async function plan(serviceDir, opts) {
8301
9087
  };
8302
9088
  }
8303
9089
  function isAllowedWritePath(serviceDir, target) {
8304
- const rel = import_node_path40.default.relative(serviceDir, target);
9090
+ const rel = import_node_path44.default.relative(serviceDir, target);
8305
9091
  if (rel.startsWith("..")) return false;
8306
- const base = import_node_path40.default.basename(target);
9092
+ const base = import_node_path44.default.basename(target);
8307
9093
  if (base === "package.json") return true;
8308
9094
  if (base === ".env.neat") return true;
8309
9095
  if (/^otel-init\.(?:js|cjs|mjs|ts)$/.test(base)) return true;
8310
- const relPosix = rel.split(import_node_path40.default.sep).join("/");
9096
+ const relPosix = rel.split(import_node_path44.default.sep).join("/");
8311
9097
  if (/^instrumentation(?:\.node)?\.(?:js|cjs|mjs|ts)$/.test(base)) {
8312
9098
  if (relPosix === base) return true;
8313
9099
  if (relPosix === `src/${base}`) return true;
@@ -8324,10 +9110,10 @@ function isAllowedWritePath(serviceDir, target) {
8324
9110
  return false;
8325
9111
  }
8326
9112
  async function writeAtomic(file, contents) {
8327
- await import_node_fs25.promises.mkdir(import_node_path40.default.dirname(file), { recursive: true });
9113
+ await import_node_fs28.promises.mkdir(import_node_path44.default.dirname(file), { recursive: true });
8328
9114
  const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
8329
- await import_node_fs25.promises.writeFile(tmp, contents, "utf8");
8330
- await import_node_fs25.promises.rename(tmp, file);
9115
+ await import_node_fs28.promises.writeFile(tmp, contents, "utf8");
9116
+ await import_node_fs28.promises.rename(tmp, file);
8331
9117
  }
8332
9118
  async function apply(installPlan) {
8333
9119
  const { serviceDir } = installPlan;
@@ -8355,6 +9141,28 @@ async function apply(installPlan) {
8355
9141
  writtenFiles: []
8356
9142
  };
8357
9143
  }
9144
+ if (installPlan.runtimeKind === "bun") {
9145
+ return { serviceDir, outcome: "bun", reason: "Bun runtime; use BYO-OTel", writtenFiles: [] };
9146
+ }
9147
+ if (installPlan.runtimeKind === "deno") {
9148
+ return { serviceDir, outcome: "deno", reason: "Deno runtime; use BYO-OTel", writtenFiles: [] };
9149
+ }
9150
+ if (installPlan.runtimeKind === "cloudflare-workers") {
9151
+ return {
9152
+ serviceDir,
9153
+ outcome: "cloudflare-workers",
9154
+ reason: "Cloudflare Workers runtime; use BYO-OTel",
9155
+ writtenFiles: []
9156
+ };
9157
+ }
9158
+ if (installPlan.runtimeKind === "electron") {
9159
+ return {
9160
+ serviceDir,
9161
+ outcome: "electron",
9162
+ reason: "Electron; use BYO-OTel",
9163
+ writtenFiles: []
9164
+ };
9165
+ }
8358
9166
  if (installPlan.dependencyEdits.length === 0 && installPlan.entrypointEdits.length === 0 && (installPlan.generatedFiles?.length ?? 0) === 0 && installPlan.nextConfigEdit === void 0) {
8359
9167
  return {
8360
9168
  serviceDir,
@@ -8379,9 +9187,9 @@ async function apply(installPlan) {
8379
9187
  const originals = /* @__PURE__ */ new Map();
8380
9188
  const createdFiles = [];
8381
9189
  for (const target of allTargets) {
8382
- if (await exists2(target)) {
9190
+ if (await exists3(target)) {
8383
9191
  try {
8384
- originals.set(target, await import_node_fs25.promises.readFile(target, "utf8"));
9192
+ originals.set(target, await import_node_fs28.promises.readFile(target, "utf8"));
8385
9193
  } catch {
8386
9194
  }
8387
9195
  }
@@ -8405,6 +9213,8 @@ async function apply(installPlan) {
8405
9213
  if (!(dep.name in (pkg.dependencies ?? {}))) {
8406
9214
  pkg.dependencies[dep.name] = dep.version;
8407
9215
  }
9216
+ } else if (dep.kind === "upgrade") {
9217
+ pkg.dependencies[dep.name] = dep.version;
8408
9218
  } else {
8409
9219
  delete pkg.dependencies[dep.name];
8410
9220
  }
@@ -8414,7 +9224,7 @@ async function apply(installPlan) {
8414
9224
  writtenFiles.push(file);
8415
9225
  }
8416
9226
  for (const gen of installPlan.generatedFiles ?? []) {
8417
- if (gen.skipIfExists && await exists2(gen.file)) {
9227
+ if (gen.skipIfExists && await exists3(gen.file)) {
8418
9228
  continue;
8419
9229
  }
8420
9230
  await writeAtomic(gen.file, gen.contents);
@@ -8462,14 +9272,14 @@ async function rollback(installPlan, originals, createdFiles) {
8462
9272
  const removed = [];
8463
9273
  for (const [file, raw] of originals.entries()) {
8464
9274
  try {
8465
- await import_node_fs25.promises.writeFile(file, raw, "utf8");
9275
+ await import_node_fs28.promises.writeFile(file, raw, "utf8");
8466
9276
  restored.push(file);
8467
9277
  } catch {
8468
9278
  }
8469
9279
  }
8470
9280
  for (const file of createdFiles) {
8471
9281
  try {
8472
- await import_node_fs25.promises.unlink(file);
9282
+ await import_node_fs28.promises.unlink(file);
8473
9283
  removed.push(file);
8474
9284
  } catch {
8475
9285
  }
@@ -8484,8 +9294,8 @@ async function rollback(installPlan, originals, createdFiles) {
8484
9294
  ...removed.map((f) => `removed: ${f}`),
8485
9295
  ""
8486
9296
  ];
8487
- const rollbackPath = import_node_path40.default.join(installPlan.serviceDir, "neat-rollback.patch");
8488
- await import_node_fs25.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
9297
+ const rollbackPath = import_node_path44.default.join(installPlan.serviceDir, "neat-rollback.patch");
9298
+ await import_node_fs28.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
8489
9299
  }
8490
9300
  function injectInstrumentationHook(raw) {
8491
9301
  if (raw.includes("instrumentationHook")) return raw;
@@ -8514,8 +9324,8 @@ var javascriptInstaller = {
8514
9324
 
8515
9325
  // src/installers/python.ts
8516
9326
  init_cjs_shims();
8517
- var import_node_fs26 = require("fs");
8518
- var import_node_path41 = __toESM(require("path"), 1);
9327
+ var import_node_fs29 = require("fs");
9328
+ var import_node_path45 = __toESM(require("path"), 1);
8519
9329
  var SDK_PACKAGES2 = [
8520
9330
  { name: "opentelemetry-distro", version: ">=0.49b0" },
8521
9331
  { name: "opentelemetry-exporter-otlp", version: ">=1.28.0" }
@@ -8525,9 +9335,9 @@ var OTEL_ENV2 = {
8525
9335
  key: "OTEL_EXPORTER_OTLP_ENDPOINT",
8526
9336
  value: "http://localhost:4318"
8527
9337
  };
8528
- async function exists3(p) {
9338
+ async function exists4(p) {
8529
9339
  try {
8530
- await import_node_fs26.promises.stat(p);
9340
+ await import_node_fs29.promises.stat(p);
8531
9341
  return true;
8532
9342
  } catch {
8533
9343
  return false;
@@ -8536,7 +9346,7 @@ async function exists3(p) {
8536
9346
  async function detect2(serviceDir) {
8537
9347
  const markers = ["requirements.txt", "pyproject.toml", "setup.py"];
8538
9348
  for (const m of markers) {
8539
- if (await exists3(import_node_path41.default.join(serviceDir, m))) return true;
9349
+ if (await exists4(import_node_path45.default.join(serviceDir, m))) return true;
8540
9350
  }
8541
9351
  return false;
8542
9352
  }
@@ -8546,9 +9356,9 @@ function reqPackageName(line) {
8546
9356
  return head.replace(/[<>=!~].*$/, "").toLowerCase();
8547
9357
  }
8548
9358
  async function planRequirementsTxtEdits(serviceDir) {
8549
- const file = import_node_path41.default.join(serviceDir, "requirements.txt");
8550
- if (!await exists3(file)) return null;
8551
- const raw = await import_node_fs26.promises.readFile(file, "utf8");
9359
+ const file = import_node_path45.default.join(serviceDir, "requirements.txt");
9360
+ if (!await exists4(file)) return null;
9361
+ const raw = await import_node_fs29.promises.readFile(file, "utf8");
8552
9362
  const presentNames = new Set(
8553
9363
  raw.split(/\r?\n/).map(reqPackageName).filter((n) => n.length > 0)
8554
9364
  );
@@ -8556,9 +9366,9 @@ async function planRequirementsTxtEdits(serviceDir) {
8556
9366
  return { manifest: file, missing: [...missing] };
8557
9367
  }
8558
9368
  async function planProcfileEdits(serviceDir) {
8559
- const procfile = import_node_path41.default.join(serviceDir, "Procfile");
8560
- if (!await exists3(procfile)) return [];
8561
- const raw = await import_node_fs26.promises.readFile(procfile, "utf8");
9369
+ const procfile = import_node_path45.default.join(serviceDir, "Procfile");
9370
+ if (!await exists4(procfile)) return [];
9371
+ const raw = await import_node_fs29.promises.readFile(procfile, "utf8");
8562
9372
  const edits = [];
8563
9373
  for (const line of raw.split(/\r?\n/)) {
8564
9374
  if (line.length === 0) continue;
@@ -8610,8 +9420,8 @@ async function applyRequirementsTxt(manifest, edits, original) {
8610
9420
  const next = `${original}${trailing}${newlines.join("\n")}
8611
9421
  `;
8612
9422
  const tmp = `${manifest}.${process.pid}.${Date.now()}.tmp`;
8613
- await import_node_fs26.promises.writeFile(tmp, next, "utf8");
8614
- await import_node_fs26.promises.rename(tmp, manifest);
9423
+ await import_node_fs29.promises.writeFile(tmp, next, "utf8");
9424
+ await import_node_fs29.promises.rename(tmp, manifest);
8615
9425
  }
8616
9426
  async function applyProcfile(procfile, edits, original) {
8617
9427
  let next = original;
@@ -8620,8 +9430,8 @@ async function applyProcfile(procfile, edits, original) {
8620
9430
  next = next.replace(e.before, e.after);
8621
9431
  }
8622
9432
  const tmp = `${procfile}.${process.pid}.${Date.now()}.tmp`;
8623
- await import_node_fs26.promises.writeFile(tmp, next, "utf8");
8624
- await import_node_fs26.promises.rename(tmp, procfile);
9433
+ await import_node_fs29.promises.writeFile(tmp, next, "utf8");
9434
+ await import_node_fs29.promises.rename(tmp, procfile);
8625
9435
  }
8626
9436
  async function apply2(installPlan) {
8627
9437
  const { serviceDir } = installPlan;
@@ -8634,7 +9444,7 @@ async function apply2(installPlan) {
8634
9444
  const originals = /* @__PURE__ */ new Map();
8635
9445
  for (const file of touched) {
8636
9446
  try {
8637
- originals.set(file, await import_node_fs26.promises.readFile(file, "utf8"));
9447
+ originals.set(file, await import_node_fs29.promises.readFile(file, "utf8"));
8638
9448
  } catch {
8639
9449
  }
8640
9450
  }
@@ -8645,7 +9455,7 @@ async function apply2(installPlan) {
8645
9455
  if (raw === void 0) {
8646
9456
  throw new Error(`python installer: cannot read ${file} during apply`);
8647
9457
  }
8648
- const base = import_node_path41.default.basename(file);
9458
+ const base = import_node_path45.default.basename(file);
8649
9459
  if (base === "requirements.txt") {
8650
9460
  const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
8651
9461
  if (edits.length > 0) {
@@ -8670,7 +9480,7 @@ async function rollback2(installPlan, originals) {
8670
9480
  const restored = [];
8671
9481
  for (const [file, raw] of originals.entries()) {
8672
9482
  try {
8673
- await import_node_fs26.promises.writeFile(file, raw, "utf8");
9483
+ await import_node_fs29.promises.writeFile(file, raw, "utf8");
8674
9484
  restored.push(file);
8675
9485
  } catch {
8676
9486
  }
@@ -8684,8 +9494,8 @@ async function rollback2(installPlan, originals) {
8684
9494
  ...restored.map((f) => `restored: ${f}`),
8685
9495
  ""
8686
9496
  ];
8687
- const rollbackPath = import_node_path41.default.join(installPlan.serviceDir, "neat-rollback.patch");
8688
- await import_node_fs26.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
9497
+ const rollbackPath = import_node_path45.default.join(installPlan.serviceDir, "neat-rollback.patch");
9498
+ await import_node_fs29.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
8689
9499
  }
8690
9500
  var pythonInstaller = {
8691
9501
  name: "python",
@@ -8808,100 +9618,19 @@ function renderPatch(sections) {
8808
9618
 
8809
9619
  // src/orchestrator.ts
8810
9620
  init_cjs_shims();
8811
- var import_node_fs28 = require("fs");
9621
+ var import_node_fs30 = require("fs");
8812
9622
  var import_node_http = __toESM(require("http"), 1);
8813
9623
  var import_node_net = __toESM(require("net"), 1);
8814
- var import_node_path43 = __toESM(require("path"), 1);
9624
+ var import_node_path46 = __toESM(require("path"), 1);
8815
9625
  var import_node_child_process3 = require("child_process");
8816
9626
  var import_node_readline = __toESM(require("readline"), 1);
8817
-
8818
- // src/installers/package-manager.ts
8819
- init_cjs_shims();
8820
- var import_node_fs27 = require("fs");
8821
- var import_node_path42 = __toESM(require("path"), 1);
8822
- var import_node_child_process2 = require("child_process");
8823
- var LOCKFILE_PRIORITY = [
8824
- { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
8825
- { lockfile: "pnpm-lock.yaml", pm: "pnpm", args: ["install", "--no-summary"] },
8826
- { lockfile: "yarn.lock", pm: "yarn", args: ["install", "--silent"] },
8827
- {
8828
- lockfile: "package-lock.json",
8829
- pm: "npm",
8830
- args: ["install", "--no-audit", "--no-fund", "--prefer-offline"]
8831
- }
8832
- ];
8833
- var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
8834
- async function exists4(p) {
8835
- try {
8836
- await import_node_fs27.promises.access(p);
8837
- return true;
8838
- } catch {
8839
- return false;
8840
- }
8841
- }
8842
- async function detectPackageManager(serviceDir) {
8843
- let dir = import_node_path42.default.resolve(serviceDir);
8844
- const stops = /* @__PURE__ */ new Set();
8845
- for (let i = 0; i < 64; i++) {
8846
- if (stops.has(dir)) break;
8847
- stops.add(dir);
8848
- for (const candidate of LOCKFILE_PRIORITY) {
8849
- const lockPath = import_node_path42.default.join(dir, candidate.lockfile);
8850
- if (await exists4(lockPath)) {
8851
- return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
8852
- }
8853
- }
8854
- const parent = import_node_path42.default.dirname(dir);
8855
- if (parent === dir) break;
8856
- dir = parent;
8857
- }
8858
- return { pm: "npm", cwd: import_node_path42.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
8859
- }
8860
- async function runPackageManagerInstall(cmd) {
8861
- return new Promise((resolve) => {
8862
- const child = (0, import_node_child_process2.spawn)(cmd.pm, cmd.args, {
8863
- cwd: cmd.cwd,
8864
- // Inherit PATH + HOME so the user's installed managers resolve.
8865
- env: process.env,
8866
- // `false` keeps the parent in control of cleanup if the orchestrator
8867
- // exits before install finishes. Cross-platform-safe.
8868
- shell: false,
8869
- stdio: ["ignore", "ignore", "pipe"]
8870
- });
8871
- let stderr = "";
8872
- child.stderr?.on("data", (chunk) => {
8873
- stderr += chunk.toString("utf8");
8874
- });
8875
- child.on("error", (err) => {
8876
- resolve({
8877
- pm: cmd.pm,
8878
- cwd: cmd.cwd,
8879
- args: cmd.args,
8880
- exitCode: 127,
8881
- stderr: stderr + `
8882
- ${err.message}`
8883
- });
8884
- });
8885
- child.on("close", (code) => {
8886
- resolve({
8887
- pm: cmd.pm,
8888
- cwd: cmd.cwd,
8889
- args: cmd.args,
8890
- exitCode: code ?? 1,
8891
- stderr: stderr.trim()
8892
- });
8893
- });
8894
- });
8895
- }
8896
-
8897
- // src/orchestrator.ts
8898
9627
  async function extractAndPersist(opts) {
8899
9628
  const services = await discoverServices(opts.scanPath);
8900
9629
  const languages = [...new Set(services.map((s) => s.node.language))].sort();
8901
9630
  const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
8902
9631
  resetGraph(graphKey);
8903
9632
  const graph = getGraph(graphKey);
8904
- const projectPaths = pathsForProject(graphKey, import_node_path43.default.join(opts.scanPath, "neat-out"));
9633
+ const projectPaths = pathsForProject(graphKey, import_node_path46.default.join(opts.scanPath, "neat-out"));
8905
9634
  const extraction = await extractFromDirectory(graph, opts.scanPath, {
8906
9635
  errorsPath: projectPaths.errorsPath
8907
9636
  });
@@ -8927,6 +9656,10 @@ async function applyInstallersOver(services, project, options = {}) {
8927
9656
  let libOnly = 0;
8928
9657
  let browserBundle = 0;
8929
9658
  let reactNative = 0;
9659
+ let bun = 0;
9660
+ let deno = 0;
9661
+ let cloudflareWorkers = 0;
9662
+ let electron = 0;
8930
9663
  const installPlans = /* @__PURE__ */ new Map();
8931
9664
  for (const svc of services) {
8932
9665
  const installer = await pickInstaller(svc.dir);
@@ -8951,7 +9684,59 @@ async function applyInstallersOver(services, project, options = {}) {
8951
9684
  console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
8952
9685
  } else if (outcome.outcome === "react-native") {
8953
9686
  reactNative++;
8954
- console.log(`skipping ${svc.dir}: React Native target; browser-OTel support lands in a future release.`);
9687
+ const svcName = import_node_path46.default.basename(svc.dir);
9688
+ console.log(
9689
+ `neat: ${svc.dir} detected as React Native / Expo
9690
+ The installer doesn't cover this runtime deterministically.
9691
+ Configure your OTel binding to send spans to:
9692
+ http://localhost:4318/projects/${project}/v1/traces
9693
+ Set OTEL_SERVICE_NAME=${svcName}
9694
+ See docs/installer-scope.md \u2192 "Manual setup for out-of-scope runtimes"`
9695
+ );
9696
+ } else if (outcome.outcome === "bun") {
9697
+ bun++;
9698
+ const svcName = import_node_path46.default.basename(svc.dir);
9699
+ console.log(
9700
+ `neat: ${svc.dir} detected as Bun
9701
+ The installer doesn't cover this runtime deterministically.
9702
+ Configure your OTel binding to send spans to:
9703
+ http://localhost:4318/projects/${project}/v1/traces
9704
+ Set OTEL_SERVICE_NAME=${svcName}
9705
+ See docs/installer-scope.md \u2192 "Manual setup for out-of-scope runtimes"`
9706
+ );
9707
+ } else if (outcome.outcome === "deno") {
9708
+ deno++;
9709
+ const svcName = import_node_path46.default.basename(svc.dir);
9710
+ console.log(
9711
+ `neat: ${svc.dir} detected as Deno
9712
+ The installer doesn't cover this runtime deterministically.
9713
+ Configure your OTel binding to send spans to:
9714
+ http://localhost:4318/projects/${project}/v1/traces
9715
+ Set OTEL_SERVICE_NAME=${svcName}
9716
+ See docs/installer-scope.md \u2192 "Manual setup for out-of-scope runtimes"`
9717
+ );
9718
+ } else if (outcome.outcome === "cloudflare-workers") {
9719
+ cloudflareWorkers++;
9720
+ const svcName = import_node_path46.default.basename(svc.dir);
9721
+ console.log(
9722
+ `neat: ${svc.dir} detected as Cloudflare Workers
9723
+ The installer doesn't cover this runtime deterministically.
9724
+ Configure your OTel binding to send spans to:
9725
+ http://localhost:4318/projects/${project}/v1/traces
9726
+ Set OTEL_SERVICE_NAME=${svcName}
9727
+ See docs/installer-scope.md \u2192 "Manual setup for out-of-scope runtimes"`
9728
+ );
9729
+ } else if (outcome.outcome === "electron") {
9730
+ electron++;
9731
+ const svcName = import_node_path46.default.basename(svc.dir);
9732
+ console.log(
9733
+ `neat: ${svc.dir} detected as Electron
9734
+ The installer doesn't cover this runtime deterministically.
9735
+ Configure your OTel binding to send spans to:
9736
+ http://localhost:4318/projects/${project}/v1/traces
9737
+ Set OTEL_SERVICE_NAME=${svcName}
9738
+ See docs/installer-scope.md \u2192 "Manual setup for out-of-scope runtimes"`
9739
+ );
8955
9740
  }
8956
9741
  }
8957
9742
  const packageManagerInstalls = [];
@@ -8976,6 +9761,10 @@ async function applyInstallersOver(services, project, options = {}) {
8976
9761
  libOnly,
8977
9762
  browserBundle,
8978
9763
  reactNative,
9764
+ bun,
9765
+ deno,
9766
+ cloudflareWorkers,
9767
+ electron,
8979
9768
  packageManagerInstalls
8980
9769
  };
8981
9770
  }
@@ -9113,10 +9902,10 @@ function formatPortCollisionMessage(port) {
9113
9902
  ];
9114
9903
  }
9115
9904
  function spawnDaemonDetached() {
9116
- const here = import_node_path43.default.dirname(new URL(importMetaUrl).pathname);
9905
+ const here = import_node_path46.default.dirname(new URL(importMetaUrl).pathname);
9117
9906
  const candidates = [
9118
- import_node_path43.default.join(here, "neatd.cjs"),
9119
- import_node_path43.default.join(here, "neatd.js")
9907
+ import_node_path46.default.join(here, "neatd.cjs"),
9908
+ import_node_path46.default.join(here, "neatd.js")
9120
9909
  ];
9121
9910
  let entry2 = null;
9122
9911
  const fsSync = require("fs");
@@ -9174,7 +9963,7 @@ async function runOrchestrator(opts) {
9174
9963
  browser: "skipped"
9175
9964
  }
9176
9965
  };
9177
- const stat = await import_node_fs28.promises.stat(opts.scanPath).catch(() => null);
9966
+ const stat = await import_node_fs30.promises.stat(opts.scanPath).catch(() => null);
9178
9967
  if (!stat || !stat.isDirectory()) {
9179
9968
  console.error(`neat: ${opts.scanPath} is not a directory`);
9180
9969
  result.exitCode = 2;
@@ -9317,11 +10106,11 @@ function printSummary(result, graph, dashboardUrl) {
9317
10106
 
9318
10107
  // src/cli-verbs.ts
9319
10108
  init_cjs_shims();
9320
- var import_node_path44 = __toESM(require("path"), 1);
10109
+ var import_node_path47 = __toESM(require("path"), 1);
9321
10110
 
9322
10111
  // src/cli-client.ts
9323
10112
  init_cjs_shims();
9324
- var import_types25 = require("@neat.is/types");
10113
+ var import_types26 = require("@neat.is/types");
9325
10114
  var HttpError = class extends Error {
9326
10115
  constructor(status2, message, responseBody = "") {
9327
10116
  super(message);
@@ -9346,10 +10135,10 @@ function createHttpClient(baseUrl, bearerToken) {
9346
10135
  const root = baseUrl.replace(/\/$/, "");
9347
10136
  const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
9348
10137
  return {
9349
- async get(path46) {
10138
+ async get(path49) {
9350
10139
  let res;
9351
10140
  try {
9352
- res = await fetch(`${root}${path46}`, {
10141
+ res = await fetch(`${root}${path49}`, {
9353
10142
  headers: { ...authHeader }
9354
10143
  });
9355
10144
  } catch (err) {
@@ -9361,16 +10150,16 @@ function createHttpClient(baseUrl, bearerToken) {
9361
10150
  const body = await res.text().catch(() => "");
9362
10151
  throw new HttpError(
9363
10152
  res.status,
9364
- `${res.status} ${res.statusText} on GET ${path46}: ${body}`,
10153
+ `${res.status} ${res.statusText} on GET ${path49}: ${body}`,
9365
10154
  body
9366
10155
  );
9367
10156
  }
9368
10157
  return await res.json();
9369
10158
  },
9370
- async post(path46, body) {
10159
+ async post(path49, body) {
9371
10160
  let res;
9372
10161
  try {
9373
- res = await fetch(`${root}${path46}`, {
10162
+ res = await fetch(`${root}${path49}`, {
9374
10163
  method: "POST",
9375
10164
  headers: { "content-type": "application/json", ...authHeader },
9376
10165
  body: JSON.stringify(body)
@@ -9384,7 +10173,7 @@ function createHttpClient(baseUrl, bearerToken) {
9384
10173
  const text = await res.text().catch(() => "");
9385
10174
  throw new HttpError(
9386
10175
  res.status,
9387
- `${res.status} ${res.statusText} on POST ${path46}: ${text}`,
10176
+ `${res.status} ${res.statusText} on POST ${path49}: ${text}`,
9388
10177
  text
9389
10178
  );
9390
10179
  }
@@ -9398,12 +10187,12 @@ function projectPath(project, suffix) {
9398
10187
  }
9399
10188
  async function runRootCause(client, input) {
9400
10189
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
9401
- const path46 = projectPath(
10190
+ const path49 = projectPath(
9402
10191
  input.project,
9403
10192
  `/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
9404
10193
  );
9405
10194
  try {
9406
- const result = await client.get(path46);
10195
+ const result = await client.get(path49);
9407
10196
  const arrowPath = result.traversalPath.join(" \u2190 ");
9408
10197
  const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
9409
10198
  const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
@@ -9429,12 +10218,12 @@ async function runRootCause(client, input) {
9429
10218
  }
9430
10219
  async function runBlastRadius(client, input) {
9431
10220
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
9432
- const path46 = projectPath(
10221
+ const path49 = projectPath(
9433
10222
  input.project,
9434
10223
  `/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
9435
10224
  );
9436
10225
  try {
9437
- const result = await client.get(path46);
10226
+ const result = await client.get(path49);
9438
10227
  if (result.totalAffected === 0) {
9439
10228
  return {
9440
10229
  summary: `${result.origin} has no downstream dependencies. Nothing else would break if it failed.`
@@ -9463,17 +10252,17 @@ async function runBlastRadius(client, input) {
9463
10252
  }
9464
10253
  }
9465
10254
  function formatBlastEntry(n) {
9466
- const tag = n.edgeProvenance === import_types25.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
10255
+ const tag = n.edgeProvenance === import_types26.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
9467
10256
  return ` \u2022 ${n.nodeId} (distance ${n.distance}, ${n.edgeProvenance})${tag}`;
9468
10257
  }
9469
10258
  async function runDependencies(client, input) {
9470
10259
  const depth = input.depth ?? 3;
9471
- const path46 = projectPath(
10260
+ const path49 = projectPath(
9472
10261
  input.project,
9473
10262
  `/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
9474
10263
  );
9475
10264
  try {
9476
- const result = await client.get(path46);
10265
+ const result = await client.get(path49);
9477
10266
  if (result.total === 0) {
9478
10267
  return {
9479
10268
  summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
@@ -9509,9 +10298,9 @@ async function runObservedDependencies(client, input) {
9509
10298
  const edges = await client.get(
9510
10299
  projectPath(input.project, `/graph/edges/${encodeURIComponent(input.nodeId)}`)
9511
10300
  );
9512
- const observed = edges.outbound.filter((e) => e.provenance === import_types25.Provenance.OBSERVED);
10301
+ const observed = edges.outbound.filter((e) => e.provenance === import_types26.Provenance.OBSERVED);
9513
10302
  if (observed.length === 0) {
9514
- const hasExtracted = edges.outbound.some((e) => e.provenance === import_types25.Provenance.EXTRACTED);
10303
+ const hasExtracted = edges.outbound.some((e) => e.provenance === import_types26.Provenance.EXTRACTED);
9515
10304
  const note = hasExtracted ? " Static (EXTRACTED) dependencies exist but no runtime traffic has been seen \u2014 is OTel running?" : "";
9516
10305
  return { summary: `No OBSERVED dependencies for ${input.nodeId}.${note}` };
9517
10306
  }
@@ -9519,7 +10308,7 @@ async function runObservedDependencies(client, input) {
9519
10308
  return {
9520
10309
  summary: `${input.nodeId} has ${observed.length} runtime dependenc${observed.length === 1 ? "y" : "ies"} confirmed by OTel.`,
9521
10310
  block: blockLines.join("\n"),
9522
- provenance: import_types25.Provenance.OBSERVED
10311
+ provenance: import_types26.Provenance.OBSERVED
9523
10312
  };
9524
10313
  } catch (err) {
9525
10314
  if (err instanceof HttpError && err.status === 404) {
@@ -9554,9 +10343,9 @@ function formatDuration(ms) {
9554
10343
  return `${Math.round(h / 24)}d`;
9555
10344
  }
9556
10345
  async function runIncidents(client, input) {
9557
- const path46 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
10346
+ const path49 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
9558
10347
  try {
9559
- const body = await client.get(path46);
10348
+ const body = await client.get(path49);
9560
10349
  const events = body.events;
9561
10350
  if (events.length === 0) {
9562
10351
  return {
@@ -9573,7 +10362,7 @@ async function runIncidents(client, input) {
9573
10362
  return {
9574
10363
  summary: `${target} has ${body.total} recorded incident${body.total === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
9575
10364
  block: blockLines.join("\n"),
9576
- provenance: import_types25.Provenance.OBSERVED
10365
+ provenance: import_types26.Provenance.OBSERVED
9577
10366
  };
9578
10367
  } catch (err) {
9579
10368
  if (err instanceof HttpError && err.status === 404) {
@@ -9682,7 +10471,7 @@ async function runStaleEdges(client, input) {
9682
10471
  return {
9683
10472
  summary: `${events.length} stale-edge transition${events.length === 1 ? "" : "s"} recorded${input.edgeType ? ` for ${input.edgeType}` : ""}.`,
9684
10473
  block: blockLines.join("\n"),
9685
- provenance: import_types25.Provenance.STALE
10474
+ provenance: import_types26.Provenance.STALE
9686
10475
  };
9687
10476
  }
9688
10477
  async function runPolicies(client, input) {
@@ -9846,7 +10635,7 @@ async function resolveProjectEntry(opts) {
9846
10635
  const cwd = opts.cwd ?? process.cwd();
9847
10636
  const resolvedCwd = await normalizeProjectPath(cwd);
9848
10637
  for (const entry2 of entries) {
9849
- if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path44.default.sep}`)) {
10638
+ if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path47.default.sep}`)) {
9850
10639
  return entry2;
9851
10640
  }
9852
10641
  }
@@ -9996,7 +10785,7 @@ async function runSync(opts) {
9996
10785
  }
9997
10786
 
9998
10787
  // src/cli.ts
9999
- var import_types26 = require("@neat.is/types");
10788
+ var import_types27 = require("@neat.is/types");
10000
10789
  function usage() {
10001
10790
  console.log("usage: neat <command> [args] [--project <name>]");
10002
10791
  console.log("");
@@ -10206,14 +10995,14 @@ function assignFlag(out, field, value) {
10206
10995
  out[field] = value;
10207
10996
  }
10208
10997
  function readPackageVersion() {
10209
- const here = typeof __dirname !== "undefined" ? __dirname : import_node_path45.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
10998
+ const here = typeof __dirname !== "undefined" ? __dirname : import_node_path48.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
10210
10999
  const candidates = [
10211
- import_node_path45.default.resolve(here, "../package.json"),
10212
- import_node_path45.default.resolve(here, "../../package.json")
11000
+ import_node_path48.default.resolve(here, "../package.json"),
11001
+ import_node_path48.default.resolve(here, "../../package.json")
10213
11002
  ];
10214
11003
  for (const candidate of candidates) {
10215
11004
  try {
10216
- const raw = (0, import_node_fs29.readFileSync)(candidate, "utf8");
11005
+ const raw = (0, import_node_fs31.readFileSync)(candidate, "utf8");
10217
11006
  const parsed = JSON.parse(raw);
10218
11007
  if (parsed.name === "@neat.is/core" && typeof parsed.version === "string") {
10219
11008
  return parsed.version;
@@ -10277,7 +11066,7 @@ async function buildPatchSections(services, project) {
10277
11066
  }
10278
11067
  async function runInit(opts) {
10279
11068
  const written = [];
10280
- const stat = await import_node_fs29.promises.stat(opts.scanPath).catch(() => null);
11069
+ const stat = await import_node_fs31.promises.stat(opts.scanPath).catch(() => null);
10281
11070
  if (!stat || !stat.isDirectory()) {
10282
11071
  console.error(`neat init: ${opts.scanPath} is not a directory`);
10283
11072
  return { exitCode: 2, writtenFiles: written };
@@ -10286,13 +11075,13 @@ async function runInit(opts) {
10286
11075
  printDiscoveryReport(opts, services);
10287
11076
  const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
10288
11077
  const patch = renderPatch(sections);
10289
- const patchPath = import_node_path45.default.join(opts.scanPath, "neat.patch");
11078
+ const patchPath = import_node_path48.default.join(opts.scanPath, "neat.patch");
10290
11079
  if (opts.dryRun) {
10291
- await import_node_fs29.promises.writeFile(patchPath, patch, "utf8");
11080
+ await import_node_fs31.promises.writeFile(patchPath, patch, "utf8");
10292
11081
  written.push(patchPath);
10293
11082
  console.log(`dry-run: patch written to ${patchPath}`);
10294
- const gitignorePath = import_node_path45.default.join(opts.scanPath, ".gitignore");
10295
- const gitignoreExists = await import_node_fs29.promises.stat(gitignorePath).then(() => true).catch(() => false);
11083
+ const gitignorePath = import_node_path48.default.join(opts.scanPath, ".gitignore");
11084
+ const gitignoreExists = await import_node_fs31.promises.stat(gitignorePath).then(() => true).catch(() => false);
10296
11085
  const verb = gitignoreExists ? "append" : "create";
10297
11086
  console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
10298
11087
  console.log("rerun without --dry-run to register and snapshot.");
@@ -10303,9 +11092,9 @@ async function runInit(opts) {
10303
11092
  const graph = getGraph(graphKey);
10304
11093
  const projectPaths = pathsForProject(
10305
11094
  graphKey,
10306
- import_node_path45.default.join(opts.scanPath, "neat-out")
11095
+ import_node_path48.default.join(opts.scanPath, "neat-out")
10307
11096
  );
10308
- const errorsPath = import_node_path45.default.join(import_node_path45.default.dirname(opts.outPath), import_node_path45.default.basename(projectPaths.errorsPath));
11097
+ const errorsPath = import_node_path48.default.join(import_node_path48.default.dirname(opts.outPath), import_node_path48.default.basename(projectPaths.errorsPath));
10309
11098
  const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
10310
11099
  await saveGraphToDisk(graph, opts.outPath);
10311
11100
  written.push(opts.outPath);
@@ -10384,7 +11173,7 @@ async function runInit(opts) {
10384
11173
  console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
10385
11174
  }
10386
11175
  } else {
10387
- await import_node_fs29.promises.writeFile(patchPath, patch, "utf8");
11176
+ await import_node_fs31.promises.writeFile(patchPath, patch, "utf8");
10388
11177
  written.push(patchPath);
10389
11178
  }
10390
11179
  }
@@ -10424,9 +11213,9 @@ var CLAUDE_SKILL_CONFIG = {
10424
11213
  };
10425
11214
  function claudeConfigPath() {
10426
11215
  const override = process.env.NEAT_CLAUDE_CONFIG;
10427
- if (override && override.length > 0) return import_node_path45.default.resolve(override);
11216
+ if (override && override.length > 0) return import_node_path48.default.resolve(override);
10428
11217
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
10429
- return import_node_path45.default.join(home, ".claude.json");
11218
+ return import_node_path48.default.join(home, ".claude.json");
10430
11219
  }
10431
11220
  async function runSkill(opts) {
10432
11221
  const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
@@ -10438,7 +11227,7 @@ async function runSkill(opts) {
10438
11227
  const target = claudeConfigPath();
10439
11228
  let existing = {};
10440
11229
  try {
10441
- existing = JSON.parse(await import_node_fs29.promises.readFile(target, "utf8"));
11230
+ existing = JSON.parse(await import_node_fs31.promises.readFile(target, "utf8"));
10442
11231
  } catch (err) {
10443
11232
  if (err.code !== "ENOENT") {
10444
11233
  console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
@@ -10450,8 +11239,8 @@ async function runSkill(opts) {
10450
11239
  ...existing,
10451
11240
  mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
10452
11241
  };
10453
- await import_node_fs29.promises.mkdir(import_node_path45.default.dirname(target), { recursive: true });
10454
- await import_node_fs29.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
11242
+ await import_node_fs31.promises.mkdir(import_node_path48.default.dirname(target), { recursive: true });
11243
+ await import_node_fs31.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
10455
11244
  console.log(`neat skill: wrote mcpServers.neat to ${target}`);
10456
11245
  console.log("restart Claude Code to pick up the new MCP server.");
10457
11246
  return { exitCode: 0 };
@@ -10489,12 +11278,12 @@ async function main() {
10489
11278
  console.error("neat init: --apply and --dry-run are mutually exclusive");
10490
11279
  process.exit(2);
10491
11280
  }
10492
- const scanPath = import_node_path45.default.resolve(target);
11281
+ const scanPath = import_node_path48.default.resolve(target);
10493
11282
  const projectExplicit = parsed.project !== null;
10494
- const projectName = projectExplicit ? project : import_node_path45.default.basename(scanPath);
11283
+ const projectName = projectExplicit ? project : import_node_path48.default.basename(scanPath);
10495
11284
  const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
10496
- const fallback = pathsForProject(projectKey, import_node_path45.default.join(scanPath, "neat-out")).snapshotPath;
10497
- const outPath = import_node_path45.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
11285
+ const fallback = pathsForProject(projectKey, import_node_path48.default.join(scanPath, "neat-out")).snapshotPath;
11286
+ const outPath = import_node_path48.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
10498
11287
  const result = await runInit({
10499
11288
  scanPath,
10500
11289
  outPath,
@@ -10515,21 +11304,21 @@ async function main() {
10515
11304
  usage();
10516
11305
  process.exit(2);
10517
11306
  }
10518
- const scanPath = import_node_path45.default.resolve(target);
10519
- const stat = await import_node_fs29.promises.stat(scanPath).catch(() => null);
11307
+ const scanPath = import_node_path48.default.resolve(target);
11308
+ const stat = await import_node_fs31.promises.stat(scanPath).catch(() => null);
10520
11309
  if (!stat || !stat.isDirectory()) {
10521
11310
  console.error(`neat watch: ${scanPath} is not a directory`);
10522
11311
  process.exit(2);
10523
11312
  }
10524
- const projectPaths = pathsForProject(project, import_node_path45.default.join(scanPath, "neat-out"));
10525
- const outPath = import_node_path45.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
10526
- const errorsPath = import_node_path45.default.resolve(
10527
- process.env.NEAT_ERRORS_PATH ?? import_node_path45.default.join(import_node_path45.default.dirname(outPath), import_node_path45.default.basename(projectPaths.errorsPath))
11313
+ const projectPaths = pathsForProject(project, import_node_path48.default.join(scanPath, "neat-out"));
11314
+ const outPath = import_node_path48.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
11315
+ const errorsPath = import_node_path48.default.resolve(
11316
+ process.env.NEAT_ERRORS_PATH ?? import_node_path48.default.join(import_node_path48.default.dirname(outPath), import_node_path48.default.basename(projectPaths.errorsPath))
10528
11317
  );
10529
- const staleEventsPath = import_node_path45.default.resolve(
10530
- process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path45.default.join(import_node_path45.default.dirname(outPath), import_node_path45.default.basename(projectPaths.staleEventsPath))
11318
+ const staleEventsPath = import_node_path48.default.resolve(
11319
+ process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path48.default.join(import_node_path48.default.dirname(outPath), import_node_path48.default.basename(projectPaths.staleEventsPath))
10531
11320
  );
10532
- const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path45.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
11321
+ const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path48.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
10533
11322
  const handle = await startWatch(getGraph(project), {
10534
11323
  scanPath,
10535
11324
  outPath,
@@ -10675,11 +11464,11 @@ async function main() {
10675
11464
  process.exit(1);
10676
11465
  }
10677
11466
  async function tryOrchestrator(cmd, parsed) {
10678
- const scanPath = import_node_path45.default.resolve(cmd);
10679
- const stat = await import_node_fs29.promises.stat(scanPath).catch(() => null);
11467
+ const scanPath = import_node_path48.default.resolve(cmd);
11468
+ const stat = await import_node_fs31.promises.stat(scanPath).catch(() => null);
10680
11469
  if (!stat || !stat.isDirectory()) return null;
10681
11470
  const projectExplicit = parsed.project !== null;
10682
- const projectName = projectExplicit ? parsed.project : import_node_path45.default.basename(scanPath);
11471
+ const projectName = projectExplicit ? parsed.project : import_node_path48.default.basename(scanPath);
10683
11472
  const result = await runOrchestrator({
10684
11473
  scanPath,
10685
11474
  project: projectName,
@@ -10829,10 +11618,10 @@ async function runQueryVerb(cmd, parsed) {
10829
11618
  const parts = parsed.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
10830
11619
  const out = [];
10831
11620
  for (const p of parts) {
10832
- const r = import_types26.DivergenceTypeSchema.safeParse(p);
11621
+ const r = import_types27.DivergenceTypeSchema.safeParse(p);
10833
11622
  if (!r.success) {
10834
11623
  console.error(
10835
- `neat divergences: unknown --type "${p}". allowed: ${import_types26.DivergenceTypeSchema.options.join(", ")}`
11624
+ `neat divergences: unknown --type "${p}". allowed: ${import_types27.DivergenceTypeSchema.options.join(", ")}`
10836
11625
  );
10837
11626
  return 2;
10838
11627
  }