@neat.is/core 0.6.1 → 0.6.2-dev.20260723

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
@@ -61,8 +61,8 @@ function mountBearerAuth(app, opts) {
61
61
  ]);
62
62
  const publicRead = opts.publicRead === true;
63
63
  app.addHook("preHandler", (req, reply, done) => {
64
- const path63 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
- if (exactUnauthPaths.has(path63) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path63)) {
64
+ const path64 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
+ if (exactUnauthPaths.has(path64) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path64)) {
66
66
  done();
67
67
  return;
68
68
  }
@@ -194,8 +194,8 @@ function reshapeGrpcRequest(req) {
194
194
  };
195
195
  }
196
196
  function resolveProtoRoot() {
197
- const here = import_node_path50.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
198
- return import_node_path50.default.resolve(here, "..", "proto");
197
+ const here = import_node_path51.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
198
+ return import_node_path51.default.resolve(here, "..", "proto");
199
199
  }
200
200
  function loadTraceService() {
201
201
  const protoRoot = resolveProtoRoot();
@@ -263,13 +263,13 @@ async function startOtelGrpcReceiver(opts) {
263
263
  })
264
264
  };
265
265
  }
266
- var import_node_url2, import_node_path50, import_node_crypto2, grpc, protoLoader;
266
+ var import_node_url2, import_node_path51, import_node_crypto2, grpc, protoLoader;
267
267
  var init_otel_grpc = __esm({
268
268
  "src/otel-grpc.ts"() {
269
269
  "use strict";
270
270
  init_cjs_shims();
271
271
  import_node_url2 = require("url");
272
- import_node_path50 = __toESM(require("path"), 1);
272
+ import_node_path51 = __toESM(require("path"), 1);
273
273
  import_node_crypto2 = require("crypto");
274
274
  grpc = __toESM(require("@grpc/grpc-js"), 1);
275
275
  protoLoader = __toESM(require("@grpc/proto-loader"), 1);
@@ -347,6 +347,14 @@ function normalizeDbSystem(attrs) {
347
347
  if (typeof raw !== "string") return void 0;
348
348
  return raw === "mongoose" ? "mongodb" : raw;
349
349
  }
350
+ function tableFromSqlStatement(sql) {
351
+ if (typeof sql !== "string" || sql.length === 0) return null;
352
+ if (/\bjoin\b/i.test(sql)) return null;
353
+ const froms = sql.match(/\bfrom\b/gi);
354
+ if (froms && froms.length > 1) return null;
355
+ const m = /\b(?:from|into|update)\s+(?:"?[\w$]+"?\s*\.\s*)?"?([a-zA-Z_][\w$]*)"?/i.exec(sql);
356
+ return m ? m[1] : null;
357
+ }
350
358
  function messagingDestinationOf(attrs) {
351
359
  for (const key of ["messaging.destination.name", "messaging.destination"]) {
352
360
  const v = attrs[key];
@@ -367,8 +375,8 @@ function websocketChannelPathOf(attrs) {
367
375
  const v = attrs[key];
368
376
  if (typeof v === "string" && v.length > 0) {
369
377
  const q = v.indexOf("?");
370
- const path63 = q === -1 ? v : v.slice(0, q);
371
- if (path63.length > 0) return path63;
378
+ const path64 = q === -1 ? v : v.slice(0, q);
379
+ if (path64.length > 0) return path64;
372
380
  }
373
381
  }
374
382
  return void 0;
@@ -404,6 +412,7 @@ function parseOtlpRequest(body) {
404
412
  dbSystem: normalizeDbSystem(attrs),
405
413
  dbName: typeof attrs["db.name"] === "string" ? attrs["db.name"] : void 0,
406
414
  dbCollection: typeof attrs["db.collection.name"] === "string" ? attrs["db.collection.name"] : typeof attrs["db.mongodb.collection"] === "string" ? attrs["db.mongodb.collection"] : void 0,
415
+ dbTable: typeof attrs["db.statement"] === "string" ? tableFromSqlStatement(attrs["db.statement"]) ?? void 0 : void 0,
407
416
  messagingSystem: typeof attrs["messaging.system"] === "string" ? attrs["messaging.system"] : void 0,
408
417
  messagingDestination: messagingDestinationOf(attrs),
409
418
  graphqlOperationName: typeof attrs["graphql.operation.name"] === "string" && attrs["graphql.operation.name"].length > 0 ? attrs["graphql.operation.name"] : void 0,
@@ -423,10 +432,10 @@ function parseOtlpRequest(body) {
423
432
  return out;
424
433
  }
425
434
  function loadProtoRoot() {
426
- const here = import_node_path51.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
427
- const protoRoot = import_node_path51.default.resolve(here, "..", "proto");
435
+ const here = import_node_path52.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
436
+ const protoRoot = import_node_path52.default.resolve(here, "..", "proto");
428
437
  const root = new import_protobufjs.default.Root();
429
- root.resolvePath = (_origin, target) => import_node_path51.default.resolve(protoRoot, target);
438
+ root.resolvePath = (_origin, target) => import_node_path52.default.resolve(protoRoot, target);
430
439
  root.loadSync(
431
440
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
432
441
  { keepCase: true }
@@ -654,12 +663,12 @@ async function listenSteppingOtlp(app, requestedPort, host) {
654
663
  }
655
664
  }
656
665
  }
657
- var import_node_path51, import_node_url3, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody, OTLP_STEP_ATTEMPTS, OTLP_STEP_STRIDE;
666
+ var import_node_path52, import_node_url3, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody, OTLP_STEP_ATTEMPTS, OTLP_STEP_STRIDE;
658
667
  var init_otel = __esm({
659
668
  "src/otel.ts"() {
660
669
  "use strict";
661
670
  init_cjs_shims();
662
- import_node_path51 = __toESM(require("path"), 1);
671
+ import_node_path52 = __toESM(require("path"), 1);
663
672
  import_node_url3 = require("url");
664
673
  import_fastify2 = __toESM(require("fastify"), 1);
665
674
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -696,7 +705,7 @@ __export(cli_exports, {
696
705
  });
697
706
  module.exports = __toCommonJS(cli_exports);
698
707
  init_cjs_shims();
699
- var import_node_path62 = __toESM(require("path"), 1);
708
+ var import_node_path63 = __toESM(require("path"), 1);
700
709
  var import_node_fs41 = require("fs");
701
710
 
702
711
  // src/banner.ts
@@ -1262,19 +1271,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1262
1271
  function longestIncomingWalk(graph, start, maxDepth) {
1263
1272
  let best = { path: [start], edges: [] };
1264
1273
  const visited = /* @__PURE__ */ new Set([start]);
1265
- function step(node, path63, edges) {
1266
- if (path63.length > best.path.length) {
1267
- best = { path: [...path63], edges: [...edges] };
1274
+ function step(node, path64, edges) {
1275
+ if (path64.length > best.path.length) {
1276
+ best = { path: [...path64], edges: [...edges] };
1268
1277
  }
1269
- if (path63.length - 1 >= maxDepth) return;
1278
+ if (path64.length - 1 >= maxDepth) return;
1270
1279
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1271
1280
  for (const [srcId, edge] of incoming) {
1272
1281
  if (visited.has(srcId)) continue;
1273
1282
  visited.add(srcId);
1274
- path63.push(srcId);
1283
+ path64.push(srcId);
1275
1284
  edges.push(edge);
1276
- step(srcId, path63, edges);
1277
- path63.pop();
1285
+ step(srcId, path64, edges);
1286
+ path64.pop();
1278
1287
  edges.pop();
1279
1288
  visited.delete(srcId);
1280
1289
  }
@@ -1282,11 +1291,11 @@ function longestIncomingWalk(graph, start, maxDepth) {
1282
1291
  step(start, [start], []);
1283
1292
  return best;
1284
1293
  }
1285
- function databaseRootCauseShape(graph, origin, walk3) {
1294
+ function databaseRootCauseShape(graph, origin, walk4) {
1286
1295
  const targetDb = origin;
1287
1296
  const candidatePairs = compatPairs().filter((p) => p.engine === targetDb.engine);
1288
1297
  if (candidatePairs.length === 0) return null;
1289
- for (const id of walk3.path) {
1298
+ for (const id of walk4.path) {
1290
1299
  const owner = resolveOwningService(graph, id);
1291
1300
  if (!owner) continue;
1292
1301
  const { id: serviceId6, svc } = owner;
@@ -1313,8 +1322,8 @@ function databaseRootCauseShape(graph, origin, walk3) {
1313
1322
  }
1314
1323
  return null;
1315
1324
  }
1316
- function serviceRootCauseShape(graph, _origin, walk3) {
1317
- for (const id of walk3.path) {
1325
+ function serviceRootCauseShape(graph, _origin, walk4) {
1326
+ for (const id of walk4.path) {
1318
1327
  const owner = resolveOwningService(graph, id);
1319
1328
  if (!owner) continue;
1320
1329
  const { id: serviceId6, svc } = owner;
@@ -1350,10 +1359,10 @@ function serviceRootCauseShape(graph, _origin, walk3) {
1350
1359
  }
1351
1360
  return null;
1352
1361
  }
1353
- function fileRootCauseShape(graph, origin, walk3) {
1362
+ function fileRootCauseShape(graph, origin, walk4) {
1354
1363
  const owner = resolveOwningService(graph, origin.id);
1355
1364
  if (!owner) return null;
1356
- return serviceRootCauseShape(graph, owner.svc, walk3);
1365
+ return serviceRootCauseShape(graph, owner.svc, walk4);
1357
1366
  }
1358
1367
  var rootCauseShapes = {
1359
1368
  [import_types.NodeType.DatabaseNode]: databaseRootCauseShape,
@@ -1365,16 +1374,16 @@ function getRootCause(graph, errorNodeId, errorEvent, incidents) {
1365
1374
  const origin = graph.getNodeAttributes(errorNodeId);
1366
1375
  const shape = rootCauseShapes[origin.type];
1367
1376
  if (shape) {
1368
- const walk3 = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
1369
- const match = shape(graph, origin, walk3);
1377
+ const walk4 = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
1378
+ const match = shape(graph, origin, walk4);
1370
1379
  if (match) {
1371
1380
  const reason = errorEvent ? `${match.rootCauseReason} (observed error: ${errorEvent.errorMessage})` : match.rootCauseReason;
1372
1381
  return import_types.RootCauseResultSchema.parse({
1373
1382
  rootCauseNode: match.rootCauseNode,
1374
1383
  rootCauseReason: reason,
1375
- traversalPath: walk3.path,
1376
- edgeProvenances: walk3.edges.map((e) => e.provenance),
1377
- confidence: confidenceFromMix(walk3.edges),
1384
+ traversalPath: walk4.path,
1385
+ edgeProvenances: walk4.edges.map((e) => e.provenance),
1386
+ confidence: confidenceFromMix(walk4.edges),
1378
1387
  fixRecommendation: match.fixRecommendation
1379
1388
  });
1380
1389
  }
@@ -1395,8 +1404,8 @@ function localizeFromIncidents(nodeId, incidents, errorEvent) {
1395
1404
  if (relevant.length === 0) return null;
1396
1405
  const latest = [...relevant].sort((a, b) => b.timestamp.localeCompare(a.timestamp))[0];
1397
1406
  const attrs = latest.attributes ?? {};
1398
- const filepath = typeof attrs["code.filepath"] === "string" ? attrs["code.filepath"] : void 0;
1399
- const lineno = typeof attrs["code.lineno"] === "number" ? attrs["code.lineno"] : void 0;
1407
+ const filepath = codeFilepathOf(attrs);
1408
+ const lineno = codeLinenoOf(attrs);
1400
1409
  const route = typeof attrs["http.route"] === "string" ? attrs["http.route"] : void 0;
1401
1410
  const location = filepath ? `${filepath}${lineno !== void 0 ? `:${lineno}` : ""}` : void 0;
1402
1411
  const sameMode = relevant.filter((ev) => ev.errorMessage === latest.errorMessage);
@@ -1468,26 +1477,26 @@ function dominantFailingCall(graph, serviceId6, visited) {
1468
1477
  return best;
1469
1478
  }
1470
1479
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1471
- const path63 = [originServiceId];
1480
+ const path64 = [originServiceId];
1472
1481
  const edges = [];
1473
1482
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1474
1483
  let current = originServiceId;
1475
1484
  for (let depth = 0; depth < maxDepth; depth++) {
1476
1485
  const hop = dominantFailingCall(graph, current, visited);
1477
1486
  if (!hop) break;
1478
- path63.push(hop.nextService);
1487
+ path64.push(hop.nextService);
1479
1488
  edges.push(hop.edge);
1480
1489
  visited.add(hop.nextService);
1481
1490
  current = hop.nextService;
1482
1491
  }
1483
1492
  if (edges.length === 0) return null;
1484
- return { path: path63, edges, culprit: current };
1493
+ return { path: path64, edges, culprit: current };
1485
1494
  }
1486
1495
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1487
1496
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1488
1497
  if (!chain) return null;
1489
1498
  const culprit = chain.culprit;
1490
- const path63 = [...chain.path];
1499
+ const path64 = [...chain.path];
1491
1500
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1492
1501
  const baseConfidence = confidenceFromMix(chain.edges);
1493
1502
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1495,14 +1504,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1495
1504
  if (loc) {
1496
1505
  let rootCauseNode = culprit;
1497
1506
  if (loc.fileNode) {
1498
- path63.push(loc.fileNode);
1507
+ path64.push(loc.fileNode);
1499
1508
  edgeProvenances.push(import_types.Provenance.OBSERVED);
1500
1509
  rootCauseNode = loc.fileNode;
1501
1510
  }
1502
1511
  return import_types.RootCauseResultSchema.parse({
1503
1512
  rootCauseNode,
1504
1513
  rootCauseReason: loc.rootCauseReason,
1505
- traversalPath: path63,
1514
+ traversalPath: path64,
1506
1515
  edgeProvenances,
1507
1516
  confidence,
1508
1517
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1514,7 +1523,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1514
1523
  return import_types.RootCauseResultSchema.parse({
1515
1524
  rootCauseNode: culprit,
1516
1525
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1517
- traversalPath: path63,
1526
+ traversalPath: path64,
1518
1527
  edgeProvenances,
1519
1528
  confidence,
1520
1529
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -2272,9 +2281,30 @@ function isLoopbackHost(host) {
2272
2281
  const h = host.toLowerCase();
2273
2282
  return h === "localhost" || h === "ip6-localhost" || h === "::1" || h === "[::1]" || /^127(?:\.\d{1,3}){3}$/.test(h);
2274
2283
  }
2284
+ var CODE_FILE_PATH_ATTR = "code.file.path";
2275
2285
  var CODE_FILEPATH_ATTR = "code.filepath";
2286
+ var CODE_LINE_NUMBER_ATTR = "code.line.number";
2276
2287
  var CODE_LINENO_ATTR = "code.lineno";
2288
+ var CODE_FUNCTION_NAME_ATTR = "code.function.name";
2277
2289
  var CODE_FUNCTION_ATTR = "code.function";
2290
+ function codeFilepathOf(attrs) {
2291
+ const stable = attrs[CODE_FILE_PATH_ATTR];
2292
+ if (typeof stable === "string" && stable.length > 0) return stable;
2293
+ const prior = attrs[CODE_FILEPATH_ATTR];
2294
+ return typeof prior === "string" && prior.length > 0 ? prior : void 0;
2295
+ }
2296
+ function codeLinenoOf(attrs) {
2297
+ const stable = attrs[CODE_LINE_NUMBER_ATTR];
2298
+ if (typeof stable === "number" && Number.isFinite(stable)) return stable;
2299
+ const prior = attrs[CODE_LINENO_ATTR];
2300
+ return typeof prior === "number" && Number.isFinite(prior) ? prior : void 0;
2301
+ }
2302
+ function codeFunctionOf(attrs) {
2303
+ const stable = attrs[CODE_FUNCTION_NAME_ATTR];
2304
+ if (typeof stable === "string" && stable.length > 0) return stable;
2305
+ const prior = attrs[CODE_FUNCTION_ATTR];
2306
+ return typeof prior === "string" && prior.length > 0 ? prior : void 0;
2307
+ }
2278
2308
  function toPosix(p) {
2279
2309
  return p.split("\\").join("/");
2280
2310
  }
@@ -2352,10 +2382,9 @@ function resolveDistToSrc(absFilepath, line) {
2352
2382
  }
2353
2383
  }
2354
2384
  function callSiteFromSpan(span, serviceNode, scanPath) {
2355
- const filepath = span.attributes[CODE_FILEPATH_ATTR];
2356
- if (typeof filepath !== "string" || filepath.length === 0) return null;
2357
- const linenoRaw = span.attributes[CODE_LINENO_ATTR];
2358
- let line = typeof linenoRaw === "number" && Number.isFinite(linenoRaw) ? linenoRaw : void 0;
2385
+ const filepath = codeFilepathOf(span.attributes);
2386
+ if (filepath === void 0) return null;
2387
+ let line = codeLinenoOf(span.attributes);
2359
2388
  const abs = toPosix(filepath).replace(/^file:\/\//, "");
2360
2389
  const resolved = resolveDistToSrc(abs, line);
2361
2390
  let effectivePath = filepath;
@@ -2370,8 +2399,7 @@ function callSiteFromSpan(span, serviceNode, scanPath) {
2370
2399
  if (!resolved && abs.endsWith(".js") && relPath.startsWith("dist/") && serviceNode?.name) {
2371
2400
  warnNoSourceMaps(serviceNode.name);
2372
2401
  }
2373
- const fnRaw = span.attributes[CODE_FUNCTION_ATTR];
2374
- const fn = typeof fnRaw === "string" && fnRaw.length > 0 ? fnRaw : void 0;
2402
+ const fn = codeFunctionOf(span.attributes);
2375
2403
  return {
2376
2404
  relPath,
2377
2405
  ...line !== void 0 ? { line } : {},
@@ -2953,6 +2981,18 @@ async function handleSpan(ctx, span) {
2953
2981
  callSiteEvidence
2954
2982
  );
2955
2983
  }
2984
+ if (span.dbTable) {
2985
+ const tableId = ensureInfraNode(ctx.graph, "sql-table", span.dbTable, "self");
2986
+ upsertObservedEdge(
2987
+ ctx.graph,
2988
+ import_types3.EdgeType.CALLS,
2989
+ observedSource(),
2990
+ tableId,
2991
+ ts,
2992
+ isError,
2993
+ callSiteEvidence
2994
+ );
2995
+ }
2956
2996
  }
2957
2997
  } else if (span.messagingSystem && span.messagingDestination && spanMintsMessagingEdge(span.kind)) {
2958
2998
  const targetId = ensureMessagingDestinationNode(
@@ -4132,14 +4172,14 @@ function buildServiceHostIndex(services) {
4132
4172
  }
4133
4173
  async function walkSourceFiles(dir) {
4134
4174
  const out = [];
4135
- async function walk3(current) {
4175
+ async function walk4(current) {
4136
4176
  const entries = await import_node_fs11.promises.readdir(current, { withFileTypes: true }).catch(() => []);
4137
4177
  for (const entry2 of entries) {
4138
4178
  const full = import_node_path11.default.join(current, entry2.name);
4139
4179
  if (entry2.isDirectory()) {
4140
4180
  if (IGNORED_DIRS.has(entry2.name)) continue;
4141
4181
  if (await isPythonVenvDir(full)) continue;
4142
- await walk3(full);
4182
+ await walk4(full);
4143
4183
  } else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path11.default.extname(entry2.name)) && // Skip NEAT's own generated `otel-init.*` bootstrap — extracting it
4144
4184
  // would attribute our instrumentation imports to the user's service.
4145
4185
  !isNeatAuthoredSourceFile(entry2.name)) {
@@ -4147,7 +4187,7 @@ async function walkSourceFiles(dir) {
4147
4187
  }
4148
4188
  }
4149
4189
  }
4150
- await walk3(dir);
4190
+ await walk4(dir);
4151
4191
  return out;
4152
4192
  }
4153
4193
  async function loadSourceFiles(dir) {
@@ -5297,20 +5337,20 @@ var import_node_path22 = __toESM(require("path"), 1);
5297
5337
  var import_types10 = require("@neat.is/types");
5298
5338
  async function walkConfigFiles(dir) {
5299
5339
  const out = [];
5300
- async function walk3(current) {
5340
+ async function walk4(current) {
5301
5341
  const entries = await import_node_fs15.promises.readdir(current, { withFileTypes: true });
5302
5342
  for (const entry2 of entries) {
5303
5343
  const full = import_node_path22.default.join(current, entry2.name);
5304
5344
  if (entry2.isDirectory()) {
5305
5345
  if (IGNORED_DIRS.has(entry2.name)) continue;
5306
5346
  if (await isPythonVenvDir(full)) continue;
5307
- await walk3(full);
5347
+ await walk4(full);
5308
5348
  } else if (entry2.isFile() && isConfigFile(entry2.name).match) {
5309
5349
  out.push(full);
5310
5350
  }
5311
5351
  }
5312
5352
  }
5313
- await walk3(dir);
5353
+ await walk4(dir);
5314
5354
  return out;
5315
5355
  }
5316
5356
  async function addConfigNodes(graph, services, scanPath) {
@@ -5837,20 +5877,20 @@ function grpcMethodsFromProto(content, fqPackage) {
5837
5877
  }
5838
5878
  async function walkProtoFiles(dir) {
5839
5879
  const out = [];
5840
- async function walk3(current) {
5880
+ async function walk4(current) {
5841
5881
  const entries = await import_node_fs16.promises.readdir(current, { withFileTypes: true }).catch(() => []);
5842
5882
  for (const entry2 of entries) {
5843
5883
  const full = import_node_path24.default.join(current, entry2.name);
5844
5884
  if (entry2.isDirectory()) {
5845
5885
  if (IGNORED_DIRS.has(entry2.name)) continue;
5846
5886
  if (await isPythonVenvDir(full)) continue;
5847
- await walk3(full);
5887
+ await walk4(full);
5848
5888
  } else if (entry2.isFile() && import_node_path24.default.extname(entry2.name) === PROTO_EXTENSION) {
5849
5889
  out.push(full);
5850
5890
  }
5851
5891
  }
5852
5892
  }
5853
- await walk3(dir);
5893
+ await walk4(dir);
5854
5894
  return out;
5855
5895
  }
5856
5896
  async function addGrpcMethods(graph, services) {
@@ -5918,7 +5958,7 @@ async function addGrpcMethods(graph, services) {
5918
5958
 
5919
5959
  // src/extract/calls/index.ts
5920
5960
  init_cjs_shims();
5921
- var import_types21 = require("@neat.is/types");
5961
+ var import_types22 = require("@neat.is/types");
5922
5962
 
5923
5963
  // src/extract/calls/http.ts
5924
5964
  init_cjs_shims();
@@ -6927,15 +6967,130 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
6927
6967
  return out;
6928
6968
  }
6929
6969
 
6970
+ // src/extract/calls/sqlalchemy.ts
6971
+ init_cjs_shims();
6972
+ var import_node_path33 = __toESM(require("path"), 1);
6973
+ var import_tree_sitter5 = __toESM(require("tree-sitter"), 1);
6974
+ var import_tree_sitter_python4 = __toESM(require("tree-sitter-python"), 1);
6975
+ var import_types21 = require("@neat.is/types");
6976
+ var SQLALCHEMY_IMPORT_RE = /(?:from|import)\s+(?:flask_sqlalchemy|sqlalchemy)\b/;
6977
+ var PARSE_CHUNK5 = 16384;
6978
+ function makePyParser4() {
6979
+ const p = new import_tree_sitter5.default();
6980
+ p.setLanguage(import_tree_sitter_python4.default);
6981
+ return p;
6982
+ }
6983
+ function parseSource5(parser, source) {
6984
+ return parser.parse(
6985
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK5)
6986
+ );
6987
+ }
6988
+ function flaskSqlalchemyTableName(className) {
6989
+ return className.replace(/((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))/g, "_$1").toLowerCase().replace(/^_+/, "");
6990
+ }
6991
+ function namedChildren(node) {
6992
+ const out = [];
6993
+ for (let i = 0; i < node.namedChildCount; i++) {
6994
+ const c = node.namedChild(i);
6995
+ if (c) out.push(c);
6996
+ }
6997
+ return out;
6998
+ }
6999
+ function pyStaticStringText2(node) {
7000
+ if (node.type !== "string") return null;
7001
+ for (const child of namedChildren(node)) {
7002
+ if (child.type === "interpolation") return null;
7003
+ if (child.type === "string_content") return child.text;
7004
+ }
7005
+ return "";
7006
+ }
7007
+ function explicitTablename(body) {
7008
+ for (const stmt of namedChildren(body)) {
7009
+ if (stmt.type !== "expression_statement") continue;
7010
+ const assign = stmt.namedChild(0);
7011
+ if (assign?.type !== "assignment") continue;
7012
+ if (assign.childForFieldName("left")?.text !== "__tablename__") continue;
7013
+ const right = assign.childForFieldName("right");
7014
+ if (right?.type === "string") {
7015
+ const s = pyStaticStringText2(right);
7016
+ return s ? { name: s } : "computed";
7017
+ }
7018
+ return "computed";
7019
+ }
7020
+ return null;
7021
+ }
7022
+ function extendsFlaskModel(cls) {
7023
+ const supers = cls.childForFieldName("superclasses");
7024
+ if (!supers) return false;
7025
+ for (const a of namedChildren(supers)) {
7026
+ const t = a.text;
7027
+ if (t === "db.Model" || t === "Model" || t.endsWith(".Model")) return true;
7028
+ }
7029
+ return false;
7030
+ }
7031
+ function walk3(node, visit) {
7032
+ visit(node);
7033
+ for (const c of namedChildren(node)) walk3(c, visit);
7034
+ }
7035
+ function sqlalchemyEndpointsFromFile(file, serviceDir) {
7036
+ if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
7037
+ const tree = parseSource5(makePyParser4(), file.content);
7038
+ const out = [];
7039
+ const seen = /* @__PURE__ */ new Set();
7040
+ const push = (name, line) => {
7041
+ if (seen.has(name)) return;
7042
+ seen.add(name);
7043
+ out.push({
7044
+ infraId: (0, import_types21.infraId)("sql-table", name),
7045
+ name,
7046
+ kind: "sql-table",
7047
+ edgeType: "CALLS",
7048
+ confidenceKind: "verified-call-site",
7049
+ evidence: {
7050
+ file: import_node_path33.default.relative(serviceDir, file.path),
7051
+ line,
7052
+ snippet: snippet(file.content, line)
7053
+ }
7054
+ });
7055
+ };
7056
+ walk3(tree.rootNode, (node) => {
7057
+ if (node.type === "class_definition") {
7058
+ const body = node.childForFieldName("body");
7059
+ const nameNode = node.childForFieldName("name");
7060
+ if (!body || !nameNode) return;
7061
+ const line = node.startPosition.row + 1;
7062
+ const explicit = explicitTablename(body);
7063
+ if (explicit === "computed") return;
7064
+ if (explicit) {
7065
+ push(explicit.name, line);
7066
+ return;
7067
+ }
7068
+ if (extendsFlaskModel(node)) push(flaskSqlalchemyTableName(nameNode.text), line);
7069
+ return;
7070
+ }
7071
+ if (node.type === "call") {
7072
+ const fn = node.childForFieldName("function");
7073
+ const fnText = fn?.text;
7074
+ if (fnText !== "Table" && fnText !== "sa.Table" && fnText !== "sqlalchemy.Table") return;
7075
+ const first = node.childForFieldName("arguments")?.namedChild(0);
7076
+ if (first?.type === "string") {
7077
+ const s = pyStaticStringText2(first);
7078
+ if (s) push(s, node.startPosition.row + 1);
7079
+ }
7080
+ }
7081
+ });
7082
+ return out;
7083
+ }
7084
+
6930
7085
  // src/extract/calls/index.ts
6931
7086
  function edgeTypeFromEndpoint(ep) {
6932
7087
  switch (ep.edgeType) {
6933
7088
  case "PUBLISHES_TO":
6934
- return import_types21.EdgeType.PUBLISHES_TO;
7089
+ return import_types22.EdgeType.PUBLISHES_TO;
6935
7090
  case "CONSUMES_FROM":
6936
- return import_types21.EdgeType.CONSUMES_FROM;
7091
+ return import_types22.EdgeType.CONSUMES_FROM;
6937
7092
  default:
6938
- return import_types21.EdgeType.CALLS;
7093
+ return import_types22.EdgeType.CALLS;
6939
7094
  }
6940
7095
  }
6941
7096
  function isAwsKind(kind) {
@@ -6959,6 +7114,7 @@ async function addExternalEndpointEdges(graph, services) {
6959
7114
  endpoints.push(...grpcEndpointsFromFile(maskedFile, service.dir));
6960
7115
  endpoints.push(...supabaseEndpointsFromFile(maskedFile, service.dir));
6961
7116
  endpoints.push(...mongooseEndpointsFromFile(maskedFile, service.dir));
7117
+ endpoints.push(...sqlalchemyEndpointsFromFile(maskedFile, service.dir));
6962
7118
  }
6963
7119
  endpoints.push(...await mongooseCrossFileEndpoints(maskedFiles, service.dir));
6964
7120
  if (endpoints.length === 0) continue;
@@ -6967,7 +7123,7 @@ async function addExternalEndpointEdges(graph, services) {
6967
7123
  if (!graph.hasNode(ep.infraId)) {
6968
7124
  const node = {
6969
7125
  id: ep.infraId,
6970
- type: import_types21.NodeType.InfraNode,
7126
+ type: import_types22.NodeType.InfraNode,
6971
7127
  name: ep.name,
6972
7128
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
6973
7129
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -6979,7 +7135,7 @@ async function addExternalEndpointEdges(graph, services) {
6979
7135
  nodesAdded++;
6980
7136
  }
6981
7137
  const edgeType = edgeTypeFromEndpoint(ep);
6982
- const confidence = (0, import_types21.confidenceForExtracted)(ep.confidenceKind);
7138
+ const confidence = (0, import_types22.confidenceForExtracted)(ep.confidenceKind);
6983
7139
  const relFile = toPosix2(ep.evidence.file);
6984
7140
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
6985
7141
  graph,
@@ -6989,7 +7145,7 @@ async function addExternalEndpointEdges(graph, services) {
6989
7145
  );
6990
7146
  nodesAdded += n;
6991
7147
  edgesAdded += e;
6992
- if (!(0, import_types21.passesExtractedFloor)(confidence)) {
7148
+ if (!(0, import_types22.passesExtractedFloor)(confidence)) {
6993
7149
  noteExtractedDropped({
6994
7150
  source: fileNodeId,
6995
7151
  target: ep.infraId,
@@ -7009,7 +7165,7 @@ async function addExternalEndpointEdges(graph, services) {
7009
7165
  source: fileNodeId,
7010
7166
  target: ep.infraId,
7011
7167
  type: edgeType,
7012
- provenance: import_types21.Provenance.EXTRACTED,
7168
+ provenance: import_types22.Provenance.EXTRACTED,
7013
7169
  confidence,
7014
7170
  evidence: ep.evidence
7015
7171
  };
@@ -7035,16 +7191,16 @@ init_cjs_shims();
7035
7191
 
7036
7192
  // src/extract/infra/docker-compose.ts
7037
7193
  init_cjs_shims();
7038
- var import_node_path33 = __toESM(require("path"), 1);
7039
- var import_types23 = require("@neat.is/types");
7194
+ var import_node_path34 = __toESM(require("path"), 1);
7195
+ var import_types24 = require("@neat.is/types");
7040
7196
 
7041
7197
  // src/extract/infra/shared.ts
7042
7198
  init_cjs_shims();
7043
- var import_types22 = require("@neat.is/types");
7199
+ var import_types23 = require("@neat.is/types");
7044
7200
  function makeInfraNode(kind, name, provider = "self", extras) {
7045
7201
  return {
7046
- id: (0, import_types22.infraId)(kind, name),
7047
- type: import_types22.NodeType.InfraNode,
7202
+ id: (0, import_types23.infraId)(kind, name),
7203
+ type: import_types23.NodeType.InfraNode,
7048
7204
  name,
7049
7205
  provider,
7050
7206
  kind,
@@ -7088,8 +7244,8 @@ function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provide
7088
7244
  source: anchorId,
7089
7245
  target: node.id,
7090
7246
  type: edgeType,
7091
- provenance: import_types22.Provenance.EXTRACTED,
7092
- confidence: (0, import_types22.confidenceForExtracted)("structural"),
7247
+ provenance: import_types23.Provenance.EXTRACTED,
7248
+ confidence: (0, import_types23.confidenceForExtracted)("structural"),
7093
7249
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
7094
7250
  };
7095
7251
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7106,7 +7262,7 @@ function dependsOnList(value) {
7106
7262
  }
7107
7263
  function serviceNameToServiceNode(name, services) {
7108
7264
  for (const s of services) {
7109
- if (s.node.name === name || import_node_path33.default.basename(s.dir) === name) return s.node.id;
7265
+ if (s.node.name === name || import_node_path34.default.basename(s.dir) === name) return s.node.id;
7110
7266
  }
7111
7267
  return null;
7112
7268
  }
@@ -7115,7 +7271,7 @@ async function addComposeInfra(graph, scanPath, services) {
7115
7271
  let edgesAdded = 0;
7116
7272
  let composePath = null;
7117
7273
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
7118
- const abs = import_node_path33.default.join(scanPath, name);
7274
+ const abs = import_node_path34.default.join(scanPath, name);
7119
7275
  if (await exists(abs)) {
7120
7276
  composePath = abs;
7121
7277
  break;
@@ -7128,13 +7284,13 @@ async function addComposeInfra(graph, scanPath, services) {
7128
7284
  } catch (err) {
7129
7285
  recordExtractionError(
7130
7286
  "infra docker-compose",
7131
- import_node_path33.default.relative(scanPath, composePath),
7287
+ import_node_path34.default.relative(scanPath, composePath),
7132
7288
  err
7133
7289
  );
7134
7290
  return { nodesAdded, edgesAdded };
7135
7291
  }
7136
7292
  if (!compose?.services) return { nodesAdded, edgesAdded };
7137
- const evidenceFile = import_node_path33.default.relative(scanPath, composePath).split(import_node_path33.default.sep).join("/");
7293
+ const evidenceFile = import_node_path34.default.relative(scanPath, composePath).split(import_node_path34.default.sep).join("/");
7138
7294
  const composeNameToNodeId = /* @__PURE__ */ new Map();
7139
7295
  for (const [composeName, svc] of Object.entries(compose.services)) {
7140
7296
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -7156,15 +7312,15 @@ async function addComposeInfra(graph, scanPath, services) {
7156
7312
  for (const dep of dependsOnList(svc.depends_on)) {
7157
7313
  const targetId = composeNameToNodeId.get(dep);
7158
7314
  if (!targetId) continue;
7159
- const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types23.EdgeType.DEPENDS_ON);
7315
+ const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types24.EdgeType.DEPENDS_ON);
7160
7316
  if (graph.hasEdge(edgeId)) continue;
7161
7317
  const edge = {
7162
7318
  id: edgeId,
7163
7319
  source: sourceId,
7164
7320
  target: targetId,
7165
- type: import_types23.EdgeType.DEPENDS_ON,
7166
- provenance: import_types23.Provenance.EXTRACTED,
7167
- confidence: (0, import_types23.confidenceForExtracted)("structural"),
7321
+ type: import_types24.EdgeType.DEPENDS_ON,
7322
+ provenance: import_types24.Provenance.EXTRACTED,
7323
+ confidence: (0, import_types24.confidenceForExtracted)("structural"),
7168
7324
  evidence: { file: evidenceFile }
7169
7325
  };
7170
7326
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7176,9 +7332,9 @@ async function addComposeInfra(graph, scanPath, services) {
7176
7332
 
7177
7333
  // src/extract/infra/dockerfile.ts
7178
7334
  init_cjs_shims();
7179
- var import_node_path34 = __toESM(require("path"), 1);
7335
+ var import_node_path35 = __toESM(require("path"), 1);
7180
7336
  var import_node_fs17 = require("fs");
7181
- var import_types24 = require("@neat.is/types");
7337
+ var import_types25 = require("@neat.is/types");
7182
7338
  function readDockerfile(content) {
7183
7339
  let image = null;
7184
7340
  const ports = [];
@@ -7207,7 +7363,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
7207
7363
  let nodesAdded = 0;
7208
7364
  let edgesAdded = 0;
7209
7365
  for (const service of services) {
7210
- const dockerfilePath = import_node_path34.default.join(service.dir, "Dockerfile");
7366
+ const dockerfilePath = import_node_path35.default.join(service.dir, "Dockerfile");
7211
7367
  if (!await exists(dockerfilePath)) continue;
7212
7368
  let content;
7213
7369
  try {
@@ -7215,7 +7371,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
7215
7371
  } catch (err) {
7216
7372
  recordExtractionError(
7217
7373
  "infra dockerfile",
7218
- import_node_path34.default.relative(scanPath, dockerfilePath),
7374
+ import_node_path35.default.relative(scanPath, dockerfilePath),
7219
7375
  err
7220
7376
  );
7221
7377
  continue;
@@ -7227,8 +7383,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
7227
7383
  graph.addNode(node.id, node);
7228
7384
  nodesAdded++;
7229
7385
  }
7230
- const relDockerfile = toPosix2(import_node_path34.default.relative(service.dir, dockerfilePath));
7231
- const evidenceFile = toPosix2(import_node_path34.default.relative(scanPath, dockerfilePath));
7386
+ const relDockerfile = toPosix2(import_node_path35.default.relative(service.dir, dockerfilePath));
7387
+ const evidenceFile = toPosix2(import_node_path35.default.relative(scanPath, dockerfilePath));
7232
7388
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
7233
7389
  graph,
7234
7390
  service.pkg.name,
@@ -7237,15 +7393,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
7237
7393
  );
7238
7394
  nodesAdded += fn;
7239
7395
  edgesAdded += fe;
7240
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types24.EdgeType.RUNS_ON);
7396
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types25.EdgeType.RUNS_ON);
7241
7397
  if (!graph.hasEdge(edgeId)) {
7242
7398
  const edge = {
7243
7399
  id: edgeId,
7244
7400
  source: fileNodeId,
7245
7401
  target: node.id,
7246
- type: import_types24.EdgeType.RUNS_ON,
7247
- provenance: import_types24.Provenance.EXTRACTED,
7248
- confidence: (0, import_types24.confidenceForExtracted)("structural"),
7402
+ type: import_types25.EdgeType.RUNS_ON,
7403
+ provenance: import_types25.Provenance.EXTRACTED,
7404
+ confidence: (0, import_types25.confidenceForExtracted)("structural"),
7249
7405
  evidence: {
7250
7406
  file: evidenceFile,
7251
7407
  ...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
@@ -7260,15 +7416,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
7260
7416
  graph.addNode(portNode.id, portNode);
7261
7417
  nodesAdded++;
7262
7418
  }
7263
- const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types24.EdgeType.CONNECTS_TO);
7419
+ const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types25.EdgeType.CONNECTS_TO);
7264
7420
  if (graph.hasEdge(portEdgeId)) continue;
7265
7421
  const portEdge = {
7266
7422
  id: portEdgeId,
7267
7423
  source: fileNodeId,
7268
7424
  target: portNode.id,
7269
- type: import_types24.EdgeType.CONNECTS_TO,
7270
- provenance: import_types24.Provenance.EXTRACTED,
7271
- confidence: (0, import_types24.confidenceForExtracted)("structural"),
7425
+ type: import_types25.EdgeType.CONNECTS_TO,
7426
+ provenance: import_types25.Provenance.EXTRACTED,
7427
+ confidence: (0, import_types25.confidenceForExtracted)("structural"),
7272
7428
  evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
7273
7429
  };
7274
7430
  graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
@@ -7281,8 +7437,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
7281
7437
  // src/extract/infra/terraform.ts
7282
7438
  init_cjs_shims();
7283
7439
  var import_node_fs18 = require("fs");
7284
- var import_node_path35 = __toESM(require("path"), 1);
7285
- var import_types25 = require("@neat.is/types");
7440
+ var import_node_path36 = __toESM(require("path"), 1);
7441
+ var import_types26 = require("@neat.is/types");
7286
7442
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
7287
7443
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
7288
7444
  async function walkTfFiles(start, depth = 0, max = 5) {
@@ -7292,11 +7448,11 @@ async function walkTfFiles(start, depth = 0, max = 5) {
7292
7448
  for (const entry2 of entries) {
7293
7449
  if (entry2.isDirectory()) {
7294
7450
  if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
7295
- const child = import_node_path35.default.join(start, entry2.name);
7451
+ const child = import_node_path36.default.join(start, entry2.name);
7296
7452
  if (await isPythonVenvDir(child)) continue;
7297
7453
  out.push(...await walkTfFiles(child, depth + 1, max));
7298
7454
  } else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
7299
- out.push(import_node_path35.default.join(start, entry2.name));
7455
+ out.push(import_node_path36.default.join(start, entry2.name));
7300
7456
  }
7301
7457
  }
7302
7458
  return out;
@@ -7328,7 +7484,7 @@ async function addTerraformResources(graph, scanPath) {
7328
7484
  const files = await walkTfFiles(scanPath);
7329
7485
  for (const file of files) {
7330
7486
  const content = await import_node_fs18.promises.readFile(file, "utf8");
7331
- const evidenceFile = toPosix2(import_node_path35.default.relative(scanPath, file));
7487
+ const evidenceFile = toPosix2(import_node_path36.default.relative(scanPath, file));
7332
7488
  const resources = [];
7333
7489
  const byKey = /* @__PURE__ */ new Map();
7334
7490
  RESOURCE_RE.lastIndex = 0;
@@ -7363,16 +7519,16 @@ async function addTerraformResources(graph, scanPath) {
7363
7519
  if (!target) continue;
7364
7520
  if (seen.has(target.nodeId)) continue;
7365
7521
  seen.add(target.nodeId);
7366
- const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types25.EdgeType.DEPENDS_ON);
7522
+ const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types26.EdgeType.DEPENDS_ON);
7367
7523
  if (graph.hasEdge(edgeId)) continue;
7368
7524
  const line = lineAt2(content, resource.bodyOffset + ref.index);
7369
7525
  const edge = {
7370
7526
  id: edgeId,
7371
7527
  source: resource.nodeId,
7372
7528
  target: target.nodeId,
7373
- type: import_types25.EdgeType.DEPENDS_ON,
7374
- provenance: import_types25.Provenance.EXTRACTED,
7375
- confidence: (0, import_types25.confidenceForExtracted)("structural"),
7529
+ type: import_types26.EdgeType.DEPENDS_ON,
7530
+ provenance: import_types26.Provenance.EXTRACTED,
7531
+ confidence: (0, import_types26.confidenceForExtracted)("structural"),
7376
7532
  evidence: { file: evidenceFile, line, snippet: key }
7377
7533
  };
7378
7534
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7386,7 +7542,7 @@ async function addTerraformResources(graph, scanPath) {
7386
7542
  // src/extract/infra/k8s.ts
7387
7543
  init_cjs_shims();
7388
7544
  var import_node_fs19 = require("fs");
7389
- var import_node_path36 = __toESM(require("path"), 1);
7545
+ var import_node_path37 = __toESM(require("path"), 1);
7390
7546
  var import_yaml3 = require("yaml");
7391
7547
  var K8S_KIND_TO_INFRA_KIND = {
7392
7548
  Service: "k8s-service",
@@ -7404,11 +7560,11 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
7404
7560
  for (const entry2 of entries) {
7405
7561
  if (entry2.isDirectory()) {
7406
7562
  if (IGNORED_DIRS.has(entry2.name)) continue;
7407
- const child = import_node_path36.default.join(start, entry2.name);
7563
+ const child = import_node_path37.default.join(start, entry2.name);
7408
7564
  if (await isPythonVenvDir(child)) continue;
7409
7565
  out.push(...await walkYamlFiles2(child, depth + 1, max));
7410
- } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path36.default.extname(entry2.name))) {
7411
- out.push(import_node_path36.default.join(start, entry2.name));
7566
+ } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path37.default.extname(entry2.name))) {
7567
+ out.push(import_node_path37.default.join(start, entry2.name));
7412
7568
  }
7413
7569
  }
7414
7570
  return out;
@@ -7442,13 +7598,13 @@ async function addK8sResources(graph, scanPath) {
7442
7598
  // src/extract/infra/cloudflare.ts
7443
7599
  init_cjs_shims();
7444
7600
  var import_node_fs20 = require("fs");
7445
- var import_node_path37 = __toESM(require("path"), 1);
7601
+ var import_node_path38 = __toESM(require("path"), 1);
7446
7602
  var import_smol_toml2 = require("smol-toml");
7447
- var import_types26 = require("@neat.is/types");
7603
+ var import_types27 = require("@neat.is/types");
7448
7604
  var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
7449
7605
  async function readWranglerConfig(dir) {
7450
7606
  for (const filename of WRANGLER_FILENAMES) {
7451
- const abs = import_node_path37.default.join(dir, filename);
7607
+ const abs = import_node_path38.default.join(dir, filename);
7452
7608
  if (!await exists(abs)) continue;
7453
7609
  const raw = await import_node_fs20.promises.readFile(abs, "utf8");
7454
7610
  const config = filename === "wrangler.toml" ? (0, import_smol_toml2.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -7492,8 +7648,8 @@ function addResourceEdge(graph, anchorId, edgeType, kind, name, evidenceFile, li
7492
7648
  source: anchorId,
7493
7649
  target: node.id,
7494
7650
  type: edgeType,
7495
- provenance: import_types26.Provenance.EXTRACTED,
7496
- confidence: (0, import_types26.confidenceForExtracted)("structural"),
7651
+ provenance: import_types27.Provenance.EXTRACTED,
7652
+ confidence: (0, import_types27.confidenceForExtracted)("structural"),
7497
7653
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
7498
7654
  };
7499
7655
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7511,11 +7667,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7511
7667
  try {
7512
7668
  read = await readWranglerConfig(service.dir);
7513
7669
  } catch (err) {
7514
- recordExtractionError("infra cloudflare", import_node_path37.default.relative(scanPath, service.dir), err);
7670
+ recordExtractionError("infra cloudflare", import_node_path38.default.relative(scanPath, service.dir), err);
7515
7671
  continue;
7516
7672
  }
7517
7673
  if (!read || !read.config.name) continue;
7518
- const evidenceFile = toPosix2(import_node_path37.default.relative(scanPath, import_node_path37.default.join(service.dir, read.relFile)));
7674
+ const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, read.relFile)));
7519
7675
  discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
7520
7676
  }
7521
7677
  for (const worker of discovered) {
@@ -7527,7 +7683,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7527
7683
  }
7528
7684
  let anchorId = service.node.id;
7529
7685
  if (config.main) {
7530
- const entryRelPath = toPosix2(import_node_path37.default.normalize(config.main));
7686
+ const entryRelPath = toPosix2(import_node_path38.default.normalize(config.main));
7531
7687
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
7532
7688
  graph,
7533
7689
  service.pkg.name,
@@ -7554,15 +7710,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7554
7710
  nodesAdded++;
7555
7711
  }
7556
7712
  if (runtimeNode.id !== anchorId) {
7557
- const runsOnId = (0, import_types4.extractedEdgeId)(anchorId, runtimeNode.id, import_types26.EdgeType.RUNS_ON);
7713
+ const runsOnId = (0, import_types4.extractedEdgeId)(anchorId, runtimeNode.id, import_types27.EdgeType.RUNS_ON);
7558
7714
  if (!graph.hasEdge(runsOnId)) {
7559
7715
  const edge = {
7560
7716
  id: runsOnId,
7561
7717
  source: anchorId,
7562
7718
  target: runtimeNode.id,
7563
- type: import_types26.EdgeType.RUNS_ON,
7564
- provenance: import_types26.Provenance.EXTRACTED,
7565
- confidence: (0, import_types26.confidenceForExtracted)("structural"),
7719
+ type: import_types27.EdgeType.RUNS_ON,
7720
+ provenance: import_types27.Provenance.EXTRACTED,
7721
+ confidence: (0, import_types27.confidenceForExtracted)("structural"),
7566
7722
  evidence: {
7567
7723
  file: evidenceFile,
7568
7724
  ...config.compatibility_date ? { snippet: `compatibility_date = ${config.compatibility_date}`.slice(0, 120) } : {}
@@ -7576,7 +7732,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7576
7732
  const result = addResourceEdge(
7577
7733
  graph,
7578
7734
  anchorId,
7579
- import_types26.EdgeType.CONNECTS_TO,
7735
+ import_types27.EdgeType.CONNECTS_TO,
7580
7736
  "cloudflare-route",
7581
7737
  route,
7582
7738
  evidenceFile,
@@ -7600,7 +7756,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7600
7756
  const result = addResourceEdge(
7601
7757
  graph,
7602
7758
  anchorId,
7603
- import_types26.EdgeType.DEPENDS_ON,
7759
+ import_types27.EdgeType.DEPENDS_ON,
7604
7760
  group.kind,
7605
7761
  name,
7606
7762
  evidenceFile,
@@ -7614,7 +7770,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7614
7770
  const result = addResourceEdge(
7615
7771
  graph,
7616
7772
  anchorId,
7617
- import_types26.EdgeType.DEPENDS_ON,
7773
+ import_types27.EdgeType.DEPENDS_ON,
7618
7774
  "cloudflare-cron",
7619
7775
  cron,
7620
7776
  evidenceFile,
@@ -7627,7 +7783,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7627
7783
  const result = addResourceEdge(
7628
7784
  graph,
7629
7785
  anchorId,
7630
- import_types26.EdgeType.DEPENDS_ON,
7786
+ import_types27.EdgeType.DEPENDS_ON,
7631
7787
  "cloudflare-env-var",
7632
7788
  varName,
7633
7789
  evidenceFile,
@@ -7640,15 +7796,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7640
7796
  if (!svc.service) continue;
7641
7797
  const target = workerIndex.get(svc.service);
7642
7798
  if (target && target.anchorId !== anchorId) {
7643
- const edgeId = (0, import_types4.extractedEdgeId)(anchorId, target.anchorId, import_types26.EdgeType.CALLS);
7799
+ const edgeId = (0, import_types4.extractedEdgeId)(anchorId, target.anchorId, import_types27.EdgeType.CALLS);
7644
7800
  if (!graph.hasEdge(edgeId)) {
7645
7801
  const edge = {
7646
7802
  id: edgeId,
7647
7803
  source: anchorId,
7648
7804
  target: target.anchorId,
7649
- type: import_types26.EdgeType.CALLS,
7650
- provenance: import_types26.Provenance.EXTRACTED,
7651
- confidence: (0, import_types26.confidenceForExtracted)("structural"),
7805
+ type: import_types27.EdgeType.CALLS,
7806
+ provenance: import_types27.Provenance.EXTRACTED,
7807
+ confidence: (0, import_types27.confidenceForExtracted)("structural"),
7652
7808
  evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
7653
7809
  };
7654
7810
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7659,7 +7815,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7659
7815
  const result = addResourceEdge(
7660
7816
  graph,
7661
7817
  anchorId,
7662
- import_types26.EdgeType.DEPENDS_ON,
7818
+ import_types27.EdgeType.DEPENDS_ON,
7663
7819
  "cloudflare-service-binding",
7664
7820
  svc.service,
7665
7821
  evidenceFile,
@@ -7675,12 +7831,12 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7675
7831
  // src/extract/infra/vercel.ts
7676
7832
  init_cjs_shims();
7677
7833
  var import_node_fs21 = require("fs");
7678
- var import_node_path38 = __toESM(require("path"), 1);
7679
- var import_types27 = require("@neat.is/types");
7834
+ var import_node_path39 = __toESM(require("path"), 1);
7835
+ var import_types28 = require("@neat.is/types");
7680
7836
  var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
7681
7837
  async function readVercelConfig(dir) {
7682
7838
  for (const filename of VERCEL_CONFIG_FILENAMES) {
7683
- const abs = import_node_path38.default.join(dir, filename);
7839
+ const abs = import_node_path39.default.join(dir, filename);
7684
7840
  if (!await exists(abs)) continue;
7685
7841
  const raw = await import_node_fs21.promises.readFile(abs, "utf8");
7686
7842
  const config = JSON.parse(maskCommentsInSource(raw));
@@ -7689,7 +7845,7 @@ async function readVercelConfig(dir) {
7689
7845
  return null;
7690
7846
  }
7691
7847
  async function readLinkedProjectName(dir) {
7692
- const abs = import_node_path38.default.join(dir, ".vercel", "project.json");
7848
+ const abs = import_node_path39.default.join(dir, ".vercel", "project.json");
7693
7849
  if (!await exists(abs)) return void 0;
7694
7850
  const parsed = JSON.parse(await import_node_fs21.promises.readFile(abs, "utf8"));
7695
7851
  return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
@@ -7707,7 +7863,7 @@ async function addVercelServices(graph, services, scanPath) {
7707
7863
  read = await readVercelConfig(service.dir);
7708
7864
  projectName = await readLinkedProjectName(service.dir);
7709
7865
  } catch (err) {
7710
- recordExtractionError("infra vercel", import_node_path38.default.relative(scanPath, service.dir), err);
7866
+ recordExtractionError("infra vercel", import_node_path39.default.relative(scanPath, service.dir), err);
7711
7867
  continue;
7712
7868
  }
7713
7869
  if (!read && !projectName) continue;
@@ -7723,7 +7879,7 @@ async function addVercelServices(graph, services, scanPath) {
7723
7879
  const anchorId = service.node.id;
7724
7880
  if (!read) continue;
7725
7881
  const { config, relFile, raw } = read;
7726
- const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, relFile)));
7882
+ const evidenceFile = toPosix2(import_node_path39.default.relative(scanPath, import_node_path39.default.join(service.dir, relFile)));
7727
7883
  const add = (edgeType, kind, name) => {
7728
7884
  if (!name) return;
7729
7885
  const result = emitPlatformResourceEdge(
@@ -7739,12 +7895,12 @@ async function addVercelServices(graph, services, scanPath) {
7739
7895
  nodesAdded += result.nodesAdded;
7740
7896
  edgesAdded += result.edgesAdded;
7741
7897
  };
7742
- add(import_types27.EdgeType.RUNS_ON, "vercel", "vercel");
7743
- for (const cron of config.crons ?? []) add(import_types27.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
7744
- for (const varName of Object.keys(config.env ?? {})) add(import_types27.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7745
- for (const varName of Object.keys(config.build?.env ?? {})) add(import_types27.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7898
+ add(import_types28.EdgeType.RUNS_ON, "vercel", "vercel");
7899
+ for (const cron of config.crons ?? []) add(import_types28.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
7900
+ for (const varName of Object.keys(config.env ?? {})) add(import_types28.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7901
+ for (const varName of Object.keys(config.build?.env ?? {})) add(import_types28.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7746
7902
  for (const route of [...config.rewrites ?? [], ...config.redirects ?? [], ...config.routes ?? []]) {
7747
- add(import_types27.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
7903
+ add(import_types28.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
7748
7904
  }
7749
7905
  }
7750
7906
  return { nodesAdded, edgesAdded };
@@ -7753,13 +7909,13 @@ async function addVercelServices(graph, services, scanPath) {
7753
7909
  // src/extract/infra/railway.ts
7754
7910
  init_cjs_shims();
7755
7911
  var import_node_fs22 = require("fs");
7756
- var import_node_path39 = __toESM(require("path"), 1);
7912
+ var import_node_path40 = __toESM(require("path"), 1);
7757
7913
  var import_smol_toml3 = require("smol-toml");
7758
- var import_types28 = require("@neat.is/types");
7914
+ var import_types29 = require("@neat.is/types");
7759
7915
  var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
7760
7916
  async function readRailwayConfig(dir) {
7761
7917
  for (const filename of RAILWAY_FILENAMES) {
7762
- const abs = import_node_path39.default.join(dir, filename);
7918
+ const abs = import_node_path40.default.join(dir, filename);
7763
7919
  if (!await exists(abs)) continue;
7764
7920
  const raw = await import_node_fs22.promises.readFile(abs, "utf8");
7765
7921
  const config = filename === "railway.toml" ? (0, import_smol_toml3.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -7775,7 +7931,7 @@ async function addRailwayServices(graph, services, scanPath) {
7775
7931
  try {
7776
7932
  read = await readRailwayConfig(service.dir);
7777
7933
  } catch (err) {
7778
- recordExtractionError("infra railway", import_node_path39.default.relative(scanPath, service.dir), err);
7934
+ recordExtractionError("infra railway", import_node_path40.default.relative(scanPath, service.dir), err);
7779
7935
  continue;
7780
7936
  }
7781
7937
  if (!read) continue;
@@ -7785,7 +7941,7 @@ async function addRailwayServices(graph, services, scanPath) {
7785
7941
  }
7786
7942
  const anchorId = service.node.id;
7787
7943
  const { config, relFile, raw } = read;
7788
- const evidenceFile = toPosix2(import_node_path39.default.relative(scanPath, import_node_path39.default.join(service.dir, relFile)));
7944
+ const evidenceFile = toPosix2(import_node_path40.default.relative(scanPath, import_node_path40.default.join(service.dir, relFile)));
7789
7945
  const add = (edgeType, kind, name) => {
7790
7946
  if (!name) return;
7791
7947
  const result = emitPlatformResourceEdge(
@@ -7801,9 +7957,9 @@ async function addRailwayServices(graph, services, scanPath) {
7801
7957
  nodesAdded += result.nodesAdded;
7802
7958
  edgesAdded += result.edgesAdded;
7803
7959
  };
7804
- add(import_types28.EdgeType.RUNS_ON, "railway", "railway");
7805
- add(import_types28.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
7806
- add(import_types28.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
7960
+ add(import_types29.EdgeType.RUNS_ON, "railway", "railway");
7961
+ add(import_types29.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
7962
+ add(import_types29.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
7807
7963
  }
7808
7964
  return { nodesAdded, edgesAdded };
7809
7965
  }
@@ -7811,12 +7967,12 @@ async function addRailwayServices(graph, services, scanPath) {
7811
7967
  // src/extract/infra/supabase.ts
7812
7968
  init_cjs_shims();
7813
7969
  var import_node_fs23 = require("fs");
7814
- var import_node_path40 = __toESM(require("path"), 1);
7970
+ var import_node_path41 = __toESM(require("path"), 1);
7815
7971
  var import_smol_toml4 = require("smol-toml");
7816
- var import_types29 = require("@neat.is/types");
7972
+ var import_types30 = require("@neat.is/types");
7817
7973
  async function readSupabaseConfig(dir) {
7818
- const relFile = import_node_path40.default.join("supabase", "config.toml");
7819
- const abs = import_node_path40.default.join(dir, relFile);
7974
+ const relFile = import_node_path41.default.join("supabase", "config.toml");
7975
+ const abs = import_node_path41.default.join(dir, relFile);
7820
7976
  if (!await exists(abs)) return null;
7821
7977
  const raw = await import_node_fs23.promises.readFile(abs, "utf8");
7822
7978
  const config = (0, import_smol_toml4.parse)(raw);
@@ -7830,7 +7986,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
7830
7986
  try {
7831
7987
  read = await readSupabaseConfig(service.dir);
7832
7988
  } catch (err) {
7833
- recordExtractionError("infra supabase", import_node_path40.default.relative(scanPath, service.dir), err);
7989
+ recordExtractionError("infra supabase", import_node_path41.default.relative(scanPath, service.dir), err);
7834
7990
  continue;
7835
7991
  }
7836
7992
  if (!read) continue;
@@ -7845,7 +8001,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
7845
8001
  });
7846
8002
  }
7847
8003
  const anchorId = service.node.id;
7848
- const evidenceFile = toPosix2(import_node_path40.default.relative(scanPath, import_node_path40.default.join(service.dir, relFile)));
8004
+ const evidenceFile = toPosix2(import_node_path41.default.relative(scanPath, import_node_path41.default.join(service.dir, relFile)));
7849
8005
  const add = (edgeType, kind, name) => {
7850
8006
  if (!name) return;
7851
8007
  const result = emitPlatformResourceEdge(
@@ -7861,10 +8017,10 @@ async function addSupabaseProjects(graph, services, scanPath) {
7861
8017
  nodesAdded += result.nodesAdded;
7862
8018
  edgesAdded += result.edgesAdded;
7863
8019
  };
7864
- add(import_types29.EdgeType.RUNS_ON, "supabase", "supabase");
7865
- for (const fn of Object.keys(config.functions ?? {})) add(import_types29.EdgeType.DEPENDS_ON, "supabase-function", fn);
7866
- if (config.storage) add(import_types29.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
7867
- if (config.auth) add(import_types29.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
8020
+ add(import_types30.EdgeType.RUNS_ON, "supabase", "supabase");
8021
+ for (const fn of Object.keys(config.functions ?? {})) add(import_types30.EdgeType.DEPENDS_ON, "supabase-function", fn);
8022
+ if (config.storage) add(import_types30.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
8023
+ if (config.auth) add(import_types30.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
7868
8024
  }
7869
8025
  return { nodesAdded, edgesAdded };
7870
8026
  }
@@ -7886,17 +8042,17 @@ async function addInfra(graph, scanPath, services) {
7886
8042
  }
7887
8043
 
7888
8044
  // src/extract/index.ts
7889
- var import_node_path42 = __toESM(require("path"), 1);
8045
+ var import_node_path43 = __toESM(require("path"), 1);
7890
8046
 
7891
8047
  // src/extract/retire.ts
7892
8048
  init_cjs_shims();
7893
8049
  var import_node_fs24 = require("fs");
7894
- var import_node_path41 = __toESM(require("path"), 1);
7895
- var import_types30 = require("@neat.is/types");
8050
+ var import_node_path42 = __toESM(require("path"), 1);
8051
+ var import_types31 = require("@neat.is/types");
7896
8052
  function dropOrphanedFileNodes(graph) {
7897
8053
  const orphans = [];
7898
8054
  graph.forEachNode((id, attrs) => {
7899
- if (attrs.type !== import_types30.NodeType.FileNode) return;
8055
+ if (attrs.type !== import_types31.NodeType.FileNode) return;
7900
8056
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
7901
8057
  orphans.push(id);
7902
8058
  }
@@ -7909,7 +8065,7 @@ function retireEdgesByFile(graph, file) {
7909
8065
  const toDrop = [];
7910
8066
  graph.forEachEdge((id, attrs) => {
7911
8067
  const edge = attrs;
7912
- if (edge.provenance !== import_types30.Provenance.EXTRACTED) return;
8068
+ if (edge.provenance !== import_types31.Provenance.EXTRACTED) return;
7913
8069
  if (!edge.evidence?.file) return;
7914
8070
  if (edge.evidence.file === normalized) toDrop.push(id);
7915
8071
  });
@@ -7922,14 +8078,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
7922
8078
  const bases = [scanPath, ...serviceDirs];
7923
8079
  graph.forEachEdge((id, attrs) => {
7924
8080
  const edge = attrs;
7925
- if (edge.provenance !== import_types30.Provenance.EXTRACTED) return;
8081
+ if (edge.provenance !== import_types31.Provenance.EXTRACTED) return;
7926
8082
  const evidenceFile = edge.evidence?.file;
7927
8083
  if (!evidenceFile) return;
7928
- if (import_node_path41.default.isAbsolute(evidenceFile)) {
8084
+ if (import_node_path42.default.isAbsolute(evidenceFile)) {
7929
8085
  if (!(0, import_node_fs24.existsSync)(evidenceFile)) toDrop.push(id);
7930
8086
  return;
7931
8087
  }
7932
- const found = bases.some((base) => (0, import_node_fs24.existsSync)(import_node_path41.default.join(base, evidenceFile)));
8088
+ const found = bases.some((base) => (0, import_node_fs24.existsSync)(import_node_path42.default.join(base, evidenceFile)));
7933
8089
  if (!found) toDrop.push(id);
7934
8090
  });
7935
8091
  for (const id of toDrop) graph.dropEdge(id);
@@ -7971,7 +8127,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7971
8127
  }
7972
8128
  const droppedEntries = drainDroppedExtracted();
7973
8129
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
7974
- const rejectedPath = import_node_path42.default.join(import_node_path42.default.dirname(opts.errorsPath), "rejected.ndjson");
8130
+ const rejectedPath = import_node_path43.default.join(import_node_path43.default.dirname(opts.errorsPath), "rejected.ndjson");
7975
8131
  try {
7976
8132
  await writeRejectedExtracted(droppedEntries, rejectedPath);
7977
8133
  } catch (err) {
@@ -8005,39 +8161,39 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
8005
8161
 
8006
8162
  // src/divergences.ts
8007
8163
  init_cjs_shims();
8008
- var import_types31 = require("@neat.is/types");
8164
+ var import_types32 = require("@neat.is/types");
8009
8165
  function bucketKey(source, target, type) {
8010
8166
  return `${type}|${source}|${target}`;
8011
8167
  }
8012
8168
  function bucketSourceFor(graph, edge) {
8013
- if (edge.type !== import_types31.EdgeType.CONNECTS_TO) return edge.source;
8014
- const parsed = (0, import_types31.parseFileId)(edge.source);
8169
+ if (edge.type !== import_types32.EdgeType.CONNECTS_TO) return edge.source;
8170
+ const parsed = (0, import_types32.parseFileId)(edge.source);
8015
8171
  if (!parsed || !graph.hasNode(edge.target)) return edge.source;
8016
8172
  const target = graph.getNodeAttributes(edge.target);
8017
- if (target.type !== import_types31.NodeType.DatabaseNode) return edge.source;
8018
- return (0, import_types31.serviceId)(parsed.service);
8173
+ if (target.type !== import_types32.NodeType.DatabaseNode) return edge.source;
8174
+ return (0, import_types32.serviceId)(parsed.service);
8019
8175
  }
8020
8176
  function bucketEdges(graph) {
8021
8177
  const buckets2 = /* @__PURE__ */ new Map();
8022
8178
  graph.forEachEdge((id, attrs) => {
8023
8179
  const e = attrs;
8024
- const parsed = (0, import_types31.parseEdgeId)(id);
8180
+ const parsed = (0, import_types32.parseEdgeId)(id);
8025
8181
  const provenance = parsed?.provenance ?? e.provenance;
8026
8182
  const source = bucketSourceFor(graph, e);
8027
8183
  const key = bucketKey(source, e.target, e.type);
8028
8184
  const cur = buckets2.get(key) ?? { source, target: e.target, type: e.type };
8029
8185
  switch (provenance) {
8030
- case import_types31.Provenance.EXTRACTED:
8186
+ case import_types32.Provenance.EXTRACTED:
8031
8187
  cur.extracted = e;
8032
8188
  break;
8033
- case import_types31.Provenance.OBSERVED:
8189
+ case import_types32.Provenance.OBSERVED:
8034
8190
  cur.observed = e;
8035
8191
  break;
8036
- case import_types31.Provenance.INFERRED:
8192
+ case import_types32.Provenance.INFERRED:
8037
8193
  cur.inferred = e;
8038
8194
  break;
8039
8195
  default:
8040
- if (e.provenance === import_types31.Provenance.STALE) cur.stale = e;
8196
+ if (e.provenance === import_types32.Provenance.STALE) cur.stale = e;
8041
8197
  }
8042
8198
  buckets2.set(key, cur);
8043
8199
  });
@@ -8046,12 +8202,12 @@ function bucketEdges(graph) {
8046
8202
  function nodeIsFrontier(graph, nodeId) {
8047
8203
  if (!graph.hasNode(nodeId)) return false;
8048
8204
  const attrs = graph.getNodeAttributes(nodeId);
8049
- return attrs.type === import_types31.NodeType.FrontierNode;
8205
+ return attrs.type === import_types32.NodeType.FrontierNode;
8050
8206
  }
8051
8207
  function nodeIsWebsocketChannel(graph, nodeId) {
8052
8208
  if (!graph.hasNode(nodeId)) return false;
8053
8209
  const attrs = graph.getNodeAttributes(nodeId);
8054
- return attrs.type === import_types31.NodeType.WebSocketChannelNode;
8210
+ return attrs.type === import_types32.NodeType.WebSocketChannelNode;
8055
8211
  }
8056
8212
  function clampConfidence(n) {
8057
8213
  if (!Number.isFinite(n)) return 0;
@@ -8071,14 +8227,14 @@ function gradedConfidence(edge) {
8071
8227
  return clampConfidence(confidenceForEdge(edge));
8072
8228
  }
8073
8229
  var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
8074
- import_types31.EdgeType.CALLS,
8075
- import_types31.EdgeType.CONNECTS_TO,
8076
- import_types31.EdgeType.PUBLISHES_TO,
8077
- import_types31.EdgeType.CONSUMES_FROM
8230
+ import_types32.EdgeType.CALLS,
8231
+ import_types32.EdgeType.CONNECTS_TO,
8232
+ import_types32.EdgeType.PUBLISHES_TO,
8233
+ import_types32.EdgeType.CONSUMES_FROM
8078
8234
  ]);
8079
8235
  function detectMissingDivergences(graph, bucket) {
8080
8236
  const out = [];
8081
- if (bucket.type === import_types31.EdgeType.CONTAINS) return out;
8237
+ if (bucket.type === import_types32.EdgeType.CONTAINS) return out;
8082
8238
  if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
8083
8239
  if (!nodeIsFrontier(graph, bucket.target)) {
8084
8240
  out.push({
@@ -8119,7 +8275,7 @@ function declaredHostFor(svc) {
8119
8275
  function hasExtractedConfiguredBy(graph, svcId) {
8120
8276
  for (const edgeId of graph.outboundEdges(svcId)) {
8121
8277
  const e = graph.getEdgeAttributes(edgeId);
8122
- if (e.type === import_types31.EdgeType.CONFIGURED_BY && e.provenance === import_types31.Provenance.EXTRACTED) {
8278
+ if (e.type === import_types32.EdgeType.CONFIGURED_BY && e.provenance === import_types32.Provenance.EXTRACTED) {
8123
8279
  return true;
8124
8280
  }
8125
8281
  }
@@ -8132,10 +8288,10 @@ function detectHostMismatch(graph, svcId, svc) {
8132
8288
  const out = [];
8133
8289
  for (const edgeId of graph.outboundEdges(svcId)) {
8134
8290
  const edge = graph.getEdgeAttributes(edgeId);
8135
- if (edge.type !== import_types31.EdgeType.CONNECTS_TO) continue;
8136
- if (edge.provenance !== import_types31.Provenance.OBSERVED) continue;
8291
+ if (edge.type !== import_types32.EdgeType.CONNECTS_TO) continue;
8292
+ if (edge.provenance !== import_types32.Provenance.OBSERVED) continue;
8137
8293
  const target = graph.getNodeAttributes(edge.target);
8138
- if (target.type !== import_types31.NodeType.DatabaseNode) continue;
8294
+ if (target.type !== import_types32.NodeType.DatabaseNode) continue;
8139
8295
  const observedHost = target.host?.trim();
8140
8296
  if (!observedHost) continue;
8141
8297
  if (observedHost === declaredHost) continue;
@@ -8157,10 +8313,10 @@ function detectCompatDivergences(graph, svcId, svc) {
8157
8313
  const deps = svc.dependencies ?? {};
8158
8314
  for (const edgeId of graph.outboundEdges(svcId)) {
8159
8315
  const edge = graph.getEdgeAttributes(edgeId);
8160
- if (edge.type !== import_types31.EdgeType.CONNECTS_TO) continue;
8161
- if (edge.provenance !== import_types31.Provenance.OBSERVED) continue;
8316
+ if (edge.type !== import_types32.EdgeType.CONNECTS_TO) continue;
8317
+ if (edge.provenance !== import_types32.Provenance.OBSERVED) continue;
8162
8318
  const target = graph.getNodeAttributes(edge.target);
8163
- if (target.type !== import_types31.NodeType.DatabaseNode) continue;
8319
+ if (target.type !== import_types32.NodeType.DatabaseNode) continue;
8164
8320
  for (const pair of compatPairs()) {
8165
8321
  if (pair.engine !== target.engine) continue;
8166
8322
  const declared = deps[pair.driver];
@@ -8219,7 +8375,7 @@ function suppressHostMismatchHalves(all) {
8219
8375
  for (const d of all) {
8220
8376
  if (d.type !== "host-mismatch") continue;
8221
8377
  observedHalf.add(`${d.source}->${d.target}`);
8222
- declaredHalf.add((0, import_types31.databaseId)(d.extractedHost));
8378
+ declaredHalf.add((0, import_types32.databaseId)(d.extractedHost));
8223
8379
  }
8224
8380
  if (observedHalf.size === 0) return all;
8225
8381
  return all.filter((d) => {
@@ -8238,7 +8394,7 @@ function computeDivergences(graph, opts = {}) {
8238
8394
  }
8239
8395
  graph.forEachNode((nodeId, attrs) => {
8240
8396
  const n = attrs;
8241
- if (n.type !== import_types31.NodeType.ServiceNode) return;
8397
+ if (n.type !== import_types32.NodeType.ServiceNode) return;
8242
8398
  const svc = n;
8243
8399
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
8244
8400
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
@@ -8272,7 +8428,7 @@ function computeDivergences(graph, opts = {}) {
8272
8428
  if (a.source !== b.source) return a.source.localeCompare(b.source);
8273
8429
  return a.target.localeCompare(b.target);
8274
8430
  });
8275
- return import_types31.DivergenceResultSchema.parse({
8431
+ return import_types32.DivergenceResultSchema.parse({
8276
8432
  divergences: filtered,
8277
8433
  totalAffected: filtered.length,
8278
8434
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -8282,8 +8438,8 @@ function computeDivergences(graph, opts = {}) {
8282
8438
  // src/persist.ts
8283
8439
  init_cjs_shims();
8284
8440
  var import_node_fs25 = require("fs");
8285
- var import_node_path43 = __toESM(require("path"), 1);
8286
- var import_types32 = require("@neat.is/types");
8441
+ var import_node_path44 = __toESM(require("path"), 1);
8442
+ var import_types33 = require("@neat.is/types");
8287
8443
  var SCHEMA_VERSION = 4;
8288
8444
  function migrateV1ToV2(payload) {
8289
8445
  const nodes = payload.graph.nodes;
@@ -8305,12 +8461,12 @@ function migrateV2ToV3(payload) {
8305
8461
  for (const edge of edges) {
8306
8462
  const attrs = edge.attributes;
8307
8463
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
8308
- attrs.provenance = import_types32.Provenance.OBSERVED;
8464
+ attrs.provenance = import_types33.Provenance.OBSERVED;
8309
8465
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
8310
8466
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
8311
8467
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
8312
8468
  if (type && source && target) {
8313
- const newId = (0, import_types32.observedEdgeId)(source, target, type);
8469
+ const newId = (0, import_types33.observedEdgeId)(source, target, type);
8314
8470
  attrs.id = newId;
8315
8471
  if (edge.key) edge.key = newId;
8316
8472
  }
@@ -8319,7 +8475,7 @@ function migrateV2ToV3(payload) {
8319
8475
  return { ...payload, schemaVersion: 3 };
8320
8476
  }
8321
8477
  async function ensureDir(filePath) {
8322
- await import_node_fs25.promises.mkdir(import_node_path43.default.dirname(filePath), { recursive: true });
8478
+ await import_node_fs25.promises.mkdir(import_node_path44.default.dirname(filePath), { recursive: true });
8323
8479
  }
8324
8480
  async function saveGraphToDisk(graph, outPath) {
8325
8481
  await ensureDir(outPath);
@@ -8401,7 +8557,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
8401
8557
  // src/gitignore.ts
8402
8558
  init_cjs_shims();
8403
8559
  var import_node_fs26 = require("fs");
8404
- var import_node_path44 = __toESM(require("path"), 1);
8560
+ var import_node_path45 = __toESM(require("path"), 1);
8405
8561
  var NEAT_OUT_LINE = "neat-out/";
8406
8562
  var NEAT_HEADER = "# NEAT \u2014 machine-local snapshots and events";
8407
8563
  function isNeatOutLine(line) {
@@ -8409,7 +8565,7 @@ function isNeatOutLine(line) {
8409
8565
  return trimmed === "neat-out/" || trimmed === "neat-out";
8410
8566
  }
8411
8567
  async function ensureNeatOutIgnored(projectDir) {
8412
- const file = import_node_path44.default.join(projectDir, ".gitignore");
8568
+ const file = import_node_path45.default.join(projectDir, ".gitignore");
8413
8569
  let existing = null;
8414
8570
  try {
8415
8571
  existing = await import_node_fs26.promises.readFile(file, "utf8");
@@ -8436,7 +8592,7 @@ ${NEAT_OUT_LINE}
8436
8592
 
8437
8593
  // src/summary.ts
8438
8594
  init_cjs_shims();
8439
- var import_types33 = require("@neat.is/types");
8595
+ var import_types34 = require("@neat.is/types");
8440
8596
  function renderOtelEnvBlock() {
8441
8597
  return [
8442
8598
  "for prod OTel routing, set these in your deploy platform's env:",
@@ -8446,19 +8602,19 @@ function renderOtelEnvBlock() {
8446
8602
  }
8447
8603
  function findIncompatServices(nodes) {
8448
8604
  return nodes.filter(
8449
- (n) => n.type === import_types33.NodeType.ServiceNode && Array.isArray(n.incompatibilities) && (n.incompatibilities ?? []).length > 0
8605
+ (n) => n.type === import_types34.NodeType.ServiceNode && Array.isArray(n.incompatibilities) && (n.incompatibilities ?? []).length > 0
8450
8606
  );
8451
8607
  }
8452
8608
  function servicesWithoutObserved(nodes, edges) {
8453
8609
  const seen = /* @__PURE__ */ new Set();
8454
8610
  for (const e of edges) {
8455
- if (e.provenance === import_types33.Provenance.OBSERVED) {
8611
+ if (e.provenance === import_types34.Provenance.OBSERVED) {
8456
8612
  seen.add(e.source);
8457
8613
  seen.add(e.target);
8458
8614
  }
8459
8615
  }
8460
8616
  return nodes.filter(
8461
- (n) => n.type === import_types33.NodeType.ServiceNode && !seen.has(n.id)
8617
+ (n) => n.type === import_types34.NodeType.ServiceNode && !seen.has(n.id)
8462
8618
  );
8463
8619
  }
8464
8620
  function formatDivergence(d) {
@@ -8533,26 +8689,26 @@ function formatIncompat(inc) {
8533
8689
  // src/watch.ts
8534
8690
  init_cjs_shims();
8535
8691
  var import_node_fs35 = __toESM(require("fs"), 1);
8536
- var import_node_path55 = __toESM(require("path"), 1);
8692
+ var import_node_path56 = __toESM(require("path"), 1);
8537
8693
  var import_chokidar = __toESM(require("chokidar"), 1);
8538
8694
 
8539
8695
  // src/api.ts
8540
8696
  init_cjs_shims();
8541
8697
  var import_fastify = __toESM(require("fastify"), 1);
8542
8698
  var import_cors = __toESM(require("@fastify/cors"), 1);
8543
- var import_types35 = require("@neat.is/types");
8699
+ var import_types36 = require("@neat.is/types");
8544
8700
 
8545
8701
  // src/extend/index.ts
8546
8702
  init_cjs_shims();
8547
8703
  var import_node_fs28 = require("fs");
8548
- var import_node_path46 = __toESM(require("path"), 1);
8704
+ var import_node_path47 = __toESM(require("path"), 1);
8549
8705
  var import_node_os2 = __toESM(require("os"), 1);
8550
8706
  var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
8551
8707
 
8552
8708
  // src/installers/package-manager.ts
8553
8709
  init_cjs_shims();
8554
8710
  var import_node_fs27 = require("fs");
8555
- var import_node_path45 = __toESM(require("path"), 1);
8711
+ var import_node_path46 = __toESM(require("path"), 1);
8556
8712
  var import_node_child_process = require("child_process");
8557
8713
  var LOCKFILE_PRIORITY = [
8558
8714
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -8574,22 +8730,22 @@ async function exists2(p) {
8574
8730
  }
8575
8731
  }
8576
8732
  async function detectPackageManager(serviceDir) {
8577
- let dir = import_node_path45.default.resolve(serviceDir);
8733
+ let dir = import_node_path46.default.resolve(serviceDir);
8578
8734
  const stops = /* @__PURE__ */ new Set();
8579
8735
  for (let i = 0; i < 64; i++) {
8580
8736
  if (stops.has(dir)) break;
8581
8737
  stops.add(dir);
8582
8738
  for (const candidate of LOCKFILE_PRIORITY) {
8583
- const lockPath = import_node_path45.default.join(dir, candidate.lockfile);
8739
+ const lockPath = import_node_path46.default.join(dir, candidate.lockfile);
8584
8740
  if (await exists2(lockPath)) {
8585
8741
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
8586
8742
  }
8587
8743
  }
8588
- const parent = import_node_path45.default.dirname(dir);
8744
+ const parent = import_node_path46.default.dirname(dir);
8589
8745
  if (parent === dir) break;
8590
8746
  dir = parent;
8591
8747
  }
8592
- return { pm: "npm", cwd: import_node_path45.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
8748
+ return { pm: "npm", cwd: import_node_path46.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
8593
8749
  }
8594
8750
  async function runPackageManagerInstall(cmd) {
8595
8751
  return new Promise((resolve) => {
@@ -8638,7 +8794,7 @@ async function fileExists2(p) {
8638
8794
  }
8639
8795
  }
8640
8796
  async function readPackageJson(scanPath) {
8641
- const pkgPath = import_node_path46.default.join(scanPath, "package.json");
8797
+ const pkgPath = import_node_path47.default.join(scanPath, "package.json");
8642
8798
  const raw = await import_node_fs28.promises.readFile(pkgPath, "utf8");
8643
8799
  return JSON.parse(raw);
8644
8800
  }
@@ -8652,27 +8808,27 @@ var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
8652
8808
  ]);
8653
8809
  async function findHookFiles(scanPath) {
8654
8810
  const found = [];
8655
- const walk3 = async (dir) => {
8811
+ const walk4 = async (dir) => {
8656
8812
  const entries = await import_node_fs28.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
8657
8813
  for (const entry2 of entries) {
8658
8814
  if (entry2.isDirectory()) {
8659
8815
  if (entry2.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry2.name)) continue;
8660
- await walk3(import_node_path46.default.join(dir, entry2.name));
8816
+ await walk4(import_node_path47.default.join(dir, entry2.name));
8661
8817
  } else if (entry2.isFile()) {
8662
8818
  if ((entry2.name.startsWith("instrumentation") || entry2.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry2.name)) {
8663
- const rel = import_node_path46.default.relative(scanPath, import_node_path46.default.join(dir, entry2.name));
8664
- found.push(rel.split(import_node_path46.default.sep).join("/"));
8819
+ const rel = import_node_path47.default.relative(scanPath, import_node_path47.default.join(dir, entry2.name));
8820
+ found.push(rel.split(import_node_path47.default.sep).join("/"));
8665
8821
  }
8666
8822
  }
8667
8823
  }
8668
8824
  };
8669
- await walk3(scanPath);
8825
+ await walk4(scanPath);
8670
8826
  return found.sort();
8671
8827
  }
8672
8828
  async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
8673
8829
  let fallback = null;
8674
8830
  for (const file of hookFiles) {
8675
- const content = await import_node_fs28.promises.readFile(import_node_path46.default.join(scanPath, file), "utf8");
8831
+ const content = await import_node_fs28.promises.readFile(import_node_path47.default.join(scanPath, file), "utf8");
8676
8832
  const patched = splicedContent(content, snippet2);
8677
8833
  if (patched !== null) return { file, content, patched };
8678
8834
  if (fallback === null) fallback = { file, content };
@@ -8680,11 +8836,11 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
8680
8836
  return { file: fallback.file, content: fallback.content, patched: null };
8681
8837
  }
8682
8838
  function extendLogPath() {
8683
- return process.env.NEAT_EXTEND_LOG ?? import_node_path46.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
8839
+ return process.env.NEAT_EXTEND_LOG ?? import_node_path47.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
8684
8840
  }
8685
8841
  async function appendExtendLog(entry2) {
8686
8842
  const logPath = extendLogPath();
8687
- await import_node_fs28.promises.mkdir(import_node_path46.default.dirname(logPath), { recursive: true });
8843
+ await import_node_fs28.promises.mkdir(import_node_path47.default.dirname(logPath), { recursive: true });
8688
8844
  await import_node_fs28.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
8689
8845
  }
8690
8846
  function splicedContent(fileContent, snippet2) {
@@ -8743,7 +8899,7 @@ function lookupInstrumentation(library, installedVersion) {
8743
8899
  }
8744
8900
  async function describeProjectInstrumentation(ctx) {
8745
8901
  const hookFiles = await findHookFiles(ctx.scanPath);
8746
- const envNeat = await fileExists2(import_node_path46.default.join(ctx.scanPath, ".env.neat"));
8902
+ const envNeat = await fileExists2(import_node_path47.default.join(ctx.scanPath, ".env.neat"));
8747
8903
  const registryInstrPackages = new Set(
8748
8904
  (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
8749
8905
  );
@@ -8765,7 +8921,7 @@ async function applyExtension(ctx, args, options) {
8765
8921
  );
8766
8922
  }
8767
8923
  for (const file of hookFiles) {
8768
- const content = await import_node_fs28.promises.readFile(import_node_path46.default.join(ctx.scanPath, file), "utf8");
8924
+ const content = await import_node_fs28.promises.readFile(import_node_path47.default.join(ctx.scanPath, file), "utf8");
8769
8925
  if (content.includes(args.registration_snippet)) {
8770
8926
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
8771
8927
  }
@@ -8777,10 +8933,10 @@ async function applyExtension(ctx, args, options) {
8777
8933
  );
8778
8934
  }
8779
8935
  const primaryFile = primary.file;
8780
- const primaryPath = import_node_path46.default.join(ctx.scanPath, primaryFile);
8936
+ const primaryPath = import_node_path47.default.join(ctx.scanPath, primaryFile);
8781
8937
  const filesTouched = [];
8782
8938
  const depsAdded = [];
8783
- const pkgPath = import_node_path46.default.join(ctx.scanPath, "package.json");
8939
+ const pkgPath = import_node_path47.default.join(ctx.scanPath, "package.json");
8784
8940
  const pkg = await readPackageJson(ctx.scanPath);
8785
8941
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
8786
8942
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
@@ -8819,7 +8975,7 @@ async function dryRunExtension(ctx, args) {
8819
8975
  };
8820
8976
  }
8821
8977
  for (const file of hookFiles) {
8822
- const content = await import_node_fs28.promises.readFile(import_node_path46.default.join(ctx.scanPath, file), "utf8");
8978
+ const content = await import_node_fs28.promises.readFile(import_node_path47.default.join(ctx.scanPath, file), "utf8");
8823
8979
  if (content.includes(args.registration_snippet)) {
8824
8980
  return {
8825
8981
  library: args.library,
@@ -8860,7 +9016,7 @@ async function rollbackExtension(ctx, args) {
8860
9016
  if (!match) {
8861
9017
  return { undone: false, message: "no apply found for library" };
8862
9018
  }
8863
- const pkgPath = import_node_path46.default.join(ctx.scanPath, "package.json");
9019
+ const pkgPath = import_node_path47.default.join(ctx.scanPath, "package.json");
8864
9020
  if (await fileExists2(pkgPath)) {
8865
9021
  const pkg = await readPackageJson(ctx.scanPath);
8866
9022
  if (pkg.dependencies?.[match.instrumentation_package]) {
@@ -8871,7 +9027,7 @@ async function rollbackExtension(ctx, args) {
8871
9027
  }
8872
9028
  const hookFiles = await findHookFiles(ctx.scanPath);
8873
9029
  for (const file of hookFiles) {
8874
- const filePath = import_node_path46.default.join(ctx.scanPath, file);
9030
+ const filePath = import_node_path47.default.join(ctx.scanPath, file);
8875
9031
  const content = await import_node_fs28.promises.readFile(filePath, "utf8");
8876
9032
  if (content.includes(match.registration_snippet)) {
8877
9033
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
@@ -9009,23 +9165,23 @@ function canonicalJson(value) {
9009
9165
 
9010
9166
  // src/projects.ts
9011
9167
  init_cjs_shims();
9012
- var import_node_path47 = __toESM(require("path"), 1);
9168
+ var import_node_path48 = __toESM(require("path"), 1);
9013
9169
  function pathsForProject(project, baseDir) {
9014
9170
  if (project === DEFAULT_PROJECT) {
9015
9171
  return {
9016
- snapshotPath: import_node_path47.default.join(baseDir, "graph.json"),
9017
- errorsPath: import_node_path47.default.join(baseDir, "errors.ndjson"),
9018
- staleEventsPath: import_node_path47.default.join(baseDir, "stale-events.ndjson"),
9019
- embeddingsCachePath: import_node_path47.default.join(baseDir, "embeddings.json"),
9020
- policyViolationsPath: import_node_path47.default.join(baseDir, "policy-violations.ndjson")
9172
+ snapshotPath: import_node_path48.default.join(baseDir, "graph.json"),
9173
+ errorsPath: import_node_path48.default.join(baseDir, "errors.ndjson"),
9174
+ staleEventsPath: import_node_path48.default.join(baseDir, "stale-events.ndjson"),
9175
+ embeddingsCachePath: import_node_path48.default.join(baseDir, "embeddings.json"),
9176
+ policyViolationsPath: import_node_path48.default.join(baseDir, "policy-violations.ndjson")
9021
9177
  };
9022
9178
  }
9023
9179
  return {
9024
- snapshotPath: import_node_path47.default.join(baseDir, `${project}.json`),
9025
- errorsPath: import_node_path47.default.join(baseDir, `errors.${project}.ndjson`),
9026
- staleEventsPath: import_node_path47.default.join(baseDir, `stale-events.${project}.ndjson`),
9027
- embeddingsCachePath: import_node_path47.default.join(baseDir, `embeddings.${project}.json`),
9028
- policyViolationsPath: import_node_path47.default.join(baseDir, `policy-violations.${project}.ndjson`)
9180
+ snapshotPath: import_node_path48.default.join(baseDir, `${project}.json`),
9181
+ errorsPath: import_node_path48.default.join(baseDir, `errors.${project}.ndjson`),
9182
+ staleEventsPath: import_node_path48.default.join(baseDir, `stale-events.${project}.ndjson`),
9183
+ embeddingsCachePath: import_node_path48.default.join(baseDir, `embeddings.${project}.json`),
9184
+ policyViolationsPath: import_node_path48.default.join(baseDir, `policy-violations.${project}.ndjson`)
9029
9185
  };
9030
9186
  }
9031
9187
  var Projects = class {
@@ -9063,26 +9219,26 @@ var Projects = class {
9063
9219
  init_cjs_shims();
9064
9220
  var import_node_fs30 = require("fs");
9065
9221
  var import_node_os3 = __toESM(require("os"), 1);
9066
- var import_node_path48 = __toESM(require("path"), 1);
9067
- var import_types34 = require("@neat.is/types");
9222
+ var import_node_path49 = __toESM(require("path"), 1);
9223
+ var import_types35 = require("@neat.is/types");
9068
9224
  var LOCK_TIMEOUT_MS = 5e3;
9069
9225
  var LOCK_RETRY_MS = 50;
9070
9226
  function neatHome() {
9071
9227
  const override = process.env.NEAT_HOME;
9072
- if (override && override.length > 0) return import_node_path48.default.resolve(override);
9073
- return import_node_path48.default.join(import_node_os3.default.homedir(), ".neat");
9228
+ if (override && override.length > 0) return import_node_path49.default.resolve(override);
9229
+ return import_node_path49.default.join(import_node_os3.default.homedir(), ".neat");
9074
9230
  }
9075
9231
  function registryPath() {
9076
- return import_node_path48.default.join(neatHome(), "projects.json");
9232
+ return import_node_path49.default.join(neatHome(), "projects.json");
9077
9233
  }
9078
9234
  function registryLockPath() {
9079
- return import_node_path48.default.join(neatHome(), "projects.json.lock");
9235
+ return import_node_path49.default.join(neatHome(), "projects.json.lock");
9080
9236
  }
9081
9237
  function daemonPidPath() {
9082
- return import_node_path48.default.join(neatHome(), "neatd.pid");
9238
+ return import_node_path49.default.join(neatHome(), "neatd.pid");
9083
9239
  }
9084
9240
  function daemonsDir() {
9085
- return import_node_path48.default.join(neatHome(), "daemons");
9241
+ return import_node_path49.default.join(neatHome(), "daemons");
9086
9242
  }
9087
9243
  function isFiniteInt(v) {
9088
9244
  return typeof v === "number" && Number.isFinite(v);
@@ -9123,7 +9279,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
9123
9279
  const out = [];
9124
9280
  for (const name of names) {
9125
9281
  if (!name.endsWith(".json")) continue;
9126
- const file = import_node_path48.default.join(dir, name);
9282
+ const file = import_node_path49.default.join(dir, name);
9127
9283
  let raw;
9128
9284
  try {
9129
9285
  raw = await import_node_fs30.promises.readFile(file, "utf8");
@@ -9244,7 +9400,7 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
9244
9400
  }
9245
9401
  }
9246
9402
  async function normalizeProjectPath(input) {
9247
- const resolved = import_node_path48.default.resolve(input);
9403
+ const resolved = import_node_path49.default.resolve(input);
9248
9404
  try {
9249
9405
  return await import_node_fs30.promises.realpath(resolved);
9250
9406
  } catch {
@@ -9252,7 +9408,7 @@ async function normalizeProjectPath(input) {
9252
9408
  }
9253
9409
  }
9254
9410
  async function writeAtomically(target, contents) {
9255
- await import_node_fs30.promises.mkdir(import_node_path48.default.dirname(target), { recursive: true });
9411
+ await import_node_fs30.promises.mkdir(import_node_path49.default.dirname(target), { recursive: true });
9256
9412
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
9257
9413
  const fd = await import_node_fs30.promises.open(tmp, "w");
9258
9414
  try {
@@ -9265,7 +9421,7 @@ async function writeAtomically(target, contents) {
9265
9421
  }
9266
9422
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
9267
9423
  const deadline = Date.now() + timeoutMs;
9268
- await import_node_fs30.promises.mkdir(import_node_path48.default.dirname(lockPath), { recursive: true });
9424
+ await import_node_fs30.promises.mkdir(import_node_path49.default.dirname(lockPath), { recursive: true });
9269
9425
  let probedHolder = false;
9270
9426
  while (true) {
9271
9427
  try {
@@ -9318,10 +9474,10 @@ async function readRegistry() {
9318
9474
  throw err;
9319
9475
  }
9320
9476
  const parsed = JSON.parse(raw);
9321
- return import_types34.RegistryFileSchema.parse(parsed);
9477
+ return import_types35.RegistryFileSchema.parse(parsed);
9322
9478
  }
9323
9479
  async function writeRegistry(reg) {
9324
- const validated = import_types34.RegistryFileSchema.parse(reg);
9480
+ const validated = import_types35.RegistryFileSchema.parse(reg);
9325
9481
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
9326
9482
  }
9327
9483
  var ProjectNameCollisionError = class extends Error {
@@ -9507,7 +9663,7 @@ init_auth();
9507
9663
  // src/connectors-config.ts
9508
9664
  init_cjs_shims();
9509
9665
  var import_node_os4 = __toESM(require("os"), 1);
9510
- var import_node_path49 = __toESM(require("path"), 1);
9666
+ var import_node_path50 = __toESM(require("path"), 1);
9511
9667
  var import_node_fs31 = require("fs");
9512
9668
  var CONNECTORS_CONFIG_VERSION = 1;
9513
9669
  var EnvRefUnsetError = class extends Error {
@@ -9522,11 +9678,11 @@ var EnvRefUnsetError = class extends Error {
9522
9678
  };
9523
9679
  function neatHome2() {
9524
9680
  const override = process.env.NEAT_HOME;
9525
- if (override && override.length > 0) return import_node_path49.default.resolve(override);
9526
- return import_node_path49.default.join(import_node_os4.default.homedir(), ".neat");
9681
+ if (override && override.length > 0) return import_node_path50.default.resolve(override);
9682
+ return import_node_path50.default.join(import_node_os4.default.homedir(), ".neat");
9527
9683
  }
9528
9684
  function connectorsConfigPath(home = neatHome2()) {
9529
- return import_node_path49.default.join(home, "connectors.json");
9685
+ return import_node_path50.default.join(home, "connectors.json");
9530
9686
  }
9531
9687
  var MODE_MASK_LOOSER_THAN_0600 = 63;
9532
9688
  async function warnIfModeLooserThan0600(file) {
@@ -9657,7 +9813,7 @@ function connectorMatchesProject(entry2, project) {
9657
9813
  var CONNECTORS_LOCK_TIMEOUT_MS = 5e3;
9658
9814
  var CONNECTORS_LOCK_RETRY_MS = 50;
9659
9815
  function connectorsConfigLockPath(home = neatHome2()) {
9660
- return import_node_path49.default.join(home, "connectors.json.lock");
9816
+ return import_node_path50.default.join(home, "connectors.json.lock");
9661
9817
  }
9662
9818
  function isEnvRef(value) {
9663
9819
  return value.length > 1 && value.startsWith("$");
@@ -9670,7 +9826,7 @@ function redactCredentialRef(ref) {
9670
9826
  return out;
9671
9827
  }
9672
9828
  async function writeConfigAtomically0600(file, contents) {
9673
- await import_node_fs31.promises.mkdir(import_node_path49.default.dirname(file), { recursive: true });
9829
+ await import_node_fs31.promises.mkdir(import_node_path50.default.dirname(file), { recursive: true });
9674
9830
  const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
9675
9831
  const fd = await import_node_fs31.promises.open(tmp, "w", 384);
9676
9832
  try {
@@ -9684,7 +9840,7 @@ async function writeConfigAtomically0600(file, contents) {
9684
9840
  }
9685
9841
  async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEOUT_MS) {
9686
9842
  const deadline = Date.now() + timeoutMs;
9687
- await import_node_fs31.promises.mkdir(import_node_path49.default.dirname(lockPath), { recursive: true });
9843
+ await import_node_fs31.promises.mkdir(import_node_path50.default.dirname(lockPath), { recursive: true });
9688
9844
  for (; ; ) {
9689
9845
  try {
9690
9846
  const fd = await import_node_fs31.promises.open(lockPath, "wx");
@@ -9967,11 +10123,11 @@ function registerRoutes(scope, ctx) {
9967
10123
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
9968
10124
  const parsed = [];
9969
10125
  for (const c of candidates) {
9970
- const r = import_types35.DivergenceTypeSchema.safeParse(c);
10126
+ const r = import_types36.DivergenceTypeSchema.safeParse(c);
9971
10127
  if (!r.success) {
9972
10128
  return reply.code(400).send({
9973
10129
  error: `unknown divergence type "${c}"`,
9974
- allowed: import_types35.DivergenceTypeSchema.options
10130
+ allowed: import_types36.DivergenceTypeSchema.options
9975
10131
  });
9976
10132
  }
9977
10133
  parsed.push(r.data);
@@ -10242,7 +10398,7 @@ function registerRoutes(scope, ctx) {
10242
10398
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
10243
10399
  let violations = await log.readAll();
10244
10400
  if (req.query.severity) {
10245
- const sev = import_types35.PolicySeveritySchema.safeParse(req.query.severity);
10401
+ const sev = import_types36.PolicySeveritySchema.safeParse(req.query.severity);
10246
10402
  if (!sev.success) {
10247
10403
  return reply.code(400).send({
10248
10404
  error: "invalid severity",
@@ -10281,7 +10437,7 @@ function registerRoutes(scope, ctx) {
10281
10437
  scope.post("/policies/check", async (req, reply) => {
10282
10438
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
10283
10439
  if (!proj) return;
10284
- const parsed = import_types35.PoliciesCheckBodySchema.safeParse(req.body ?? {});
10440
+ const parsed = import_types36.PoliciesCheckBodySchema.safeParse(req.body ?? {});
10285
10441
  if (!parsed.success) {
10286
10442
  return reply.code(400).send({
10287
10443
  error: "invalid /policies/check body",
@@ -10583,13 +10739,13 @@ init_otel();
10583
10739
  // src/daemon.ts
10584
10740
  init_cjs_shims();
10585
10741
  var import_node_fs33 = require("fs");
10586
- var import_node_path53 = __toESM(require("path"), 1);
10742
+ var import_node_path54 = __toESM(require("path"), 1);
10587
10743
  var import_node_module = require("module");
10588
10744
  init_otel();
10589
10745
 
10590
10746
  // src/connectors/index.ts
10591
10747
  init_cjs_shims();
10592
- var import_types36 = require("@neat.is/types");
10748
+ var import_types37 = require("@neat.is/types");
10593
10749
 
10594
10750
  // src/connectors/registry.ts
10595
10751
  init_cjs_shims();
@@ -10964,10 +11120,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
10964
11120
  // src/connectors/supabase/map.ts
10965
11121
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
10966
11122
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
10967
- function targetFromRestPath(path63) {
10968
- const rpcMatch = REST_RPC_PATH_RE.exec(path63);
11123
+ function targetFromRestPath(path64) {
11124
+ const rpcMatch = REST_RPC_PATH_RE.exec(path64);
10969
11125
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
10970
- const tableMatch = REST_TABLE_PATH_RE.exec(path63);
11126
+ const tableMatch = REST_TABLE_PATH_RE.exec(path64);
10971
11127
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
10972
11128
  return null;
10973
11129
  }
@@ -11076,23 +11232,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
11076
11232
 
11077
11233
  // src/connectors/supabase/resolve.ts
11078
11234
  init_cjs_shims();
11079
- var import_types38 = require("@neat.is/types");
11235
+ var import_types39 = require("@neat.is/types");
11080
11236
  function createSupabaseResolveTarget(graph, config) {
11081
11237
  return (signal, _ctx) => {
11082
11238
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
11083
11239
  return null;
11084
11240
  }
11085
- const subResourceId = (0, import_types38.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
11241
+ const subResourceId = (0, import_types39.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
11086
11242
  if (graph.hasNode(subResourceId)) {
11087
- return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types38.EdgeType.CALLS };
11243
+ return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types39.EdgeType.CALLS };
11088
11244
  }
11089
- const bareResourceId = (0, import_types38.infraId)(signal.targetKind, signal.targetName);
11245
+ const bareResourceId = (0, import_types39.infraId)(signal.targetKind, signal.targetName);
11090
11246
  if (graph.hasNode(bareResourceId)) {
11091
- return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types38.EdgeType.CALLS };
11247
+ return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types39.EdgeType.CALLS };
11092
11248
  }
11093
- const projectLevelId = (0, import_types38.infraId)("supabase", config.nodeRef);
11249
+ const projectLevelId = (0, import_types39.infraId)("supabase", config.nodeRef);
11094
11250
  if (graph.hasNode(projectLevelId)) {
11095
- return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types38.EdgeType.CALLS };
11251
+ return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types39.EdgeType.CALLS };
11096
11252
  }
11097
11253
  return null;
11098
11254
  };
@@ -11185,7 +11341,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
11185
11341
 
11186
11342
  // src/connectors/railway/index.ts
11187
11343
  init_cjs_shims();
11188
- var import_types42 = require("@neat.is/types");
11344
+ var import_types43 = require("@neat.is/types");
11189
11345
 
11190
11346
  // src/connectors/railway/client.ts
11191
11347
  init_cjs_shims();
@@ -11311,7 +11467,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
11311
11467
  const out = [];
11312
11468
  graph.forEachNode((_id, attrs) => {
11313
11469
  const node = attrs;
11314
- if (node.type !== import_types42.NodeType.RouteNode) return;
11470
+ if (node.type !== import_types43.NodeType.RouteNode) return;
11315
11471
  const route = attrs;
11316
11472
  if (route.service !== serviceName) return;
11317
11473
  out.push({
@@ -11415,12 +11571,12 @@ function createRailwayResolveTarget(config) {
11415
11571
  const serviceName = config.serviceNameById[config.serviceId];
11416
11572
  if (!serviceName) return null;
11417
11573
  if (signal.targetKind === ROUTE_TARGET_KIND) {
11418
- return { targetNodeId: signal.targetName, serviceName, edgeType: import_types42.EdgeType.CALLS };
11574
+ return { targetNodeId: signal.targetName, serviceName, edgeType: import_types43.EdgeType.CALLS };
11419
11575
  }
11420
11576
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
11421
11577
  const peerName = config.serviceNameById[signal.targetName];
11422
11578
  if (!peerName) return null;
11423
- return { targetNodeId: (0, import_types42.serviceId)(peerName), serviceName, edgeType: import_types42.EdgeType.CONNECTS_TO };
11579
+ return { targetNodeId: (0, import_types43.serviceId)(peerName), serviceName, edgeType: import_types43.EdgeType.CONNECTS_TO };
11424
11580
  }
11425
11581
  return null;
11426
11582
  };
@@ -11544,9 +11700,9 @@ function parseFirebaseTargetName(targetName) {
11544
11700
  const secondSep = rest.indexOf(FIELD_SEP);
11545
11701
  if (secondSep === -1) return null;
11546
11702
  const method = rest.slice(0, secondSep);
11547
- const path63 = rest.slice(secondSep + 1);
11548
- if (!resourceName || !method || !path63) return null;
11549
- return { resourceName, method, path: path63 };
11703
+ const path64 = rest.slice(secondSep + 1);
11704
+ if (!resourceName || !method || !path64) return null;
11705
+ return { resourceName, method, path: path64 };
11550
11706
  }
11551
11707
  function resourceNameFor(type, labels) {
11552
11708
  if (!labels) return null;
@@ -11584,14 +11740,14 @@ function mapLogEntryToSignal(entry2) {
11584
11740
  if (!req) return null;
11585
11741
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
11586
11742
  const method = req.requestMethod.toUpperCase();
11587
- const path63 = pathFromRequestUrl(req.requestUrl);
11588
- if (path63 === null) return null;
11743
+ const path64 = pathFromRequestUrl(req.requestUrl);
11744
+ if (path64 === null) return null;
11589
11745
  const timestamp = entry2.timestamp;
11590
11746
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
11591
11747
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
11592
11748
  return {
11593
11749
  targetKind: resourceType,
11594
- targetName: packFirebaseTargetName({ resourceName, method, path: path63 }),
11750
+ targetName: packFirebaseTargetName({ resourceName, method, path: path64 }),
11595
11751
  callCount: 1,
11596
11752
  errorCount: isError ? 1 : 0,
11597
11753
  lastObservedIso: timestamp
@@ -11608,7 +11764,7 @@ function mapLogEntriesToSignals(entries) {
11608
11764
 
11609
11765
  // src/connectors/firebase/resolve.ts
11610
11766
  init_cjs_shims();
11611
- var import_types43 = require("@neat.is/types");
11767
+ var import_types44 = require("@neat.is/types");
11612
11768
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
11613
11769
  switch (resourceType) {
11614
11770
  case "cloud_function":
@@ -11623,7 +11779,7 @@ function routeEntriesFor(graph, serviceName) {
11623
11779
  const entries = [];
11624
11780
  graph.forEachNode((_id, attrs) => {
11625
11781
  const node = attrs;
11626
- if (node.type !== import_types43.NodeType.RouteNode) return;
11782
+ if (node.type !== import_types44.NodeType.RouteNode) return;
11627
11783
  const route = attrs;
11628
11784
  if (route.service !== serviceName) return;
11629
11785
  entries.push({
@@ -11655,7 +11811,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
11655
11811
  return {
11656
11812
  targetNodeId: match.routeNodeId,
11657
11813
  serviceName,
11658
- edgeType: import_types43.EdgeType.CALLS
11814
+ edgeType: import_types44.EdgeType.CALLS
11659
11815
  };
11660
11816
  };
11661
11817
  }
@@ -11682,7 +11838,7 @@ init_cjs_shims();
11682
11838
 
11683
11839
  // src/connectors/cloudflare/connector.ts
11684
11840
  init_cjs_shims();
11685
- var import_types45 = require("@neat.is/types");
11841
+ var import_types46 = require("@neat.is/types");
11686
11842
 
11687
11843
  // src/connectors/cloudflare/client.ts
11688
11844
  init_cjs_shims();
@@ -11798,7 +11954,7 @@ function mapEventToSignal(event) {
11798
11954
  if (Number.isNaN(observedAt.getTime())) return null;
11799
11955
  const statusCode = metadata?.statusCode;
11800
11956
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
11801
- const path63 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
11957
+ const path64 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
11802
11958
  return {
11803
11959
  targetKind: CLOUDFLARE_TARGET_KIND,
11804
11960
  targetName: scriptName,
@@ -11806,7 +11962,7 @@ function mapEventToSignal(event) {
11806
11962
  errorCount: isError ? 1 : 0,
11807
11963
  lastObservedIso: observedAt.toISOString(),
11808
11964
  method,
11809
- ...path63 ? { path: path63 } : {},
11965
+ ...path64 ? { path: path64 } : {},
11810
11966
  ...typeof statusCode === "number" ? { statusCode } : {},
11811
11967
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
11812
11968
  };
@@ -11846,19 +12002,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
11846
12002
  graph.forEachNode((id, attrs) => {
11847
12003
  if (found) return;
11848
12004
  const a = attrs;
11849
- if (a.type === import_types45.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
12005
+ if (a.type === import_types46.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
11850
12006
  found = id;
11851
12007
  }
11852
12008
  });
11853
12009
  return found;
11854
12010
  }
11855
- function findMatchingRouteNode(graph, serviceName, method, path63) {
11856
- const normalizedPath = normalizePathTemplate(path63);
12011
+ function findMatchingRouteNode(graph, serviceName, method, path64) {
12012
+ const normalizedPath = normalizePathTemplate(path64);
11857
12013
  let found = null;
11858
12014
  graph.forEachNode((id, attrs) => {
11859
12015
  if (found) return;
11860
12016
  const a = attrs;
11861
- if (a.type !== import_types45.NodeType.RouteNode || a.service !== serviceName) return;
12017
+ if (a.type !== import_types46.NodeType.RouteNode || a.service !== serviceName) return;
11862
12018
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
11863
12019
  const routeMethod = (a.method ?? "").toUpperCase();
11864
12020
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -11870,18 +12026,18 @@ function createCloudflareResolveTarget(config, graph) {
11870
12026
  return (signal) => {
11871
12027
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
11872
12028
  const scriptName = signal.targetName;
11873
- const { method, path: path63 } = signal;
12029
+ const { method, path: path64 } = signal;
11874
12030
  const resolveRouteGrain = (serviceName, wholeFileId) => {
11875
- if (!method || !path63) return wholeFileId;
11876
- return findMatchingRouteNode(graph, serviceName, method, path63) ?? wholeFileId;
12031
+ if (!method || !path64) return wholeFileId;
12032
+ return findMatchingRouteNode(graph, serviceName, method, path64) ?? wholeFileId;
11877
12033
  };
11878
12034
  const mapping = config.workers?.[scriptName];
11879
12035
  if (mapping) {
11880
- const wholeFileId = (0, import_types45.fileId)(mapping.service, mapping.entryFile);
12036
+ const wholeFileId = (0, import_types46.fileId)(mapping.service, mapping.entryFile);
11881
12037
  return {
11882
12038
  targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
11883
12039
  serviceName: mapping.service,
11884
- edgeType: import_types45.EdgeType.CALLS
12040
+ edgeType: import_types46.EdgeType.CALLS
11885
12041
  };
11886
12042
  }
11887
12043
  const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
@@ -11890,13 +12046,13 @@ function createCloudflareResolveTarget(config, graph) {
11890
12046
  return {
11891
12047
  targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
11892
12048
  serviceName: fileNode.service,
11893
- edgeType: import_types45.EdgeType.CALLS
12049
+ edgeType: import_types46.EdgeType.CALLS
11894
12050
  };
11895
12051
  }
11896
12052
  return {
11897
- targetNodeId: (0, import_types45.infraId)("cloudflare-worker", scriptName),
12053
+ targetNodeId: (0, import_types46.infraId)("cloudflare-worker", scriptName),
11898
12054
  serviceName: scriptName,
11899
- edgeType: import_types45.EdgeType.CALLS,
12055
+ edgeType: import_types46.EdgeType.CALLS,
11900
12056
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
11901
12057
  };
11902
12058
  };
@@ -12355,28 +12511,28 @@ init_auth();
12355
12511
  // src/unrouted.ts
12356
12512
  init_cjs_shims();
12357
12513
  var import_node_fs32 = require("fs");
12358
- var import_node_path52 = __toESM(require("path"), 1);
12514
+ var import_node_path53 = __toESM(require("path"), 1);
12359
12515
 
12360
12516
  // src/daemon.ts
12361
- var import_types48 = require("@neat.is/types");
12517
+ var import_types49 = require("@neat.is/types");
12362
12518
  function daemonJsonPath(scanPath) {
12363
- return import_node_path53.default.join(scanPath, "neat-out", "daemon.json");
12519
+ return import_node_path54.default.join(scanPath, "neat-out", "daemon.json");
12364
12520
  }
12365
12521
  function daemonsDiscoveryDir(home) {
12366
12522
  const base = home && home.length > 0 ? home : neatHomeFromEnv();
12367
- return import_node_path53.default.join(base, "daemons");
12523
+ return import_node_path54.default.join(base, "daemons");
12368
12524
  }
12369
12525
  function daemonDiscoveryPath(project, home) {
12370
- return import_node_path53.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
12526
+ return import_node_path54.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
12371
12527
  }
12372
12528
  function sanitizeDiscoveryName(project) {
12373
12529
  return project.replace(/[^A-Za-z0-9._-]/g, "_");
12374
12530
  }
12375
12531
  function neatHomeFromEnv() {
12376
12532
  const env = process.env.NEAT_HOME;
12377
- if (env && env.length > 0) return import_node_path53.default.resolve(env);
12533
+ if (env && env.length > 0) return import_node_path54.default.resolve(env);
12378
12534
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
12379
- return import_node_path53.default.join(home, ".neat");
12535
+ return import_node_path54.default.join(home, ".neat");
12380
12536
  }
12381
12537
  async function readDaemonRecord(scanPath) {
12382
12538
  try {
@@ -12447,7 +12603,7 @@ init_otel_grpc();
12447
12603
  // src/search.ts
12448
12604
  init_cjs_shims();
12449
12605
  var import_node_fs34 = require("fs");
12450
- var import_node_path54 = __toESM(require("path"), 1);
12606
+ var import_node_path55 = __toESM(require("path"), 1);
12451
12607
  var import_node_crypto4 = require("crypto");
12452
12608
  var DEFAULT_LIMIT = 10;
12453
12609
  var NOMIC_DIM = 768;
@@ -12610,7 +12766,7 @@ async function readCache(cachePath) {
12610
12766
  }
12611
12767
  }
12612
12768
  async function writeCache(cachePath, cache) {
12613
- await import_node_fs34.promises.mkdir(import_node_path54.default.dirname(cachePath), { recursive: true });
12769
+ await import_node_fs34.promises.mkdir(import_node_path55.default.dirname(cachePath), { recursive: true });
12614
12770
  await import_node_fs34.promises.writeFile(cachePath, JSON.stringify(cache));
12615
12771
  }
12616
12772
  var VectorIndex = class {
@@ -12768,8 +12924,8 @@ var ALL_PHASES = [
12768
12924
  ];
12769
12925
  function classifyChange(relPath) {
12770
12926
  const phases = /* @__PURE__ */ new Set();
12771
- const base = import_node_path55.default.basename(relPath).toLowerCase();
12772
- const segments = relPath.split(import_node_path55.default.sep).map((s) => s.toLowerCase());
12927
+ const base = import_node_path56.default.basename(relPath).toLowerCase();
12928
+ const segments = relPath.split(import_node_path56.default.sep).map((s) => s.toLowerCase());
12773
12929
  if (base === "package.json" || base === "requirements.txt" || base === "pyproject.toml" || base === "setup.py") {
12774
12930
  phases.add("services");
12775
12931
  phases.add("aliases");
@@ -12897,9 +13053,9 @@ function countWatchableDirs(scanPath, limit) {
12897
13053
  for (const e of entries) {
12898
13054
  if (count >= limit) return;
12899
13055
  if (!e.isDirectory()) continue;
12900
- if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path55.default.join(dir, e.name) + import_node_path55.default.sep))) continue;
13056
+ if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path56.default.join(dir, e.name) + import_node_path56.default.sep))) continue;
12901
13057
  count++;
12902
- if (depth < 2) visit(import_node_path55.default.join(dir, e.name), depth + 1);
13058
+ if (depth < 2) visit(import_node_path56.default.join(dir, e.name), depth + 1);
12903
13059
  }
12904
13060
  };
12905
13061
  visit(scanPath, 0);
@@ -12917,8 +13073,8 @@ async function startWatch(graph, opts) {
12917
13073
  const projectName = opts.project ?? DEFAULT_PROJECT;
12918
13074
  await loadGraphFromDisk(graph, opts.outPath);
12919
13075
  const detachEventBus = attachGraphToEventBus(graph, { project: projectName });
12920
- const policyFilePath = import_node_path55.default.join(opts.scanPath, "policy.json");
12921
- const policyViolationsPath = import_node_path55.default.join(import_node_path55.default.dirname(opts.outPath), "policy-violations.ndjson");
13076
+ const policyFilePath = import_node_path56.default.join(opts.scanPath, "policy.json");
13077
+ const policyViolationsPath = import_node_path56.default.join(import_node_path56.default.dirname(opts.outPath), "policy-violations.ndjson");
12922
13078
  let policies = [];
12923
13079
  try {
12924
13080
  policies = await loadPolicyFile(policyFilePath);
@@ -12969,7 +13125,7 @@ async function startWatch(graph, opts) {
12969
13125
  assertBindAuthority(host, auth.authToken);
12970
13126
  const port = opts.port ?? 8080;
12971
13127
  const otelPort = opts.otelPort ?? 4318;
12972
- const cachePath = opts.embeddingsCachePath ?? import_node_path55.default.join(import_node_path55.default.dirname(opts.outPath), "embeddings.json");
13128
+ const cachePath = opts.embeddingsCachePath ?? import_node_path56.default.join(import_node_path56.default.dirname(opts.outPath), "embeddings.json");
12973
13129
  let searchIndex;
12974
13130
  try {
12975
13131
  searchIndex = await buildSearchIndex(graph, { cachePath });
@@ -12987,7 +13143,7 @@ async function startWatch(graph, opts) {
12987
13143
  // Paths are derived from the explicit options the watch caller passes
12988
13144
  // — pathsForProject is only used to fill in the embeddings/snapshot
12989
13145
  // fields so the registry shape is complete.
12990
- ...pathsForProject(projectName, import_node_path55.default.dirname(opts.outPath)),
13146
+ ...pathsForProject(projectName, import_node_path56.default.dirname(opts.outPath)),
12991
13147
  snapshotPath: opts.outPath,
12992
13148
  errorsPath: opts.errorsPath,
12993
13149
  staleEventsPath: opts.staleEventsPath
@@ -13105,9 +13261,9 @@ async function startWatch(graph, opts) {
13105
13261
  };
13106
13262
  const onPath = (absPath) => {
13107
13263
  if (shouldIgnore(absPath)) return;
13108
- const rel = import_node_path55.default.relative(opts.scanPath, absPath);
13264
+ const rel = import_node_path56.default.relative(opts.scanPath, absPath);
13109
13265
  if (!rel || rel.startsWith("..")) return;
13110
- pendingPaths.add(rel.split(import_node_path55.default.sep).join("/"));
13266
+ pendingPaths.add(rel.split(import_node_path56.default.sep).join("/"));
13111
13267
  const phases = classifyChange(rel);
13112
13268
  if (phases.size === 0) {
13113
13269
  for (const p of ALL_PHASES) pending.add(p);
@@ -13164,7 +13320,7 @@ async function startWatch(graph, opts) {
13164
13320
  // src/deploy/detect.ts
13165
13321
  init_cjs_shims();
13166
13322
  var import_node_fs36 = require("fs");
13167
- var import_node_path56 = __toESM(require("path"), 1);
13323
+ var import_node_path57 = __toESM(require("path"), 1);
13168
13324
  var import_node_child_process2 = require("child_process");
13169
13325
  var import_node_crypto5 = require("crypto");
13170
13326
  function generateToken() {
@@ -13264,7 +13420,7 @@ async function runDeploy(opts = {}) {
13264
13420
  const token = generateToken();
13265
13421
  switch (substrate) {
13266
13422
  case "docker-compose": {
13267
- const artifactPath = import_node_path56.default.join(cwd, "docker-compose.neat.yml");
13423
+ const artifactPath = import_node_path57.default.join(cwd, "docker-compose.neat.yml");
13268
13424
  const contents = emitDockerCompose(cwd);
13269
13425
  await import_node_fs36.promises.writeFile(artifactPath, contents, "utf8");
13270
13426
  return {
@@ -13272,11 +13428,11 @@ async function runDeploy(opts = {}) {
13272
13428
  artifactPath,
13273
13429
  token,
13274
13430
  contents,
13275
- startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path56.default.basename(artifactPath)} up -d`
13431
+ startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path57.default.basename(artifactPath)} up -d`
13276
13432
  };
13277
13433
  }
13278
13434
  case "systemd": {
13279
- const artifactPath = import_node_path56.default.join(cwd, "neat.service");
13435
+ const artifactPath = import_node_path57.default.join(cwd, "neat.service");
13280
13436
  const contents = emitSystemdUnit(cwd);
13281
13437
  await import_node_fs36.promises.writeFile(artifactPath, contents, "utf8");
13282
13438
  return {
@@ -13312,7 +13468,7 @@ init_cjs_shims();
13312
13468
  // src/installers/javascript.ts
13313
13469
  init_cjs_shims();
13314
13470
  var import_node_fs37 = require("fs");
13315
- var import_node_path57 = __toESM(require("path"), 1);
13471
+ var import_node_path58 = __toESM(require("path"), 1);
13316
13472
  var import_semver2 = __toESM(require("semver"), 1);
13317
13473
 
13318
13474
  // src/installers/templates.ts
@@ -13965,11 +14121,11 @@ var OTEL_ENV = {
13965
14121
  value: "http://localhost:4318/projects/<project>/v1/traces"
13966
14122
  };
13967
14123
  function serviceNodeName(pkg, serviceDir) {
13968
- return pkg.name ?? import_node_path57.default.basename(serviceDir);
14124
+ return pkg.name ?? import_node_path58.default.basename(serviceDir);
13969
14125
  }
13970
14126
  function projectToken(pkg, serviceDir, project) {
13971
14127
  if (project && project.length > 0) return project;
13972
- return pkg.name ?? import_node_path57.default.basename(serviceDir);
14128
+ return pkg.name ?? import_node_path58.default.basename(serviceDir);
13973
14129
  }
13974
14130
  async function readJsonFile(p) {
13975
14131
  try {
@@ -13982,16 +14138,16 @@ async function readJsonFile(p) {
13982
14138
  async function detectRuntimeKind(pkgRoot, pkg) {
13983
14139
  const deps = allDeps(pkg);
13984
14140
  if ("react-native" in deps || "expo" in deps) return "react-native";
13985
- const appJson = await readJsonFile(import_node_path57.default.join(pkgRoot, "app.json"));
14141
+ const appJson = await readJsonFile(import_node_path58.default.join(pkgRoot, "app.json"));
13986
14142
  if (appJson && typeof appJson === "object" && "expo" in appJson) {
13987
14143
  return "react-native";
13988
14144
  }
13989
- if (await exists3(import_node_path57.default.join(pkgRoot, "vite.config.js")) || await exists3(import_node_path57.default.join(pkgRoot, "vite.config.ts")) || await exists3(import_node_path57.default.join(pkgRoot, "vite.config.mjs")) || "vite" in deps) {
14145
+ if (await exists3(import_node_path58.default.join(pkgRoot, "vite.config.js")) || await exists3(import_node_path58.default.join(pkgRoot, "vite.config.ts")) || await exists3(import_node_path58.default.join(pkgRoot, "vite.config.mjs")) || "vite" in deps) {
13990
14146
  return "browser-bundle";
13991
14147
  }
13992
- if (await exists3(import_node_path57.default.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
13993
- if (await exists3(import_node_path57.default.join(pkgRoot, "bun.lockb"))) return "bun";
13994
- if (await exists3(import_node_path57.default.join(pkgRoot, "deno.json")) || await exists3(import_node_path57.default.join(pkgRoot, "deno.lock"))) {
14148
+ if (await exists3(import_node_path58.default.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
14149
+ if (await exists3(import_node_path58.default.join(pkgRoot, "bun.lockb"))) return "bun";
14150
+ if (await exists3(import_node_path58.default.join(pkgRoot, "deno.json")) || await exists3(import_node_path58.default.join(pkgRoot, "deno.lock"))) {
13995
14151
  return "deno";
13996
14152
  }
13997
14153
  const engines = pkg.engines ?? {};
@@ -14000,7 +14156,7 @@ async function detectRuntimeKind(pkgRoot, pkg) {
14000
14156
  }
14001
14157
  async function readPackageJson2(serviceDir) {
14002
14158
  try {
14003
- const raw = await import_node_fs37.promises.readFile(import_node_path57.default.join(serviceDir, "package.json"), "utf8");
14159
+ const raw = await import_node_fs37.promises.readFile(import_node_path58.default.join(serviceDir, "package.json"), "utf8");
14004
14160
  return JSON.parse(raw);
14005
14161
  } catch {
14006
14162
  return null;
@@ -14044,7 +14200,7 @@ function needsVersionUpgrade(installed, expected) {
14044
14200
  var NEXT_CONFIG_CANDIDATES = ["next.config.js", "next.config.ts", "next.config.mjs"];
14045
14201
  async function findNextConfig(serviceDir) {
14046
14202
  for (const name of NEXT_CONFIG_CANDIDATES) {
14047
- const candidate = import_node_path57.default.join(serviceDir, name);
14203
+ const candidate = import_node_path58.default.join(serviceDir, name);
14048
14204
  if (await exists3(candidate)) return candidate;
14049
14205
  }
14050
14206
  return null;
@@ -14113,7 +14269,7 @@ function hasRemixDependency(pkg) {
14113
14269
  }
14114
14270
  async function findRemixEntry(serviceDir) {
14115
14271
  for (const rel of REMIX_ENTRY_CANDIDATES) {
14116
- const candidate = import_node_path57.default.join(serviceDir, rel);
14272
+ const candidate = import_node_path58.default.join(serviceDir, rel);
14117
14273
  if (await exists3(candidate)) return candidate;
14118
14274
  }
14119
14275
  return null;
@@ -14125,14 +14281,14 @@ function hasSvelteKitDependency(pkg) {
14125
14281
  }
14126
14282
  async function findSvelteKitHooks(serviceDir) {
14127
14283
  for (const rel of SVELTEKIT_HOOKS_CANDIDATES) {
14128
- const candidate = import_node_path57.default.join(serviceDir, rel);
14284
+ const candidate = import_node_path58.default.join(serviceDir, rel);
14129
14285
  if (await exists3(candidate)) return candidate;
14130
14286
  }
14131
14287
  return null;
14132
14288
  }
14133
14289
  async function findSvelteKitConfig(serviceDir) {
14134
14290
  for (const rel of SVELTEKIT_CONFIG_CANDIDATES) {
14135
- const candidate = import_node_path57.default.join(serviceDir, rel);
14291
+ const candidate = import_node_path58.default.join(serviceDir, rel);
14136
14292
  if (await exists3(candidate)) return candidate;
14137
14293
  }
14138
14294
  return null;
@@ -14143,7 +14299,7 @@ function hasNuxtDependency(pkg) {
14143
14299
  }
14144
14300
  async function findNuxtConfig(serviceDir) {
14145
14301
  for (const name of NUXT_CONFIG_CANDIDATES) {
14146
- const candidate = import_node_path57.default.join(serviceDir, name);
14302
+ const candidate = import_node_path58.default.join(serviceDir, name);
14147
14303
  if (await exists3(candidate)) return candidate;
14148
14304
  }
14149
14305
  return null;
@@ -14154,7 +14310,7 @@ function hasAstroDependency(pkg) {
14154
14310
  }
14155
14311
  async function findAstroConfig(serviceDir) {
14156
14312
  for (const name of ASTRO_CONFIG_CANDIDATES) {
14157
- const candidate = import_node_path57.default.join(serviceDir, name);
14313
+ const candidate = import_node_path58.default.join(serviceDir, name);
14158
14314
  if (await exists3(candidate)) return candidate;
14159
14315
  }
14160
14316
  return null;
@@ -14168,7 +14324,7 @@ function parseNextMajor(range) {
14168
14324
  return Number.isFinite(n) ? n : null;
14169
14325
  }
14170
14326
  async function isTypeScriptProject(serviceDir) {
14171
- return exists3(import_node_path57.default.join(serviceDir, "tsconfig.json"));
14327
+ return exists3(import_node_path58.default.join(serviceDir, "tsconfig.json"));
14172
14328
  }
14173
14329
  var INDEX_EXTENSIONS = [".ts", ".tsx", ".js", ".mjs", ".cjs"];
14174
14330
  var INDEX_CANDIDATES = INDEX_EXTENSIONS.map((ext) => `index${ext}`);
@@ -14217,7 +14373,7 @@ function entryFromScript(script) {
14217
14373
  }
14218
14374
  async function resolveEntry(serviceDir, pkg) {
14219
14375
  if (typeof pkg.main === "string" && pkg.main.length > 0) {
14220
- const candidate = import_node_path57.default.resolve(serviceDir, pkg.main);
14376
+ const candidate = import_node_path58.default.resolve(serviceDir, pkg.main);
14221
14377
  if (await exists3(candidate)) return candidate;
14222
14378
  }
14223
14379
  if (pkg.bin) {
@@ -14231,40 +14387,40 @@ async function resolveEntry(serviceDir, pkg) {
14231
14387
  if (typeof first === "string") binEntry = first;
14232
14388
  }
14233
14389
  if (binEntry) {
14234
- const candidate = import_node_path57.default.resolve(serviceDir, binEntry);
14390
+ const candidate = import_node_path58.default.resolve(serviceDir, binEntry);
14235
14391
  if (await exists3(candidate)) return candidate;
14236
14392
  }
14237
14393
  }
14238
14394
  const startEntry = entryFromScript(pkg.scripts?.start);
14239
14395
  if (startEntry) {
14240
- const candidate = import_node_path57.default.resolve(serviceDir, startEntry);
14396
+ const candidate = import_node_path58.default.resolve(serviceDir, startEntry);
14241
14397
  if (await exists3(candidate)) return candidate;
14242
14398
  }
14243
14399
  const devEntry = entryFromScript(pkg.scripts?.dev);
14244
14400
  if (devEntry) {
14245
- const candidate = import_node_path57.default.resolve(serviceDir, devEntry);
14401
+ const candidate = import_node_path58.default.resolve(serviceDir, devEntry);
14246
14402
  if (await exists3(candidate)) return candidate;
14247
14403
  }
14248
14404
  for (const rel of SRC_INDEX_CANDIDATES) {
14249
- const candidate = import_node_path57.default.join(serviceDir, rel);
14405
+ const candidate = import_node_path58.default.join(serviceDir, rel);
14250
14406
  if (await exists3(candidate)) return candidate;
14251
14407
  }
14252
14408
  for (const rel of SRC_NAMED_CANDIDATES) {
14253
- const candidate = import_node_path57.default.join(serviceDir, rel);
14409
+ const candidate = import_node_path58.default.join(serviceDir, rel);
14254
14410
  if (await exists3(candidate)) return candidate;
14255
14411
  }
14256
14412
  for (const rel of ROOT_NAMED_CANDIDATES) {
14257
- const candidate = import_node_path57.default.join(serviceDir, rel);
14413
+ const candidate = import_node_path58.default.join(serviceDir, rel);
14258
14414
  if (await exists3(candidate)) return candidate;
14259
14415
  }
14260
14416
  for (const name of INDEX_CANDIDATES) {
14261
- const candidate = import_node_path57.default.join(serviceDir, name);
14417
+ const candidate = import_node_path58.default.join(serviceDir, name);
14262
14418
  if (await exists3(candidate)) return candidate;
14263
14419
  }
14264
14420
  return null;
14265
14421
  }
14266
14422
  function dispatchEntry(entryFile, pkg) {
14267
- const ext = import_node_path57.default.extname(entryFile).toLowerCase();
14423
+ const ext = import_node_path58.default.extname(entryFile).toLowerCase();
14268
14424
  if (ext === ".ts" || ext === ".tsx") return "ts";
14269
14425
  if (ext === ".mjs") return "esm";
14270
14426
  if (ext === ".cjs") return "cjs";
@@ -14281,9 +14437,9 @@ function otelInitContents(flavor) {
14281
14437
  return OTEL_INIT_CJS;
14282
14438
  }
14283
14439
  function injectionLine(flavor, entryFile, otelInitFile) {
14284
- let rel = import_node_path57.default.relative(import_node_path57.default.dirname(entryFile), otelInitFile);
14440
+ let rel = import_node_path58.default.relative(import_node_path58.default.dirname(entryFile), otelInitFile);
14285
14441
  if (!rel.startsWith(".")) rel = `./${rel}`;
14286
- rel = rel.split(import_node_path57.default.sep).join("/");
14442
+ rel = rel.split(import_node_path58.default.sep).join("/");
14287
14443
  if (flavor === "cjs") return `require('${rel}')`;
14288
14444
  if (flavor === "esm") return `import '${rel}'`;
14289
14445
  const tsRel = rel.replace(/\.ts$/, "");
@@ -14296,27 +14452,27 @@ function lineIsOtelInjection(line) {
14296
14452
  }
14297
14453
  async function detectsSrcLayout(serviceDir) {
14298
14454
  const [hasSrcApp, hasSrcPages, hasRootApp, hasRootPages] = await Promise.all([
14299
- exists3(import_node_path57.default.join(serviceDir, "src", "app")),
14300
- exists3(import_node_path57.default.join(serviceDir, "src", "pages")),
14301
- exists3(import_node_path57.default.join(serviceDir, "app")),
14302
- exists3(import_node_path57.default.join(serviceDir, "pages"))
14455
+ exists3(import_node_path58.default.join(serviceDir, "src", "app")),
14456
+ exists3(import_node_path58.default.join(serviceDir, "src", "pages")),
14457
+ exists3(import_node_path58.default.join(serviceDir, "app")),
14458
+ exists3(import_node_path58.default.join(serviceDir, "pages"))
14303
14459
  ]);
14304
14460
  return (hasSrcApp || hasSrcPages) && !hasRootApp && !hasRootPages;
14305
14461
  }
14306
14462
  async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project) {
14307
14463
  const useTs = await isTypeScriptProject(serviceDir);
14308
14464
  const srcLayout = await detectsSrcLayout(serviceDir);
14309
- const baseDir = srcLayout ? import_node_path57.default.join(serviceDir, "src") : serviceDir;
14310
- const instrumentationFile = import_node_path57.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
14311
- const instrumentationNodeFile = import_node_path57.default.join(
14465
+ const baseDir = srcLayout ? import_node_path58.default.join(serviceDir, "src") : serviceDir;
14466
+ const instrumentationFile = import_node_path58.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
14467
+ const instrumentationNodeFile = import_node_path58.default.join(
14312
14468
  baseDir,
14313
14469
  useTs ? "instrumentation.node.ts" : "instrumentation.node.js"
14314
14470
  );
14315
- const instrumentationEdgeFile = import_node_path57.default.join(
14471
+ const instrumentationEdgeFile = import_node_path58.default.join(
14316
14472
  baseDir,
14317
14473
  useTs ? "instrumentation.edge.ts" : "instrumentation.edge.js"
14318
14474
  );
14319
- const envNeatFile = import_node_path57.default.join(baseDir, ".env.neat");
14475
+ const envNeatFile = import_node_path58.default.join(baseDir, ".env.neat");
14320
14476
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
14321
14477
  const dependencyEdits = [];
14322
14478
  for (const sdk of SDK_PACKAGES) {
@@ -14441,7 +14597,7 @@ function buildDependencyEdits(pkg, manifestPath) {
14441
14597
  return edits;
14442
14598
  }
14443
14599
  async function queueEnvNeat(serviceDir, pkg, project, generatedFiles) {
14444
- const envNeatFile = import_node_path57.default.join(serviceDir, ".env.neat");
14600
+ const envNeatFile = import_node_path58.default.join(serviceDir, ".env.neat");
14445
14601
  if (!await exists3(envNeatFile)) {
14446
14602
  generatedFiles.push({
14447
14603
  file: envNeatFile,
@@ -14476,7 +14632,7 @@ function fileImportsOtelHook(raw, specifiers) {
14476
14632
  }
14477
14633
  async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
14478
14634
  const useTs = await isTypeScriptProject(serviceDir);
14479
- const otelServerFile = import_node_path57.default.join(
14635
+ const otelServerFile = import_node_path58.default.join(
14480
14636
  serviceDir,
14481
14637
  useTs ? "app/otel.server.ts" : "app/otel.server.js"
14482
14638
  );
@@ -14533,11 +14689,11 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
14533
14689
  }
14534
14690
  async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project) {
14535
14691
  const useTs = await isTypeScriptProject(serviceDir);
14536
- const otelInitFile = import_node_path57.default.join(
14692
+ const otelInitFile = import_node_path58.default.join(
14537
14693
  serviceDir,
14538
14694
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
14539
14695
  );
14540
- const resolvedHooksFile = hooksFile ?? import_node_path57.default.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
14696
+ const resolvedHooksFile = hooksFile ?? import_node_path58.default.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
14541
14697
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
14542
14698
  const generatedFiles = [];
14543
14699
  const entrypointEdits = [];
@@ -14599,11 +14755,11 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
14599
14755
  }
14600
14756
  async function planNuxt(serviceDir, pkg, manifestPath, project) {
14601
14757
  const useTs = await isTypeScriptProject(serviceDir);
14602
- const otelPluginFile = import_node_path57.default.join(
14758
+ const otelPluginFile = import_node_path58.default.join(
14603
14759
  serviceDir,
14604
14760
  useTs ? "server/plugins/otel.ts" : "server/plugins/otel.js"
14605
14761
  );
14606
- const otelInitFile = import_node_path57.default.join(
14762
+ const otelInitFile = import_node_path58.default.join(
14607
14763
  serviceDir,
14608
14764
  useTs ? "server/plugins/otel-init.ts" : "server/plugins/otel-init.js"
14609
14765
  );
@@ -14654,19 +14810,19 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
14654
14810
  var ASTRO_MIDDLEWARE_CANDIDATES = ["src/middleware.ts", "src/middleware.js"];
14655
14811
  async function findAstroMiddleware(serviceDir) {
14656
14812
  for (const rel of ASTRO_MIDDLEWARE_CANDIDATES) {
14657
- const candidate = import_node_path57.default.join(serviceDir, rel);
14813
+ const candidate = import_node_path58.default.join(serviceDir, rel);
14658
14814
  if (await exists3(candidate)) return candidate;
14659
14815
  }
14660
14816
  return null;
14661
14817
  }
14662
14818
  async function planAstro(serviceDir, pkg, manifestPath, project) {
14663
14819
  const useTs = await isTypeScriptProject(serviceDir);
14664
- const otelInitFile = import_node_path57.default.join(
14820
+ const otelInitFile = import_node_path58.default.join(
14665
14821
  serviceDir,
14666
14822
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
14667
14823
  );
14668
14824
  const existingMiddleware = await findAstroMiddleware(serviceDir);
14669
- const middlewareFile = existingMiddleware ?? import_node_path57.default.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
14825
+ const middlewareFile = existingMiddleware ?? import_node_path58.default.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
14670
14826
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
14671
14827
  const generatedFiles = [];
14672
14828
  const entrypointEdits = [];
@@ -14762,7 +14918,7 @@ async function findFrameworkDispatch(serviceDir, pkg, manifestPath, project) {
14762
14918
  }
14763
14919
  async function plan(serviceDir, opts) {
14764
14920
  const pkg = await readPackageJson2(serviceDir);
14765
- const manifestPath = import_node_path57.default.join(serviceDir, "package.json");
14921
+ const manifestPath = import_node_path58.default.join(serviceDir, "package.json");
14766
14922
  const project = opts?.project;
14767
14923
  const empty = {
14768
14924
  language: "javascript",
@@ -14797,8 +14953,8 @@ async function plan(serviceDir, opts) {
14797
14953
  return { ...empty, libOnly: true };
14798
14954
  }
14799
14955
  const flavor = dispatchEntry(entryFile, pkg);
14800
- const otelInitFile = import_node_path57.default.join(import_node_path57.default.dirname(entryFile), otelInitFilename(flavor));
14801
- const envNeatFile = import_node_path57.default.join(serviceDir, ".env.neat");
14956
+ const otelInitFile = import_node_path58.default.join(import_node_path58.default.dirname(entryFile), otelInitFilename(flavor));
14957
+ const envNeatFile = import_node_path58.default.join(serviceDir, ".env.neat");
14802
14958
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
14803
14959
  const dependencyEdits = [];
14804
14960
  for (const sdk of SDK_PACKAGES) {
@@ -14867,13 +15023,13 @@ async function plan(serviceDir, opts) {
14867
15023
  };
14868
15024
  }
14869
15025
  function isAllowedWritePath(serviceDir, target) {
14870
- const rel = import_node_path57.default.relative(serviceDir, target);
15026
+ const rel = import_node_path58.default.relative(serviceDir, target);
14871
15027
  if (rel.startsWith("..")) return false;
14872
- const base = import_node_path57.default.basename(target);
15028
+ const base = import_node_path58.default.basename(target);
14873
15029
  if (base === "package.json") return true;
14874
15030
  if (base === ".env.neat") return true;
14875
15031
  if (/^otel-init\.(?:js|cjs|mjs|ts)$/.test(base)) return true;
14876
- const relPosix = rel.split(import_node_path57.default.sep).join("/");
15032
+ const relPosix = rel.split(import_node_path58.default.sep).join("/");
14877
15033
  if (/^instrumentation(?:\.(?:node|edge))?\.(?:js|cjs|mjs|ts)$/.test(base)) {
14878
15034
  if (relPosix === base) return true;
14879
15035
  if (relPosix === `src/${base}`) return true;
@@ -14890,7 +15046,7 @@ function isAllowedWritePath(serviceDir, target) {
14890
15046
  return false;
14891
15047
  }
14892
15048
  async function writeAtomic(file, contents) {
14893
- await import_node_fs37.promises.mkdir(import_node_path57.default.dirname(file), { recursive: true });
15049
+ await import_node_fs37.promises.mkdir(import_node_path58.default.dirname(file), { recursive: true });
14894
15050
  const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
14895
15051
  await import_node_fs37.promises.writeFile(tmp, contents, "utf8");
14896
15052
  await import_node_fs37.promises.rename(tmp, file);
@@ -15074,7 +15230,7 @@ async function rollback(installPlan, originals, createdFiles) {
15074
15230
  ...removed.map((f) => `removed: ${f}`),
15075
15231
  ""
15076
15232
  ];
15077
- const rollbackPath = import_node_path57.default.join(installPlan.serviceDir, "neat-rollback.patch");
15233
+ const rollbackPath = import_node_path58.default.join(installPlan.serviceDir, "neat-rollback.patch");
15078
15234
  await import_node_fs37.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
15079
15235
  }
15080
15236
  function injectInstrumentationHook(raw) {
@@ -15105,7 +15261,7 @@ var javascriptInstaller = {
15105
15261
  // src/installers/python.ts
15106
15262
  init_cjs_shims();
15107
15263
  var import_node_fs38 = require("fs");
15108
- var import_node_path58 = __toESM(require("path"), 1);
15264
+ var import_node_path59 = __toESM(require("path"), 1);
15109
15265
  var SDK_PACKAGES2 = [
15110
15266
  { name: "opentelemetry-distro", version: ">=0.49b0" },
15111
15267
  { name: "opentelemetry-exporter-otlp", version: ">=1.28.0" }
@@ -15115,6 +15271,132 @@ var OTEL_ENV2 = {
15115
15271
  key: "OTEL_EXPORTER_OTLP_ENDPOINT",
15116
15272
  value: "http://localhost:4318"
15117
15273
  };
15274
+ var NEAT_OTEL_FILENAME = "neat_otel.py";
15275
+ var NEAT_OTEL_STAMP = "neat-otel-init v1";
15276
+ var NEAT_IMPORT_LINE = "import neat_otel # neat: call-site file attribution";
15277
+ function neatOtelPy() {
15278
+ return `# ${NEAT_OTEL_STAMP} \u2014 generated by NEAT. Safe to re-generate; do not edit.
15279
+ # Call-site file attribution (docs/contracts/file-awareness.md section 4). Stamps
15280
+ # code.file.path / code.line.number / code.function.name on spans so NEAT fuses
15281
+ # runtime spans onto your source files. Import it once at your entry point.
15282
+ import os
15283
+ import sys
15284
+
15285
+ try:
15286
+ from opentelemetry import trace as _neat_trace
15287
+ from opentelemetry.sdk.trace import SpanProcessor as _NeatSpanProcessor
15288
+ from opentelemetry.trace import SpanKind as _NeatSpanKind
15289
+
15290
+ _NEAT_OTEL = True
15291
+ except Exception:
15292
+ _NEAT_OTEL = False
15293
+
15294
+ if _NEAT_OTEL:
15295
+ _NEAT_SELF = os.path.abspath(__file__)
15296
+ _NEAT_PREFIXES = tuple(
15297
+ os.path.abspath(p) + os.sep
15298
+ for p in {sys.prefix, sys.base_prefix, sys.exec_prefix, sys.base_exec_prefix}
15299
+ )
15300
+ _NEAT_KINDS = {_NeatSpanKind.CLIENT, _NeatSpanKind.PRODUCER, _NeatSpanKind.SERVER}
15301
+
15302
+ def _neat_is_user_frame(filename):
15303
+ if not filename:
15304
+ return False
15305
+ if filename.startswith("<") and filename.endswith(">"):
15306
+ return False
15307
+ abs_name = os.path.abspath(filename)
15308
+ if abs_name == _NEAT_SELF:
15309
+ return False
15310
+ parts = abs_name.split(os.sep)
15311
+ if "opentelemetry" in parts or "site-packages" in parts:
15312
+ return False
15313
+ for pref in _NEAT_PREFIXES:
15314
+ if abs_name.startswith(pref):
15315
+ return False
15316
+ return True
15317
+
15318
+ class NeatCallSiteSpanProcessor(_NeatSpanProcessor):
15319
+ def on_start(self, span, parent_context=None):
15320
+ try:
15321
+ if span.kind not in _NEAT_KINDS:
15322
+ return
15323
+ frame = sys._getframe(1)
15324
+ while frame is not None:
15325
+ if _neat_is_user_frame(frame.f_code.co_filename):
15326
+ span.set_attribute("code.file.path", os.path.abspath(frame.f_code.co_filename))
15327
+ span.set_attribute("code.line.number", frame.f_lineno)
15328
+ span.set_attribute("code.function.name", frame.f_code.co_name)
15329
+ return
15330
+ frame = frame.f_back
15331
+ except Exception:
15332
+ pass # never break the host application
15333
+
15334
+ def on_end(self, span):
15335
+ pass
15336
+
15337
+ def shutdown(self):
15338
+ pass
15339
+
15340
+ def force_flush(self, timeout_millis=30000):
15341
+ return True
15342
+
15343
+ def _neat_register():
15344
+ try:
15345
+ provider = _neat_trace.get_tracer_provider()
15346
+ add = getattr(provider, "add_span_processor", None)
15347
+ if callable(add):
15348
+ add(NeatCallSiteSpanProcessor())
15349
+ except Exception:
15350
+ pass
15351
+
15352
+ if os.environ.get("NEAT_CALLSITE_DISABLED", "") != "1":
15353
+ _neat_register()
15354
+ `;
15355
+ }
15356
+ async function writeFileAtomic(file, contents) {
15357
+ const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
15358
+ await import_node_fs38.promises.writeFile(tmp, contents, "utf8");
15359
+ await import_node_fs38.promises.rename(tmp, file);
15360
+ }
15361
+ async function resolvePyEntrypoint(serviceDir) {
15362
+ const procfile = import_node_path59.default.join(serviceDir, "Procfile");
15363
+ if (await exists4(procfile)) {
15364
+ const raw = await import_node_fs38.promises.readFile(procfile, "utf8");
15365
+ for (const line of raw.split(/\r?\n/)) {
15366
+ const m = line.match(/^[a-zA-Z0-9_-]+:\s*(.+)$/);
15367
+ if (!m) continue;
15368
+ const cmd = m[1];
15369
+ const asgi = cmd.match(/\b(?:uvicorn|gunicorn|hypercorn|daphne)\s+([\w.]+):/);
15370
+ if (asgi) {
15371
+ const modPath = asgi[1].replace(/\./g, "/");
15372
+ const asFile = import_node_path59.default.join(serviceDir, `${modPath}.py`);
15373
+ if (await exists4(asFile)) return asFile;
15374
+ const asPkg = import_node_path59.default.join(serviceDir, modPath, "__init__.py");
15375
+ if (await exists4(asPkg)) return asPkg;
15376
+ }
15377
+ const runFile = cmd.match(/\b(?:python3?|fastapi\s+(?:run|dev))\s+([\w./-]+\.py)\b/);
15378
+ if (runFile) {
15379
+ const p = import_node_path59.default.join(serviceDir, runFile[1]);
15380
+ if (await exists4(p)) return p;
15381
+ }
15382
+ }
15383
+ }
15384
+ for (const name of ["main.py", "app.py", "asgi.py", "wsgi.py", "manage.py", "server.py"]) {
15385
+ const p = import_node_path59.default.join(serviceDir, name);
15386
+ if (await exists4(p)) return p;
15387
+ }
15388
+ return null;
15389
+ }
15390
+ function injectNeatImport(source) {
15391
+ if (/^\s*import\s+neat_otel\b/m.test(source)) return null;
15392
+ const lines = source.split("\n");
15393
+ let insertAt = lines[0]?.startsWith("#!") ? 1 : 0;
15394
+ for (let i = 0; i < Math.min(lines.length, 15); i++) {
15395
+ if (/^\s*from\s+__future__\s+import\b/.test(lines[i] ?? "")) insertAt = i + 1;
15396
+ }
15397
+ lines.splice(insertAt, 0, NEAT_IMPORT_LINE);
15398
+ return lines.join("\n");
15399
+ }
15118
15400
  async function exists4(p) {
15119
15401
  try {
15120
15402
  await import_node_fs38.promises.stat(p);
@@ -15126,7 +15408,7 @@ async function exists4(p) {
15126
15408
  async function detect2(serviceDir) {
15127
15409
  const markers = ["requirements.txt", "pyproject.toml", "setup.py"];
15128
15410
  for (const m of markers) {
15129
- if (await exists4(import_node_path58.default.join(serviceDir, m))) return true;
15411
+ if (await exists4(import_node_path59.default.join(serviceDir, m))) return true;
15130
15412
  }
15131
15413
  return false;
15132
15414
  }
@@ -15136,7 +15418,7 @@ function reqPackageName(line) {
15136
15418
  return head.replace(/[<>=!~].*$/, "").toLowerCase();
15137
15419
  }
15138
15420
  async function planRequirementsTxtEdits(serviceDir) {
15139
- const file = import_node_path58.default.join(serviceDir, "requirements.txt");
15421
+ const file = import_node_path59.default.join(serviceDir, "requirements.txt");
15140
15422
  if (!await exists4(file)) return null;
15141
15423
  const raw = await import_node_fs38.promises.readFile(file, "utf8");
15142
15424
  const presentNames = new Set(
@@ -15146,7 +15428,7 @@ async function planRequirementsTxtEdits(serviceDir) {
15146
15428
  return { manifest: file, missing: [...missing] };
15147
15429
  }
15148
15430
  async function planProcfileEdits(serviceDir) {
15149
- const procfile = import_node_path58.default.join(serviceDir, "Procfile");
15431
+ const procfile = import_node_path59.default.join(serviceDir, "Procfile");
15150
15432
  if (!await exists4(procfile)) return [];
15151
15433
  const raw = await import_node_fs38.promises.readFile(procfile, "utf8");
15152
15434
  const edits = [];
@@ -15183,7 +15465,9 @@ async function plan2(serviceDir) {
15183
15465
  }
15184
15466
  }
15185
15467
  const entrypointEdits = await planProcfileEdits(serviceDir);
15186
- if (dependencyEdits.length === 0 && entrypointEdits.length === 0) {
15468
+ const entryFile = await resolvePyEntrypoint(serviceDir);
15469
+ const generatedFiles = entryFile ? [{ file: import_node_path59.default.join(serviceDir, NEAT_OTEL_FILENAME), contents: neatOtelPy() }] : [];
15470
+ if (dependencyEdits.length === 0 && entrypointEdits.length === 0 && !entryFile) {
15187
15471
  return empty;
15188
15472
  }
15189
15473
  return {
@@ -15191,7 +15475,9 @@ async function plan2(serviceDir) {
15191
15475
  serviceDir,
15192
15476
  dependencyEdits,
15193
15477
  entrypointEdits,
15194
- envEdits: [OTEL_ENV2]
15478
+ envEdits: [OTEL_ENV2],
15479
+ ...generatedFiles.length > 0 ? { generatedFiles } : {},
15480
+ ...entryFile ? { entryFile } : {}
15195
15481
  };
15196
15482
  }
15197
15483
  async function applyRequirementsTxt(manifest, edits, original) {
@@ -15214,11 +15500,13 @@ async function applyProcfile(procfile, edits, original) {
15214
15500
  await import_node_fs38.promises.rename(tmp, procfile);
15215
15501
  }
15216
15502
  async function apply2(installPlan) {
15217
- const { serviceDir } = installPlan;
15503
+ const { serviceDir, entryFile } = installPlan;
15504
+ const generatedFiles = installPlan.generatedFiles ?? [];
15218
15505
  const touched = /* @__PURE__ */ new Set();
15219
15506
  for (const e of installPlan.dependencyEdits) touched.add(e.file);
15220
15507
  for (const e of installPlan.entrypointEdits) touched.add(e.file);
15221
- if (touched.size === 0) {
15508
+ if (entryFile) touched.add(entryFile);
15509
+ if (touched.size === 0 && generatedFiles.length === 0) {
15222
15510
  return { serviceDir, outcome: "already-instrumented", writtenFiles: [] };
15223
15511
  }
15224
15512
  const originals = /* @__PURE__ */ new Map();
@@ -15229,13 +15517,24 @@ async function apply2(installPlan) {
15229
15517
  }
15230
15518
  }
15231
15519
  const writtenFiles = [];
15520
+ const createdFiles = [];
15232
15521
  try {
15522
+ for (const gf of generatedFiles) {
15523
+ const already = await exists4(gf.file);
15524
+ if (already) {
15525
+ const cur = await import_node_fs38.promises.readFile(gf.file, "utf8").catch(() => null);
15526
+ if (cur === gf.contents) continue;
15527
+ }
15528
+ await writeFileAtomic(gf.file, gf.contents);
15529
+ writtenFiles.push(gf.file);
15530
+ if (!already) createdFiles.push(gf.file);
15531
+ }
15233
15532
  for (const file of touched) {
15234
15533
  const raw = originals.get(file);
15235
15534
  if (raw === void 0) {
15236
15535
  throw new Error(`python installer: cannot read ${file} during apply`);
15237
15536
  }
15238
- const base = import_node_path58.default.basename(file);
15537
+ const base = import_node_path59.default.basename(file);
15239
15538
  if (base === "requirements.txt") {
15240
15539
  const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
15241
15540
  if (edits.length > 0) {
@@ -15248,15 +15547,22 @@ async function apply2(installPlan) {
15248
15547
  await applyProcfile(file, edits, raw);
15249
15548
  writtenFiles.push(file);
15250
15549
  }
15550
+ } else if (file === entryFile) {
15551
+ const injected = injectNeatImport(raw);
15552
+ if (injected !== null) {
15553
+ await writeFileAtomic(file, injected);
15554
+ writtenFiles.push(file);
15555
+ }
15251
15556
  }
15252
15557
  }
15253
15558
  } catch (err) {
15254
- await rollback2(installPlan, originals);
15559
+ await rollback2(installPlan, originals, createdFiles);
15255
15560
  throw err;
15256
15561
  }
15257
- return { serviceDir, outcome: "instrumented", writtenFiles };
15562
+ const outcome = writtenFiles.length > 0 ? "instrumented" : "already-instrumented";
15563
+ return { serviceDir, outcome, writtenFiles };
15258
15564
  }
15259
- async function rollback2(installPlan, originals) {
15565
+ async function rollback2(installPlan, originals, createdFiles = []) {
15260
15566
  const restored = [];
15261
15567
  for (const [file, raw] of originals.entries()) {
15262
15568
  try {
@@ -15265,6 +15571,14 @@ async function rollback2(installPlan, originals) {
15265
15571
  } catch {
15266
15572
  }
15267
15573
  }
15574
+ const removed = [];
15575
+ for (const file of createdFiles) {
15576
+ try {
15577
+ await import_node_fs38.promises.rm(file, { force: true });
15578
+ removed.push(file);
15579
+ } catch {
15580
+ }
15581
+ }
15268
15582
  const lines = [
15269
15583
  "# neat-rollback.patch",
15270
15584
  "",
@@ -15272,9 +15586,10 @@ async function rollback2(installPlan, originals) {
15272
15586
  "# Files listed below were restored to their pre-apply contents.",
15273
15587
  "",
15274
15588
  ...restored.map((f) => `restored: ${f}`),
15589
+ ...removed.map((f) => `removed: ${f}`),
15275
15590
  ""
15276
15591
  ];
15277
- const rollbackPath = import_node_path58.default.join(installPlan.serviceDir, "neat-rollback.patch");
15592
+ const rollbackPath = import_node_path59.default.join(installPlan.serviceDir, "neat-rollback.patch");
15278
15593
  await import_node_fs38.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
15279
15594
  }
15280
15595
  var pythonInstaller = {
@@ -15401,7 +15716,7 @@ init_cjs_shims();
15401
15716
  var import_node_fs39 = require("fs");
15402
15717
  var import_node_http = __toESM(require("http"), 1);
15403
15718
  var import_node_net = __toESM(require("net"), 1);
15404
- var import_node_path59 = __toESM(require("path"), 1);
15719
+ var import_node_path60 = __toESM(require("path"), 1);
15405
15720
  var import_node_url4 = require("url");
15406
15721
  var import_node_child_process3 = require("child_process");
15407
15722
  var import_node_readline = __toESM(require("readline"), 1);
@@ -15411,7 +15726,7 @@ async function extractAndPersist(opts) {
15411
15726
  const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
15412
15727
  resetGraph(graphKey);
15413
15728
  const graph = getGraph(graphKey);
15414
- const projectPaths = pathsForProject(graphKey, import_node_path59.default.join(opts.scanPath, "neat-out"));
15729
+ const projectPaths = pathsForProject(graphKey, import_node_path60.default.join(opts.scanPath, "neat-out"));
15415
15730
  const extraction = await extractFromDirectory(graph, opts.scanPath, {
15416
15731
  errorsPath: projectPaths.errorsPath
15417
15732
  });
@@ -15463,7 +15778,7 @@ async function applyInstallersOver(services, project, options = {}) {
15463
15778
  libOnly++;
15464
15779
  const appDeps = svc.pkg ? appFrameworkDependencies(svc.pkg) : [];
15465
15780
  if (appDeps.length > 0) {
15466
- const svcName = import_node_path59.default.basename(svc.dir);
15781
+ const svcName = import_node_path60.default.basename(svc.dir);
15467
15782
  const list = appDeps.join(", ");
15468
15783
  console.warn(
15469
15784
  `neat: runtime layer won't engage for ${svcName}: no entry point found.
@@ -15476,7 +15791,7 @@ async function applyInstallersOver(services, project, options = {}) {
15476
15791
  console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
15477
15792
  } else if (outcome.outcome === "react-native") {
15478
15793
  reactNative++;
15479
- const svcName = import_node_path59.default.basename(svc.dir);
15794
+ const svcName = import_node_path60.default.basename(svc.dir);
15480
15795
  console.log(
15481
15796
  `neat: ${svc.dir} detected as React Native / Expo
15482
15797
  The installer doesn't cover this runtime deterministically.
@@ -15487,7 +15802,7 @@ async function applyInstallersOver(services, project, options = {}) {
15487
15802
  );
15488
15803
  } else if (outcome.outcome === "bun") {
15489
15804
  bun++;
15490
- const svcName = import_node_path59.default.basename(svc.dir);
15805
+ const svcName = import_node_path60.default.basename(svc.dir);
15491
15806
  console.log(
15492
15807
  `neat: ${svc.dir} detected as Bun
15493
15808
  The installer doesn't cover this runtime deterministically.
@@ -15498,7 +15813,7 @@ async function applyInstallersOver(services, project, options = {}) {
15498
15813
  );
15499
15814
  } else if (outcome.outcome === "deno") {
15500
15815
  deno++;
15501
- const svcName = import_node_path59.default.basename(svc.dir);
15816
+ const svcName = import_node_path60.default.basename(svc.dir);
15502
15817
  console.log(
15503
15818
  `neat: ${svc.dir} detected as Deno
15504
15819
  The installer doesn't cover this runtime deterministically.
@@ -15509,7 +15824,7 @@ async function applyInstallersOver(services, project, options = {}) {
15509
15824
  );
15510
15825
  } else if (outcome.outcome === "cloudflare-workers") {
15511
15826
  cloudflareWorkers++;
15512
- const svcName = import_node_path59.default.basename(svc.dir);
15827
+ const svcName = import_node_path60.default.basename(svc.dir);
15513
15828
  console.log(
15514
15829
  `neat: ${svc.dir} detected as Cloudflare Workers
15515
15830
  The installer doesn't cover this runtime deterministically.
@@ -15520,7 +15835,7 @@ async function applyInstallersOver(services, project, options = {}) {
15520
15835
  );
15521
15836
  } else if (outcome.outcome === "electron") {
15522
15837
  electron++;
15523
- const svcName = import_node_path59.default.basename(svc.dir);
15838
+ const svcName = import_node_path60.default.basename(svc.dir);
15524
15839
  console.log(
15525
15840
  `neat: ${svc.dir} detected as Electron
15526
15841
  The installer doesn't cover this runtime deterministically.
@@ -15533,7 +15848,7 @@ async function applyInstallersOver(services, project, options = {}) {
15533
15848
  if (svc.pkg && (outcome.outcome === "instrumented" || outcome.outcome === "already-instrumented")) {
15534
15849
  const gaps = uninstrumentedLibraries(svc.pkg);
15535
15850
  if (gaps.length > 0) {
15536
- const svcName = import_node_path59.default.basename(svc.dir);
15851
+ const svcName = import_node_path60.default.basename(svc.dir);
15537
15852
  const list = gaps.join(", ");
15538
15853
  const subject = gaps.length === 1 ? "this library" : "these libraries";
15539
15854
  const aux = gaps.length === 1 ? "isn't" : "aren't";
@@ -15739,8 +16054,8 @@ async function persistedPortsFor(scanPath) {
15739
16054
  return { rest: record.ports.rest, otlp: record.ports.otlp, web: record.ports.web };
15740
16055
  }
15741
16056
  async function acquireSpawnLock(scanPath) {
15742
- const lockPath = import_node_path59.default.join(scanPath, "neat-out", "daemon.spawn.lock");
15743
- await import_node_fs39.promises.mkdir(import_node_path59.default.dirname(lockPath), { recursive: true });
16057
+ const lockPath = import_node_path60.default.join(scanPath, "neat-out", "daemon.spawn.lock");
16058
+ await import_node_fs39.promises.mkdir(import_node_path60.default.dirname(lockPath), { recursive: true });
15744
16059
  const STALE_LOCK_MS = 6e4;
15745
16060
  try {
15746
16061
  const fd = await import_node_fs39.promises.open(lockPath, "wx");
@@ -15785,13 +16100,13 @@ async function healthIsForProject(restPort, project) {
15785
16100
  return false;
15786
16101
  }
15787
16102
  function daemonLogPath(projectPath2) {
15788
- return import_node_path59.default.join(projectPath2, "neat-out", "daemon.log");
16103
+ return import_node_path60.default.join(projectPath2, "neat-out", "daemon.log");
15789
16104
  }
15790
16105
  function spawnDaemonDetached(spec) {
15791
- const here = import_node_path59.default.dirname((0, import_node_url4.fileURLToPath)(importMetaUrl));
16106
+ const here = import_node_path60.default.dirname((0, import_node_url4.fileURLToPath)(importMetaUrl));
15792
16107
  const candidates = [
15793
- import_node_path59.default.join(here, "neatd.cjs"),
15794
- import_node_path59.default.join(here, "neatd.js")
16108
+ import_node_path60.default.join(here, "neatd.cjs"),
16109
+ import_node_path60.default.join(here, "neatd.js")
15795
16110
  ];
15796
16111
  let entry2 = null;
15797
16112
  const fsSync = require("fs");
@@ -15821,7 +16136,7 @@ function spawnDaemonDetached(spec) {
15821
16136
  let logFd = null;
15822
16137
  if (spec) {
15823
16138
  const logPath = daemonLogPath(spec.projectPath);
15824
- fsSync.mkdirSync(import_node_path59.default.dirname(logPath), { recursive: true });
16139
+ fsSync.mkdirSync(import_node_path60.default.dirname(logPath), { recursive: true });
15825
16140
  logFd = fsSync.openSync(logPath, "a");
15826
16141
  }
15827
16142
  const child = (0, import_node_child_process3.spawn)(process.execPath, [entry2, "start"], {
@@ -16020,7 +16335,7 @@ async function runOrchestrator(opts) {
16020
16335
  result.steps.browser = openBrowser(dashboardUrl);
16021
16336
  }
16022
16337
  const daemonRunning = result.steps.daemon === "spawned" || result.steps.daemon === "already-running";
16023
- const daemonLog = daemonRunning ? import_node_path59.default.relative(opts.scanPath, daemonLogPath(opts.scanPath)) : null;
16338
+ const daemonLog = daemonRunning ? import_node_path60.default.relative(opts.scanPath, daemonLogPath(opts.scanPath)) : null;
16024
16339
  printSummary(result, graph, dashboardUrl, daemonLog);
16025
16340
  return result;
16026
16341
  }
@@ -16477,7 +16792,7 @@ async function runConnectorCommand(rawArgs, deps = {}) {
16477
16792
 
16478
16793
  // src/hooks-cli.ts
16479
16794
  init_cjs_shims();
16480
- var import_node_path60 = __toESM(require("path"), 1);
16795
+ var import_node_path61 = __toESM(require("path"), 1);
16481
16796
  var import_node_os5 = __toESM(require("os"), 1);
16482
16797
  var import_node_fs40 = require("fs");
16483
16798
  var import_node_url5 = require("url");
@@ -16486,14 +16801,14 @@ var GUIDE_FILENAME = "GRAPH_FIRST.md";
16486
16801
  var GUIDE_INSTALL_NAME = "neat-graph-first.md";
16487
16802
  var HOOK_MATCHER = "Grep|Glob|Bash";
16488
16803
  function moduleDir() {
16489
- return typeof __dirname !== "undefined" ? __dirname : import_node_path60.default.dirname((0, import_node_url5.fileURLToPath)(importMetaUrl));
16804
+ return typeof __dirname !== "undefined" ? __dirname : import_node_path61.default.dirname((0, import_node_url5.fileURLToPath)(importMetaUrl));
16490
16805
  }
16491
16806
  async function readSkillAsset(rel) {
16492
16807
  const here = moduleDir();
16493
16808
  const candidates = [
16494
- import_node_path60.default.resolve(here, "../../claude-skill", rel),
16495
- import_node_path60.default.resolve(here, "../../../claude-skill", rel),
16496
- import_node_path60.default.resolve(here, "../claude-skill", rel)
16809
+ import_node_path61.default.resolve(here, "../../claude-skill", rel),
16810
+ import_node_path61.default.resolve(here, "../../../claude-skill", rel),
16811
+ import_node_path61.default.resolve(here, "../claude-skill", rel)
16497
16812
  ];
16498
16813
  for (const candidate of candidates) {
16499
16814
  try {
@@ -16507,17 +16822,17 @@ async function readSkillAsset(rel) {
16507
16822
  }
16508
16823
  function neatHome3() {
16509
16824
  const override = process.env.NEAT_HOME;
16510
- if (override && override.length > 0) return import_node_path60.default.resolve(override);
16511
- return import_node_path60.default.join(import_node_os5.default.homedir(), ".neat");
16825
+ if (override && override.length > 0) return import_node_path61.default.resolve(override);
16826
+ return import_node_path61.default.join(import_node_os5.default.homedir(), ".neat");
16512
16827
  }
16513
16828
  function claudeSettingsPath() {
16514
16829
  const override = process.env.NEAT_CLAUDE_SETTINGS;
16515
- if (override && override.length > 0) return import_node_path60.default.resolve(override);
16830
+ if (override && override.length > 0) return import_node_path61.default.resolve(override);
16516
16831
  const home = process.env.HOME ?? process.env.USERPROFILE ?? import_node_os5.default.homedir();
16517
- return import_node_path60.default.join(home, ".claude", "settings.json");
16832
+ return import_node_path61.default.join(home, ".claude", "settings.json");
16518
16833
  }
16519
16834
  function installedHookPath() {
16520
- return import_node_path60.default.join(neatHome3(), "hooks", HOOK_FILENAME);
16835
+ return import_node_path61.default.join(neatHome3(), "hooks", HOOK_FILENAME);
16521
16836
  }
16522
16837
  function isNeatSearchEntry(entry2) {
16523
16838
  return (entry2.hooks ?? []).some(
@@ -16550,9 +16865,9 @@ async function runHooks(opts) {
16550
16865
  const hookScript = await readSkillAsset(`hooks/${HOOK_FILENAME}`);
16551
16866
  const guide = await readSkillAsset(GUIDE_FILENAME);
16552
16867
  const scriptPath = installedHookPath();
16553
- await import_node_fs40.promises.mkdir(import_node_path60.default.dirname(scriptPath), { recursive: true });
16868
+ await import_node_fs40.promises.mkdir(import_node_path61.default.dirname(scriptPath), { recursive: true });
16554
16869
  await import_node_fs40.promises.writeFile(scriptPath, hookScript, { mode: 493 });
16555
- const guidePath = import_node_path60.default.join(neatHome3(), GUIDE_INSTALL_NAME);
16870
+ const guidePath = import_node_path61.default.join(neatHome3(), GUIDE_INSTALL_NAME);
16556
16871
  await import_node_fs40.promises.writeFile(guidePath, guide, "utf8");
16557
16872
  const settingsFile = claudeSettingsPath();
16558
16873
  let settings = {};
@@ -16579,7 +16894,7 @@ async function runHooks(opts) {
16579
16894
  ...settings,
16580
16895
  hooks: { ...hooks, PreToolUse: preToolUse }
16581
16896
  };
16582
- await import_node_fs40.promises.mkdir(import_node_path60.default.dirname(settingsFile), { recursive: true });
16897
+ await import_node_fs40.promises.mkdir(import_node_path61.default.dirname(settingsFile), { recursive: true });
16583
16898
  await import_node_fs40.promises.writeFile(settingsFile, JSON.stringify(merged, null, 2) + "\n", "utf8");
16584
16899
  console.log(`neat hooks: installed the search-nudge hook`);
16585
16900
  console.log(` script: ${scriptPath}`);
@@ -16651,11 +16966,11 @@ async function runHooksCommand(args) {
16651
16966
 
16652
16967
  // src/cli-verbs.ts
16653
16968
  init_cjs_shims();
16654
- var import_node_path61 = __toESM(require("path"), 1);
16969
+ var import_node_path62 = __toESM(require("path"), 1);
16655
16970
 
16656
16971
  // src/cli-client.ts
16657
16972
  init_cjs_shims();
16658
- var import_types49 = require("@neat.is/types");
16973
+ var import_types50 = require("@neat.is/types");
16659
16974
  var HttpError = class extends Error {
16660
16975
  constructor(status2, message, responseBody = "") {
16661
16976
  super(message);
@@ -16680,10 +16995,10 @@ function createHttpClient(baseUrl, bearerToken) {
16680
16995
  const root = baseUrl.replace(/\/$/, "");
16681
16996
  const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
16682
16997
  return {
16683
- async get(path63) {
16998
+ async get(path64) {
16684
16999
  let res;
16685
17000
  try {
16686
- res = await fetch(`${root}${path63}`, {
17001
+ res = await fetch(`${root}${path64}`, {
16687
17002
  headers: { ...authHeader }
16688
17003
  });
16689
17004
  } catch (err) {
@@ -16695,16 +17010,16 @@ function createHttpClient(baseUrl, bearerToken) {
16695
17010
  const body = await res.text().catch(() => "");
16696
17011
  throw new HttpError(
16697
17012
  res.status,
16698
- `${res.status} ${res.statusText} on GET ${path63}: ${body}`,
17013
+ `${res.status} ${res.statusText} on GET ${path64}: ${body}`,
16699
17014
  body
16700
17015
  );
16701
17016
  }
16702
17017
  return await res.json();
16703
17018
  },
16704
- async post(path63, body) {
17019
+ async post(path64, body) {
16705
17020
  let res;
16706
17021
  try {
16707
- res = await fetch(`${root}${path63}`, {
17022
+ res = await fetch(`${root}${path64}`, {
16708
17023
  method: "POST",
16709
17024
  headers: { "content-type": "application/json", ...authHeader },
16710
17025
  body: JSON.stringify(body)
@@ -16718,7 +17033,7 @@ function createHttpClient(baseUrl, bearerToken) {
16718
17033
  const text = await res.text().catch(() => "");
16719
17034
  throw new HttpError(
16720
17035
  res.status,
16721
- `${res.status} ${res.statusText} on POST ${path63}: ${text}`,
17036
+ `${res.status} ${res.statusText} on POST ${path64}: ${text}`,
16722
17037
  text
16723
17038
  );
16724
17039
  }
@@ -16732,12 +17047,12 @@ function projectPath(project, suffix) {
16732
17047
  }
16733
17048
  async function runRootCause(client, input) {
16734
17049
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
16735
- const path63 = projectPath(
17050
+ const path64 = projectPath(
16736
17051
  input.project,
16737
17052
  `/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
16738
17053
  );
16739
17054
  try {
16740
- const result = await client.get(path63);
17055
+ const result = await client.get(path64);
16741
17056
  const arrowPath = result.traversalPath.join(" \u2190 ");
16742
17057
  const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
16743
17058
  const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
@@ -16763,12 +17078,12 @@ async function runRootCause(client, input) {
16763
17078
  }
16764
17079
  async function runBlastRadius(client, input) {
16765
17080
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
16766
- const path63 = projectPath(
17081
+ const path64 = projectPath(
16767
17082
  input.project,
16768
17083
  `/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
16769
17084
  );
16770
17085
  try {
16771
- const result = await client.get(path63);
17086
+ const result = await client.get(path64);
16772
17087
  if (result.totalAffected === 0) {
16773
17088
  return {
16774
17089
  summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
@@ -16797,17 +17112,17 @@ async function runBlastRadius(client, input) {
16797
17112
  }
16798
17113
  }
16799
17114
  function formatBlastEntry(n) {
16800
- const tag = n.edgeProvenance === import_types49.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
17115
+ const tag = n.edgeProvenance === import_types50.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
16801
17116
  return ` \u2022 ${n.nodeId} (distance ${n.distance}, ${n.edgeProvenance})${tag}`;
16802
17117
  }
16803
17118
  async function runDependencies(client, input) {
16804
17119
  const depth = input.depth ?? 3;
16805
- const path63 = projectPath(
17120
+ const path64 = projectPath(
16806
17121
  input.project,
16807
17122
  `/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
16808
17123
  );
16809
17124
  try {
16810
- const result = await client.get(path63);
17125
+ const result = await client.get(path64);
16811
17126
  if (result.total === 0) {
16812
17127
  return {
16813
17128
  summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
@@ -16854,7 +17169,7 @@ async function runObservedDependencies(client, input) {
16854
17169
  if (result.observed) {
16855
17170
  return {
16856
17171
  summary: `${input.nodeId} makes no outbound runtime calls, but OTel has observed it receiving traffic on ${result.inboundObservedCount} inbound call path${result.inboundObservedCount === 1 ? "" : "s"} \u2014 it's a pure receiver.`,
16857
- provenance: import_types49.Provenance.OBSERVED
17172
+ provenance: import_types50.Provenance.OBSERVED
16858
17173
  };
16859
17174
  }
16860
17175
  const note = result.hasExtractedOutbound ? " Static (EXTRACTED) dependencies exist but no runtime traffic has been seen \u2014 is OTel running?" : "";
@@ -16864,7 +17179,7 @@ async function runObservedDependencies(client, input) {
16864
17179
  return {
16865
17180
  summary: `${input.nodeId} has ${result.dependencies.length} runtime dependenc${result.dependencies.length === 1 ? "y" : "ies"} confirmed by OTel.`,
16866
17181
  block: blockLines.join("\n"),
16867
- provenance: import_types49.Provenance.OBSERVED
17182
+ provenance: import_types50.Provenance.OBSERVED
16868
17183
  };
16869
17184
  } catch (err) {
16870
17185
  if (err instanceof HttpError && err.status === 404) {
@@ -16899,9 +17214,9 @@ function formatDuration(ms) {
16899
17214
  return `${Math.round(h / 24)}d`;
16900
17215
  }
16901
17216
  async function runIncidents(client, input) {
16902
- const path63 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
17217
+ const path64 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
16903
17218
  try {
16904
- const body = await client.get(path63);
17219
+ const body = await client.get(path64);
16905
17220
  const events = body.events;
16906
17221
  if (events.length === 0) {
16907
17222
  return {
@@ -16918,7 +17233,7 @@ async function runIncidents(client, input) {
16918
17233
  return {
16919
17234
  summary: `${target} has ${body.total} recorded incident${body.total === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
16920
17235
  block: blockLines.join("\n"),
16921
- provenance: import_types49.Provenance.OBSERVED
17236
+ provenance: import_types50.Provenance.OBSERVED
16922
17237
  };
16923
17238
  } catch (err) {
16924
17239
  if (err instanceof HttpError && err.status === 404) {
@@ -17027,7 +17342,7 @@ async function runStaleEdges(client, input) {
17027
17342
  return {
17028
17343
  summary: `${events.length} stale-edge transition${events.length === 1 ? "" : "s"} recorded${input.edgeType ? ` for ${input.edgeType}` : ""}.`,
17029
17344
  block: blockLines.join("\n"),
17030
- provenance: import_types49.Provenance.STALE
17345
+ provenance: import_types50.Provenance.STALE
17031
17346
  };
17032
17347
  }
17033
17348
  async function runPolicies(client, input) {
@@ -17191,7 +17506,7 @@ async function resolveProjectEntry(opts) {
17191
17506
  const cwd = opts.cwd ?? process.cwd();
17192
17507
  const resolvedCwd = await normalizeProjectPath(cwd);
17193
17508
  for (const entry2 of entries) {
17194
- if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path61.default.sep}`)) {
17509
+ if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path62.default.sep}`)) {
17195
17510
  return entry2;
17196
17511
  }
17197
17512
  }
@@ -17344,7 +17659,7 @@ async function runSync(opts) {
17344
17659
  }
17345
17660
 
17346
17661
  // src/cli.ts
17347
- var import_types50 = require("@neat.is/types");
17662
+ var import_types51 = require("@neat.is/types");
17348
17663
  function isNpxInvocation() {
17349
17664
  if (process.env.npm_command === "exec") return true;
17350
17665
  const execpath = process.env.npm_execpath ?? "";
@@ -17655,12 +17970,12 @@ async function runInit(opts) {
17655
17970
  printDiscoveryReport(opts, services);
17656
17971
  const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
17657
17972
  const patch = renderPatch(sections);
17658
- const patchPath = import_node_path62.default.join(opts.scanPath, "neat.patch");
17973
+ const patchPath = import_node_path63.default.join(opts.scanPath, "neat.patch");
17659
17974
  if (opts.dryRun) {
17660
17975
  await import_node_fs41.promises.writeFile(patchPath, patch, "utf8");
17661
17976
  written.push(patchPath);
17662
17977
  console.log(`dry-run: patch written to ${patchPath}`);
17663
- const gitignorePath = import_node_path62.default.join(opts.scanPath, ".gitignore");
17978
+ const gitignorePath = import_node_path63.default.join(opts.scanPath, ".gitignore");
17664
17979
  const gitignoreExists = await import_node_fs41.promises.stat(gitignorePath).then(() => true).catch(() => false);
17665
17980
  const verb = gitignoreExists ? "append" : "create";
17666
17981
  console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
@@ -17672,9 +17987,9 @@ async function runInit(opts) {
17672
17987
  const graph = getGraph(graphKey);
17673
17988
  const projectPaths = pathsForProject(
17674
17989
  graphKey,
17675
- import_node_path62.default.join(opts.scanPath, "neat-out")
17990
+ import_node_path63.default.join(opts.scanPath, "neat-out")
17676
17991
  );
17677
- const errorsPath = import_node_path62.default.join(import_node_path62.default.dirname(opts.outPath), import_node_path62.default.basename(projectPaths.errorsPath));
17992
+ const errorsPath = import_node_path63.default.join(import_node_path63.default.dirname(opts.outPath), import_node_path63.default.basename(projectPaths.errorsPath));
17678
17993
  const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
17679
17994
  await saveGraphToDisk(graph, opts.outPath);
17680
17995
  written.push(opts.outPath);
@@ -17793,9 +18108,9 @@ var CLAUDE_SKILL_CONFIG = {
17793
18108
  };
17794
18109
  function claudeConfigPath() {
17795
18110
  const override = process.env.NEAT_CLAUDE_CONFIG;
17796
- if (override && override.length > 0) return import_node_path62.default.resolve(override);
18111
+ if (override && override.length > 0) return import_node_path63.default.resolve(override);
17797
18112
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
17798
- return import_node_path62.default.join(home, ".claude.json");
18113
+ return import_node_path63.default.join(home, ".claude.json");
17799
18114
  }
17800
18115
  async function runSkill(opts) {
17801
18116
  const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
@@ -17819,7 +18134,7 @@ async function runSkill(opts) {
17819
18134
  ...existing,
17820
18135
  mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
17821
18136
  };
17822
- await import_node_fs41.promises.mkdir(import_node_path62.default.dirname(target), { recursive: true });
18137
+ await import_node_fs41.promises.mkdir(import_node_path63.default.dirname(target), { recursive: true });
17823
18138
  await import_node_fs41.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
17824
18139
  console.log(`neat skill: wrote mcpServers.neat to ${target}`);
17825
18140
  console.log("restart Claude Code to pick up the new MCP server.");
@@ -17885,12 +18200,12 @@ async function main() {
17885
18200
  console.error("neat init: --apply and --dry-run are mutually exclusive");
17886
18201
  process.exit(2);
17887
18202
  }
17888
- const scanPath = import_node_path62.default.resolve(target);
18203
+ const scanPath = import_node_path63.default.resolve(target);
17889
18204
  const projectExplicit = parsed.project !== null;
17890
- const projectName = projectExplicit ? project : import_node_path62.default.basename(scanPath);
18205
+ const projectName = projectExplicit ? project : import_node_path63.default.basename(scanPath);
17891
18206
  const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
17892
- const fallback = pathsForProject(projectKey, import_node_path62.default.join(scanPath, "neat-out")).snapshotPath;
17893
- const outPath = import_node_path62.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
18207
+ const fallback = pathsForProject(projectKey, import_node_path63.default.join(scanPath, "neat-out")).snapshotPath;
18208
+ const outPath = import_node_path63.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
17894
18209
  const result = await runInit({
17895
18210
  scanPath,
17896
18211
  outPath,
@@ -17911,21 +18226,21 @@ async function main() {
17911
18226
  usage2();
17912
18227
  process.exit(2);
17913
18228
  }
17914
- const scanPath = import_node_path62.default.resolve(target);
18229
+ const scanPath = import_node_path63.default.resolve(target);
17915
18230
  const stat = await import_node_fs41.promises.stat(scanPath).catch(() => null);
17916
18231
  if (!stat || !stat.isDirectory()) {
17917
18232
  console.error(`neat watch: ${scanPath} is not a directory`);
17918
18233
  process.exit(2);
17919
18234
  }
17920
- const projectPaths = pathsForProject(project, import_node_path62.default.join(scanPath, "neat-out"));
17921
- const outPath = import_node_path62.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
17922
- const errorsPath = import_node_path62.default.resolve(
17923
- process.env.NEAT_ERRORS_PATH ?? import_node_path62.default.join(import_node_path62.default.dirname(outPath), import_node_path62.default.basename(projectPaths.errorsPath))
18235
+ const projectPaths = pathsForProject(project, import_node_path63.default.join(scanPath, "neat-out"));
18236
+ const outPath = import_node_path63.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
18237
+ const errorsPath = import_node_path63.default.resolve(
18238
+ process.env.NEAT_ERRORS_PATH ?? import_node_path63.default.join(import_node_path63.default.dirname(outPath), import_node_path63.default.basename(projectPaths.errorsPath))
17924
18239
  );
17925
- const staleEventsPath = import_node_path62.default.resolve(
17926
- process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path62.default.join(import_node_path62.default.dirname(outPath), import_node_path62.default.basename(projectPaths.staleEventsPath))
18240
+ const staleEventsPath = import_node_path63.default.resolve(
18241
+ process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path63.default.join(import_node_path63.default.dirname(outPath), import_node_path63.default.basename(projectPaths.staleEventsPath))
17927
18242
  );
17928
- const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path62.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
18243
+ const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path63.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
17929
18244
  const handle = await startWatch(getGraph(project), {
17930
18245
  scanPath,
17931
18246
  outPath,
@@ -18108,11 +18423,11 @@ async function main() {
18108
18423
  process.exit(1);
18109
18424
  }
18110
18425
  async function tryOrchestrator(cmd, parsed) {
18111
- const scanPath = import_node_path62.default.resolve(cmd);
18426
+ const scanPath = import_node_path63.default.resolve(cmd);
18112
18427
  const stat = await import_node_fs41.promises.stat(scanPath).catch(() => null);
18113
18428
  if (!stat || !stat.isDirectory()) return null;
18114
18429
  const projectExplicit = parsed.project !== null;
18115
- const projectName = projectExplicit ? parsed.project : import_node_path62.default.basename(scanPath);
18430
+ const projectName = projectExplicit ? parsed.project : import_node_path63.default.basename(scanPath);
18116
18431
  const result = await runOrchestrator({
18117
18432
  scanPath,
18118
18433
  project: projectName,
@@ -18301,10 +18616,10 @@ async function runQueryVerb(cmd, parsed) {
18301
18616
  const parts = parsed.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
18302
18617
  const out = [];
18303
18618
  for (const p of parts) {
18304
- const r = import_types50.DivergenceTypeSchema.safeParse(p);
18619
+ const r = import_types51.DivergenceTypeSchema.safeParse(p);
18305
18620
  if (!r.success) {
18306
18621
  console.error(
18307
- `neat divergences: unknown --type "${p}". allowed: ${import_types50.DivergenceTypeSchema.options.join(", ")}`
18622
+ `neat divergences: unknown --type "${p}". allowed: ${import_types51.DivergenceTypeSchema.options.join(", ")}`
18308
18623
  );
18309
18624
  return 2;
18310
18625
  }