@neat.is/core 0.4.15 → 0.4.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -58,9 +58,9 @@ function mountBearerAuth(app, opts) {
58
58
  const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
59
59
  const publicRead = opts.publicRead === true;
60
60
  app.addHook("preHandler", (req, reply, done) => {
61
- const path49 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
61
+ const path51 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
62
62
  for (const suffix of suffixes) {
63
- if (path49 === suffix || path49.endsWith(suffix)) {
63
+ if (path51 === suffix || path51.endsWith(suffix)) {
64
64
  done();
65
65
  return;
66
66
  }
@@ -188,8 +188,8 @@ function reshapeGrpcRequest(req) {
188
188
  };
189
189
  }
190
190
  function resolveProtoRoot() {
191
- const here = import_node_path39.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
192
- return import_node_path39.default.resolve(here, "..", "proto");
191
+ const here = import_node_path41.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
192
+ return import_node_path41.default.resolve(here, "..", "proto");
193
193
  }
194
194
  function loadTraceService() {
195
195
  const protoRoot = resolveProtoRoot();
@@ -257,13 +257,13 @@ async function startOtelGrpcReceiver(opts) {
257
257
  })
258
258
  };
259
259
  }
260
- var import_node_url, import_node_path39, import_node_crypto2, grpc, protoLoader;
260
+ var import_node_url2, import_node_path41, import_node_crypto2, grpc, protoLoader;
261
261
  var init_otel_grpc = __esm({
262
262
  "src/otel-grpc.ts"() {
263
263
  "use strict";
264
264
  init_cjs_shims();
265
- import_node_url = require("url");
266
- import_node_path39 = __toESM(require("path"), 1);
265
+ import_node_url2 = require("url");
266
+ import_node_path41 = __toESM(require("path"), 1);
267
267
  import_node_crypto2 = require("crypto");
268
268
  grpc = __toESM(require("@grpc/grpc-js"), 1);
269
269
  protoLoader = __toESM(require("@grpc/proto-loader"), 1);
@@ -373,10 +373,10 @@ function parseOtlpRequest(body) {
373
373
  return out;
374
374
  }
375
375
  function loadProtoRoot() {
376
- const here = import_node_path40.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
377
- const protoRoot = import_node_path40.default.resolve(here, "..", "proto");
376
+ const here = import_node_path42.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
377
+ const protoRoot = import_node_path42.default.resolve(here, "..", "proto");
378
378
  const root = new import_protobufjs.default.Root();
379
- root.resolvePath = (_origin, target) => import_node_path40.default.resolve(protoRoot, target);
379
+ root.resolvePath = (_origin, target) => import_node_path42.default.resolve(protoRoot, target);
380
380
  root.loadSync(
381
381
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
382
382
  { keepCase: true }
@@ -572,13 +572,13 @@ async function buildOtelReceiver(opts) {
572
572
  };
573
573
  return decorated;
574
574
  }
575
- var import_node_path40, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
575
+ var import_node_path42, import_node_url3, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
576
576
  var init_otel = __esm({
577
577
  "src/otel.ts"() {
578
578
  "use strict";
579
579
  init_cjs_shims();
580
- import_node_path40 = __toESM(require("path"), 1);
581
- import_node_url2 = require("url");
580
+ import_node_path42 = __toESM(require("path"), 1);
581
+ import_node_url3 = require("url");
582
582
  import_fastify2 = __toESM(require("fastify"), 1);
583
583
  import_protobufjs = __toESM(require("protobufjs"), 1);
584
584
  init_auth();
@@ -595,19 +595,60 @@ var init_otel = __esm({
595
595
  var cli_exports = {};
596
596
  __export(cli_exports, {
597
597
  CLAUDE_SKILL_CONFIG: () => CLAUDE_SKILL_CONFIG,
598
+ ProjectResolutionError: () => ProjectResolutionError,
598
599
  QUERY_VERBS: () => QUERY_VERBS,
600
+ commandPrefix: () => commandPrefix,
601
+ isNpxInvocation: () => isNpxInvocation,
602
+ main: () => main,
599
603
  parseArgs: () => parseArgs,
600
604
  printBanner: () => printBanner,
601
605
  readPackageVersion: () => readPackageVersion,
606
+ resolveProjectForVerb: () => resolveProjectForVerb,
602
607
  runInit: () => runInit,
603
608
  runQueryVerb: () => runQueryVerb,
604
- runSkill: () => runSkill
609
+ runSkill: () => runSkill,
610
+ usage: () => usage
605
611
  });
606
612
  module.exports = __toCommonJS(cli_exports);
607
613
  init_cjs_shims();
608
- var import_node_path48 = __toESM(require("path"), 1);
609
- var import_node_fs31 = require("fs");
610
- var import_node_url3 = require("url");
614
+ var import_node_path50 = __toESM(require("path"), 1);
615
+ var import_node_fs32 = require("fs");
616
+
617
+ // src/banner.ts
618
+ init_cjs_shims();
619
+ var import_node_path = __toESM(require("path"), 1);
620
+ var import_node_fs = require("fs");
621
+ var import_node_url = require("url");
622
+ function readPackageVersion() {
623
+ const here = typeof __dirname !== "undefined" ? __dirname : import_node_path.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
624
+ const candidates = [
625
+ import_node_path.default.resolve(here, "../package.json"),
626
+ import_node_path.default.resolve(here, "../../package.json")
627
+ ];
628
+ for (const candidate of candidates) {
629
+ try {
630
+ const raw = (0, import_node_fs.readFileSync)(candidate, "utf8");
631
+ const parsed = JSON.parse(raw);
632
+ if (parsed.name === "@neat.is/core" && typeof parsed.version === "string") {
633
+ return parsed.version;
634
+ }
635
+ } catch {
636
+ }
637
+ }
638
+ return "unknown";
639
+ }
640
+ function printBanner() {
641
+ console.log("\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557");
642
+ console.log("\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D");
643
+ console.log("\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 ");
644
+ console.log("\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 ");
645
+ console.log("\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 ");
646
+ console.log("\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D ");
647
+ console.log("");
648
+ console.log(" Network Expressive Architecting Tool");
649
+ console.log(` neat.is \xB7 v${readPackageVersion()} \xB7 Apache 2.0`);
650
+ console.log("");
651
+ }
611
652
 
612
653
  // src/graph.ts
613
654
  init_cjs_shims();
@@ -642,21 +683,21 @@ init_cjs_shims();
642
683
 
643
684
  // src/ingest.ts
644
685
  init_cjs_shims();
645
- var import_node_fs3 = require("fs");
646
- var import_node_path3 = __toESM(require("path"), 1);
686
+ var import_node_fs4 = require("fs");
687
+ var import_node_path4 = __toESM(require("path"), 1);
647
688
  var sourceMapJs = __toESM(require("source-map-js"), 1);
648
689
 
649
690
  // src/policy.ts
650
691
  init_cjs_shims();
651
- var import_node_fs2 = require("fs");
652
- var import_node_path2 = __toESM(require("path"), 1);
692
+ var import_node_fs3 = require("fs");
693
+ var import_node_path3 = __toESM(require("path"), 1);
653
694
  var import_types2 = require("@neat.is/types");
654
695
 
655
696
  // src/compat.ts
656
697
  init_cjs_shims();
657
- var import_node_fs = require("fs");
698
+ var import_node_fs2 = require("fs");
658
699
  var import_node_os = __toESM(require("os"), 1);
659
- var import_node_path = __toESM(require("path"), 1);
700
+ var import_node_path2 = __toESM(require("path"), 1);
660
701
  var import_semver = __toESM(require("semver"), 1);
661
702
 
662
703
  // compat.json
@@ -785,8 +826,8 @@ var compat_default = {
785
826
  var bundledMatrix = compat_default;
786
827
  var mergedMatrix = null;
787
828
  var remoteLoadAttempted = false;
788
- var REMOTE_CACHE_DIR = import_node_path.default.join(import_node_os.default.homedir(), ".neat");
789
- var REMOTE_CACHE_PATH = import_node_path.default.join(REMOTE_CACHE_DIR, "compat-cache.json");
829
+ var REMOTE_CACHE_DIR = import_node_path2.default.join(import_node_os.default.homedir(), ".neat");
830
+ var REMOTE_CACHE_PATH = import_node_path2.default.join(REMOTE_CACHE_DIR, "compat-cache.json");
790
831
  var REMOTE_TTL_MS = 24 * 60 * 60 * 1e3;
791
832
  function engineMeetsThreshold(engineVersion, threshold) {
792
833
  const e = parseInt(engineVersion, 10);
@@ -897,7 +938,7 @@ function mergeMatrices(a, b) {
897
938
  }
898
939
  async function readRemoteCache(url) {
899
940
  try {
900
- const raw = await import_node_fs.promises.readFile(REMOTE_CACHE_PATH, "utf8");
941
+ const raw = await import_node_fs2.promises.readFile(REMOTE_CACHE_PATH, "utf8");
901
942
  const parsed = JSON.parse(raw);
902
943
  if (parsed.url !== url) return null;
903
944
  const age = Date.now() - new Date(parsed.fetchedAt).getTime();
@@ -914,8 +955,8 @@ async function writeRemoteCache(url, matrix) {
914
955
  matrix
915
956
  };
916
957
  try {
917
- await import_node_fs.promises.mkdir(REMOTE_CACHE_DIR, { recursive: true });
918
- await import_node_fs.promises.writeFile(REMOTE_CACHE_PATH, JSON.stringify(file), "utf8");
958
+ await import_node_fs2.promises.mkdir(REMOTE_CACHE_DIR, { recursive: true });
959
+ await import_node_fs2.promises.writeFile(REMOTE_CACHE_PATH, JSON.stringify(file), "utf8");
919
960
  } catch (err) {
920
961
  console.warn(`[neat] failed to cache compat matrix: ${err.message}`);
921
962
  }
@@ -1136,19 +1177,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1136
1177
  function longestIncomingWalk(graph, start, maxDepth) {
1137
1178
  let best = { path: [start], edges: [] };
1138
1179
  const visited = /* @__PURE__ */ new Set([start]);
1139
- function step(node, path49, edges) {
1140
- if (path49.length > best.path.length) {
1141
- best = { path: [...path49], edges: [...edges] };
1180
+ function step(node, path51, edges) {
1181
+ if (path51.length > best.path.length) {
1182
+ best = { path: [...path51], edges: [...edges] };
1142
1183
  }
1143
- if (path49.length - 1 >= maxDepth) return;
1184
+ if (path51.length - 1 >= maxDepth) return;
1144
1185
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1145
1186
  for (const [srcId, edge] of incoming) {
1146
1187
  if (visited.has(srcId)) continue;
1147
1188
  visited.add(srcId);
1148
- path49.push(srcId);
1189
+ path51.push(srcId);
1149
1190
  edges.push(edge);
1150
- step(srcId, path49, edges);
1151
- path49.pop();
1191
+ step(srcId, path51, edges);
1192
+ path51.pop();
1152
1193
  edges.pop();
1153
1194
  visited.delete(srcId);
1154
1195
  }
@@ -1607,7 +1648,7 @@ function evaluateAllPolicies(graph, policies, ctx) {
1607
1648
  async function loadPolicyFile(policyPath) {
1608
1649
  let raw;
1609
1650
  try {
1610
- raw = await import_node_fs2.promises.readFile(policyPath, "utf8");
1651
+ raw = await import_node_fs3.promises.readFile(policyPath, "utf8");
1611
1652
  } catch (err) {
1612
1653
  if (err.code === "ENOENT") return [];
1613
1654
  throw err;
@@ -1628,8 +1669,8 @@ var PolicyViolationsLog = class {
1628
1669
  if (!this.seen) await this.hydrate();
1629
1670
  if (this.seen.has(v.id)) return false;
1630
1671
  this.seen.add(v.id);
1631
- await import_node_fs2.promises.mkdir(import_node_path2.default.dirname(this.path), { recursive: true });
1632
- await import_node_fs2.promises.appendFile(this.path, JSON.stringify(v) + "\n", "utf8");
1672
+ await import_node_fs3.promises.mkdir(import_node_path3.default.dirname(this.path), { recursive: true });
1673
+ await import_node_fs3.promises.appendFile(this.path, JSON.stringify(v) + "\n", "utf8");
1633
1674
  emitNeatEvent({
1634
1675
  type: "policy-violation",
1635
1676
  project: this.project,
@@ -1639,7 +1680,7 @@ var PolicyViolationsLog = class {
1639
1680
  }
1640
1681
  async readAll() {
1641
1682
  try {
1642
- const raw = await import_node_fs2.promises.readFile(this.path, "utf8");
1683
+ const raw = await import_node_fs3.promises.readFile(this.path, "utf8");
1643
1684
  return raw.split("\n").filter(Boolean).map((line) => JSON.parse(line));
1644
1685
  } catch (err) {
1645
1686
  if (err.code === "ENOENT") return [];
@@ -1688,6 +1729,41 @@ function thresholdForEdgeType(edgeType, overrides) {
1688
1729
  const map = overrides ?? loadStaleThresholdsFromEnv();
1689
1730
  return map[edgeType] ?? FALLBACK_STALE_THRESHOLD_MS;
1690
1731
  }
1732
+ var DEFAULT_INCIDENT_THRESHOLDS = {
1733
+ threshold: 5,
1734
+ windowMs: 6e4
1735
+ };
1736
+ function loadIncidentThresholdsFromEnv() {
1737
+ const raw = process.env.NEAT_INCIDENT_THRESHOLDS;
1738
+ if (!raw) return DEFAULT_INCIDENT_THRESHOLDS;
1739
+ try {
1740
+ const overrides = JSON.parse(raw);
1741
+ const merged = { ...DEFAULT_INCIDENT_THRESHOLDS };
1742
+ if (typeof overrides.threshold === "number" && Number.isFinite(overrides.threshold) && overrides.threshold >= 1) {
1743
+ merged.threshold = Math.floor(overrides.threshold);
1744
+ }
1745
+ if (typeof overrides.windowMs === "number" && Number.isFinite(overrides.windowMs) && overrides.windowMs >= 0) {
1746
+ merged.windowMs = overrides.windowMs;
1747
+ }
1748
+ return merged;
1749
+ } catch (err) {
1750
+ console.warn(
1751
+ `[neat] NEAT_INCIDENT_THRESHOLDS could not be parsed (${err.message}); using defaults`
1752
+ );
1753
+ return DEFAULT_INCIDENT_THRESHOLDS;
1754
+ }
1755
+ }
1756
+ function httpResponseStatus(span) {
1757
+ for (const key of ["http.response.status_code", "http.status_code"]) {
1758
+ const v = span.attributes[key];
1759
+ if (typeof v === "number" && Number.isFinite(v)) return v;
1760
+ if (typeof v === "string") {
1761
+ const n = Number(v);
1762
+ if (Number.isFinite(n)) return n;
1763
+ }
1764
+ }
1765
+ return void 0;
1766
+ }
1691
1767
  function nowIso(ctx) {
1692
1768
  return new Date(ctx.now ? ctx.now() : Date.now()).toISOString();
1693
1769
  }
@@ -1752,7 +1828,7 @@ function languageForExt(relPath) {
1752
1828
  function relPathForRuntimeFile(filepath, serviceNode, scanPath) {
1753
1829
  let p = toPosix(filepath).replace(/^file:\/\//, "");
1754
1830
  if (scanPath && scanPath.length > 0) {
1755
- const absRoot = toPosix(import_node_path3.default.resolve(scanPath, serviceNode?.repoPath ?? ""));
1831
+ const absRoot = toPosix(import_node_path4.default.resolve(scanPath, serviceNode?.repoPath ?? ""));
1756
1832
  const anchor = absRoot.endsWith("/") ? absRoot : `${absRoot}/`;
1757
1833
  if (p.startsWith(anchor)) return p.slice(anchor.length);
1758
1834
  }
@@ -1780,10 +1856,10 @@ function resolveDistToSrc(absFilepath, line) {
1780
1856
  entry2 = null;
1781
1857
  const mapPath = `${absFilepath}.map`;
1782
1858
  try {
1783
- if ((0, import_node_fs3.existsSync)(mapPath)) {
1784
- const raw = JSON.parse((0, import_node_fs3.readFileSync)(mapPath, "utf8"));
1859
+ if ((0, import_node_fs4.existsSync)(mapPath)) {
1860
+ const raw = JSON.parse((0, import_node_fs4.readFileSync)(mapPath, "utf8"));
1785
1861
  const consumer = new sourceMapJs.SourceMapConsumer(raw);
1786
- entry2 = { consumer, dir: import_node_path3.default.dirname(mapPath) };
1862
+ entry2 = { consumer, dir: import_node_path4.default.dirname(mapPath) };
1787
1863
  }
1788
1864
  } catch {
1789
1865
  entry2 = null;
@@ -1798,7 +1874,7 @@ function resolveDistToSrc(absFilepath, line) {
1798
1874
  });
1799
1875
  if (!pos || !pos.source) return null;
1800
1876
  const root = entry2.consumer.sourceRoot ?? "";
1801
- const resolved = import_node_path3.default.resolve(entry2.dir, root, pos.source);
1877
+ const resolved = import_node_path4.default.resolve(entry2.dir, root, pos.source);
1802
1878
  return { filepath: resolved, ...pos.line ? { line: pos.line } : {} };
1803
1879
  } catch {
1804
1880
  return null;
@@ -2065,8 +2141,8 @@ function upsertInferredEdge(graph, type, source, target, ts) {
2065
2141
  graph.addEdgeWithKey(id, source, target, edge);
2066
2142
  }
2067
2143
  async function appendErrorEvent(ctx, ev) {
2068
- await import_node_fs3.promises.mkdir(import_node_path3.default.dirname(ctx.errorsPath), { recursive: true });
2069
- await import_node_fs3.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
2144
+ await import_node_fs4.promises.mkdir(import_node_path4.default.dirname(ctx.errorsPath), { recursive: true });
2145
+ await import_node_fs4.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
2070
2146
  }
2071
2147
  function sanitizeAttributes(attrs) {
2072
2148
  const out = {};
@@ -2097,10 +2173,75 @@ function makeErrorSpanWriter(errorsPath) {
2097
2173
  return async (span) => {
2098
2174
  const ev = buildErrorEventForReceiver(span);
2099
2175
  if (!ev) return;
2100
- await import_node_fs3.promises.mkdir(import_node_path3.default.dirname(errorsPath), { recursive: true });
2101
- await import_node_fs3.promises.appendFile(errorsPath, JSON.stringify(ev) + "\n", "utf8");
2176
+ await import_node_fs4.promises.mkdir(import_node_path4.default.dirname(errorsPath), { recursive: true });
2177
+ await import_node_fs4.promises.appendFile(errorsPath, JSON.stringify(ev) + "\n", "utf8");
2102
2178
  };
2103
2179
  }
2180
+ async function recordFailingResponseIncident(ctx, span, affectedNode, timestamp, statusCode, count, firstTimestamp) {
2181
+ const attrs = sanitizeAttributes(span.attributes);
2182
+ const first = firstTimestamp ?? timestamp;
2183
+ const peer = pickAddress(span);
2184
+ const message = count > 1 ? `${count} consecutive HTTP ${statusCode} responses` + (peer ? ` to ${peer}` : "") : `HTTP ${statusCode} response` + (peer ? ` from ${peer}` : "");
2185
+ const ev = {
2186
+ id: `${span.traceId}:${span.spanId}`,
2187
+ timestamp,
2188
+ service: span.service,
2189
+ traceId: span.traceId,
2190
+ spanId: span.spanId,
2191
+ errorType: "http-failure",
2192
+ errorMessage: message,
2193
+ ...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
2194
+ affectedNode,
2195
+ httpStatusCode: statusCode,
2196
+ incidentCount: count,
2197
+ firstTimestamp: first,
2198
+ lastTimestamp: timestamp
2199
+ };
2200
+ await appendErrorEvent(ctx, ev);
2201
+ }
2202
+ async function advance4xxBurst(ctx, span, affectedNode, ts, nowMs, status2) {
2203
+ const { threshold, windowMs } = loadIncidentThresholdsFromEnv();
2204
+ if (!ctx.burstState) ctx.burstState = /* @__PURE__ */ new Map();
2205
+ const peer = pickAddress(span) ?? span.spanId;
2206
+ const key = `${span.service}->${peer}`;
2207
+ const existing = ctx.burstState.get(key);
2208
+ let state;
2209
+ if (existing && nowMs - existing.lastMs <= windowMs) {
2210
+ existing.count += 1;
2211
+ existing.lastTs = ts;
2212
+ existing.lastMs = nowMs;
2213
+ existing.codes.set(status2, (existing.codes.get(status2) ?? 0) + 1);
2214
+ state = existing;
2215
+ } else {
2216
+ state = {
2217
+ count: 1,
2218
+ firstTs: ts,
2219
+ lastTs: ts,
2220
+ lastMs: nowMs,
2221
+ codes: /* @__PURE__ */ new Map([[status2, 1]])
2222
+ };
2223
+ ctx.burstState.set(key, state);
2224
+ }
2225
+ if (state.count < threshold) return;
2226
+ let dominant = status2;
2227
+ let max = 0;
2228
+ for (const [code, n] of state.codes) {
2229
+ if (n > max) {
2230
+ max = n;
2231
+ dominant = code;
2232
+ }
2233
+ }
2234
+ await recordFailingResponseIncident(
2235
+ ctx,
2236
+ span,
2237
+ affectedNode,
2238
+ state.lastTs,
2239
+ dominant,
2240
+ state.count,
2241
+ state.firstTs
2242
+ );
2243
+ ctx.burstState.delete(key);
2244
+ }
2104
2245
  async function handleSpan(ctx, span) {
2105
2246
  const ts = span.startTimeIso ?? nowIso(ctx);
2106
2247
  const nowMs = ctx.now ? ctx.now() : Date.now();
@@ -2199,6 +2340,14 @@ async function handleSpan(ctx, span) {
2199
2340
  await appendErrorEvent(ctx, ev);
2200
2341
  }
2201
2342
  }
2343
+ if (span.statusCode !== 2) {
2344
+ const status2 = httpResponseStatus(span);
2345
+ if (status2 !== void 0 && status2 >= 500) {
2346
+ await recordFailingResponseIncident(ctx, span, sourceId, ts, status2, 1);
2347
+ } else if (status2 !== void 0 && status2 >= 400 && spanMintsObservedEdge(span.kind)) {
2348
+ await advance4xxBurst(ctx, span, sourceId, ts, nowMs, status2);
2349
+ }
2350
+ }
2202
2351
  void affectedNode;
2203
2352
  if (ctx.onPolicyTrigger) await ctx.onPolicyTrigger(ctx.graph);
2204
2353
  }
@@ -2317,13 +2466,13 @@ async function markStaleEdges(graph, options = {}) {
2317
2466
  return { count: events.length, events };
2318
2467
  }
2319
2468
  async function appendStaleEvents(staleEventsPath, events) {
2320
- await import_node_fs3.promises.mkdir(import_node_path3.default.dirname(staleEventsPath), { recursive: true });
2469
+ await import_node_fs4.promises.mkdir(import_node_path4.default.dirname(staleEventsPath), { recursive: true });
2321
2470
  const lines = events.map((e) => JSON.stringify(e)).join("\n") + "\n";
2322
- await import_node_fs3.promises.appendFile(staleEventsPath, lines, "utf8");
2471
+ await import_node_fs4.promises.appendFile(staleEventsPath, lines, "utf8");
2323
2472
  }
2324
2473
  async function readStaleEvents(staleEventsPath) {
2325
2474
  try {
2326
- const raw = await import_node_fs3.promises.readFile(staleEventsPath, "utf8");
2475
+ const raw = await import_node_fs4.promises.readFile(staleEventsPath, "utf8");
2327
2476
  return raw.split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
2328
2477
  } catch (err) {
2329
2478
  if (err.code === "ENOENT") return [];
@@ -2357,7 +2506,7 @@ function startStalenessLoop(graph, options = {}) {
2357
2506
  }
2358
2507
  async function readErrorEvents(errorsPath) {
2359
2508
  try {
2360
- const raw = await import_node_fs3.promises.readFile(errorsPath, "utf8");
2509
+ const raw = await import_node_fs4.promises.readFile(errorsPath, "utf8");
2361
2510
  return raw.split("\n").filter((line) => line.length > 0).map((line) => JSON.parse(line));
2362
2511
  } catch (err) {
2363
2512
  if (err.code === "ENOENT") return [];
@@ -2389,16 +2538,16 @@ function mergeSnapshot(graph, snapshot) {
2389
2538
 
2390
2539
  // src/extract/services.ts
2391
2540
  init_cjs_shims();
2392
- var import_node_fs8 = require("fs");
2393
- var import_node_path8 = __toESM(require("path"), 1);
2541
+ var import_node_fs9 = require("fs");
2542
+ var import_node_path9 = __toESM(require("path"), 1);
2394
2543
  var import_ignore = __toESM(require("ignore"), 1);
2395
2544
  var import_minimatch2 = require("minimatch");
2396
2545
  var import_types5 = require("@neat.is/types");
2397
2546
 
2398
2547
  // src/extract/shared.ts
2399
2548
  init_cjs_shims();
2400
- var import_node_fs4 = require("fs");
2401
- var import_node_path4 = __toESM(require("path"), 1);
2549
+ var import_node_fs5 = require("fs");
2550
+ var import_node_path5 = __toESM(require("path"), 1);
2402
2551
  var import_yaml = require("yaml");
2403
2552
  var import_types4 = require("@neat.is/types");
2404
2553
  var SERVICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".py"]);
@@ -2430,7 +2579,7 @@ async function isPythonVenvDir(dir) {
2430
2579
  const cached = PYVENV_MARKER_CACHE.get(dir);
2431
2580
  if (cached !== void 0) return cached;
2432
2581
  try {
2433
- const stat = await import_node_fs4.promises.stat(import_node_path4.default.join(dir, "pyvenv.cfg"));
2582
+ const stat = await import_node_fs5.promises.stat(import_node_path5.default.join(dir, "pyvenv.cfg"));
2434
2583
  const ok = stat.isFile();
2435
2584
  PYVENV_MARKER_CACHE.set(dir, ok);
2436
2585
  return ok;
@@ -2440,7 +2589,7 @@ async function isPythonVenvDir(dir) {
2440
2589
  }
2441
2590
  }
2442
2591
  function isConfigFile(name) {
2443
- const ext = import_node_path4.default.extname(name);
2592
+ const ext = import_node_path5.default.extname(name);
2444
2593
  if (CONFIG_FILE_EXTENSIONS.has(ext)) return { match: true, fileType: ext.slice(1) };
2445
2594
  if (name === ".env" || name.startsWith(".env.")) {
2446
2595
  if (isEnvTemplateFile(name)) return { match: false, fileType: "" };
@@ -2548,16 +2697,16 @@ function cleanVersion(raw) {
2548
2697
  return raw.replace(/^[\^~><=v\s]+/, "").trim() || void 0;
2549
2698
  }
2550
2699
  async function readJson(filePath) {
2551
- const raw = await import_node_fs4.promises.readFile(filePath, "utf8");
2700
+ const raw = await import_node_fs5.promises.readFile(filePath, "utf8");
2552
2701
  return JSON.parse(raw);
2553
2702
  }
2554
2703
  async function readYaml(filePath) {
2555
- const raw = await import_node_fs4.promises.readFile(filePath, "utf8");
2704
+ const raw = await import_node_fs5.promises.readFile(filePath, "utf8");
2556
2705
  return (0, import_yaml.parse)(raw);
2557
2706
  }
2558
2707
  async function exists(p) {
2559
2708
  try {
2560
- await import_node_fs4.promises.access(p);
2709
+ await import_node_fs5.promises.access(p);
2561
2710
  return true;
2562
2711
  } catch {
2563
2712
  return false;
@@ -2566,8 +2715,8 @@ async function exists(p) {
2566
2715
 
2567
2716
  // src/extract/python.ts
2568
2717
  init_cjs_shims();
2569
- var import_node_fs5 = require("fs");
2570
- var import_node_path5 = __toESM(require("path"), 1);
2718
+ var import_node_fs6 = require("fs");
2719
+ var import_node_path6 = __toESM(require("path"), 1);
2571
2720
  var import_smol_toml = require("smol-toml");
2572
2721
  var REQUIREMENT_LINE = /^\s*([A-Za-z0-9_.-]+)(?:\[[^\]]*\])?\s*(?:(==)\s*([A-Za-z0-9_.+-]+))?/;
2573
2722
  function parseRequirementsTxt(content) {
@@ -2600,25 +2749,25 @@ function depsFromPyProject(pyproject) {
2600
2749
  return out;
2601
2750
  }
2602
2751
  async function discoverPythonService(serviceDir) {
2603
- const pyprojectPath = import_node_path5.default.join(serviceDir, "pyproject.toml");
2604
- const requirementsPath = import_node_path5.default.join(serviceDir, "requirements.txt");
2605
- const setupPath = import_node_path5.default.join(serviceDir, "setup.py");
2752
+ const pyprojectPath = import_node_path6.default.join(serviceDir, "pyproject.toml");
2753
+ const requirementsPath = import_node_path6.default.join(serviceDir, "requirements.txt");
2754
+ const setupPath = import_node_path6.default.join(serviceDir, "setup.py");
2606
2755
  const hasPyproject = await exists(pyprojectPath);
2607
2756
  const hasRequirements = await exists(requirementsPath);
2608
2757
  const hasSetup = await exists(setupPath);
2609
2758
  if (!hasPyproject && !hasRequirements && !hasSetup) return null;
2610
- let name = import_node_path5.default.basename(serviceDir);
2759
+ let name = import_node_path6.default.basename(serviceDir);
2611
2760
  let version;
2612
2761
  const dependencies = {};
2613
2762
  if (hasPyproject) {
2614
- const raw = await import_node_fs5.promises.readFile(pyprojectPath, "utf8");
2763
+ const raw = await import_node_fs6.promises.readFile(pyprojectPath, "utf8");
2615
2764
  const pyproject = (0, import_smol_toml.parse)(raw);
2616
2765
  name = pyproject.project?.name ?? pyproject.tool?.poetry?.name ?? name;
2617
2766
  version = pyproject.project?.version ?? pyproject.tool?.poetry?.version ?? void 0;
2618
2767
  Object.assign(dependencies, depsFromPyProject(pyproject));
2619
2768
  }
2620
2769
  if (hasRequirements) {
2621
- const raw = await import_node_fs5.promises.readFile(requirementsPath, "utf8");
2770
+ const raw = await import_node_fs6.promises.readFile(requirementsPath, "utf8");
2622
2771
  Object.assign(dependencies, parseRequirementsTxt(raw));
2623
2772
  }
2624
2773
  return { name, version, dependencies };
@@ -2633,17 +2782,17 @@ function pythonToPackage(service) {
2633
2782
 
2634
2783
  // src/extract/owners.ts
2635
2784
  init_cjs_shims();
2636
- var import_node_fs6 = require("fs");
2637
- var import_node_path6 = __toESM(require("path"), 1);
2785
+ var import_node_fs7 = require("fs");
2786
+ var import_node_path7 = __toESM(require("path"), 1);
2638
2787
  var import_minimatch = require("minimatch");
2639
2788
  async function loadCodeowners(scanPath) {
2640
2789
  const candidates = [
2641
- import_node_path6.default.join(scanPath, "CODEOWNERS"),
2642
- import_node_path6.default.join(scanPath, ".github", "CODEOWNERS")
2790
+ import_node_path7.default.join(scanPath, "CODEOWNERS"),
2791
+ import_node_path7.default.join(scanPath, ".github", "CODEOWNERS")
2643
2792
  ];
2644
2793
  for (const file of candidates) {
2645
2794
  if (await exists(file)) {
2646
- const raw = await import_node_fs6.promises.readFile(file, "utf8");
2795
+ const raw = await import_node_fs7.promises.readFile(file, "utf8");
2647
2796
  return parseCodeowners(raw);
2648
2797
  }
2649
2798
  }
@@ -2661,7 +2810,7 @@ function parseCodeowners(raw) {
2661
2810
  return { rules };
2662
2811
  }
2663
2812
  function matchOwner(file, repoPath) {
2664
- const normalized = repoPath.split(import_node_path6.default.sep).join("/");
2813
+ const normalized = repoPath.split(import_node_path7.default.sep).join("/");
2665
2814
  for (const rule of file.rules) {
2666
2815
  if (matchesPattern(rule.pattern, normalized)) return rule.owners;
2667
2816
  }
@@ -2677,7 +2826,7 @@ function matchesPattern(rawPattern, repoPath) {
2677
2826
  return false;
2678
2827
  }
2679
2828
  async function readPackageJsonAuthor(serviceDir) {
2680
- const pkgPath = import_node_path6.default.join(serviceDir, "package.json");
2829
+ const pkgPath = import_node_path7.default.join(serviceDir, "package.json");
2681
2830
  if (!await exists(pkgPath)) return null;
2682
2831
  try {
2683
2832
  const pkg = await readJson(pkgPath);
@@ -2700,8 +2849,8 @@ async function computeServiceOwner(codeowners, repoPath, serviceDir) {
2700
2849
 
2701
2850
  // src/extract/errors.ts
2702
2851
  init_cjs_shims();
2703
- var import_node_fs7 = require("fs");
2704
- var import_node_path7 = __toESM(require("path"), 1);
2852
+ var import_node_fs8 = require("fs");
2853
+ var import_node_path8 = __toESM(require("path"), 1);
2705
2854
  var sink = [];
2706
2855
  function recordExtractionError(producer, file, err) {
2707
2856
  const e = err instanceof Error ? err : new Error(String(err));
@@ -2720,9 +2869,9 @@ function drainExtractionErrors() {
2720
2869
  }
2721
2870
  async function writeExtractionErrors(errors, errorsPath) {
2722
2871
  if (errors.length === 0) return;
2723
- await import_node_fs7.promises.mkdir(import_node_path7.default.dirname(errorsPath), { recursive: true });
2872
+ await import_node_fs8.promises.mkdir(import_node_path8.default.dirname(errorsPath), { recursive: true });
2724
2873
  const lines = errors.map((e) => JSON.stringify(e)).join("\n") + "\n";
2725
- await import_node_fs7.promises.appendFile(errorsPath, lines, "utf8");
2874
+ await import_node_fs8.promises.appendFile(errorsPath, lines, "utf8");
2726
2875
  }
2727
2876
  function isStrictExtractionEnabled() {
2728
2877
  const raw = process.env.NEAT_STRICT_EXTRACTION;
@@ -2745,9 +2894,9 @@ function isRejectedLogEnabled() {
2745
2894
  }
2746
2895
  async function writeRejectedExtracted(drops, rejectedPath) {
2747
2896
  if (drops.length === 0) return;
2748
- await import_node_fs7.promises.mkdir(import_node_path7.default.dirname(rejectedPath), { recursive: true });
2897
+ await import_node_fs8.promises.mkdir(import_node_path8.default.dirname(rejectedPath), { recursive: true });
2749
2898
  const lines = drops.map((d) => JSON.stringify({ ...d, ts: (/* @__PURE__ */ new Date()).toISOString() })).join("\n") + "\n";
2750
- await import_node_fs7.promises.appendFile(rejectedPath, lines, "utf8");
2899
+ await import_node_fs8.promises.appendFile(rejectedPath, lines, "utf8");
2751
2900
  }
2752
2901
  function formatPrecisionFloorBanner(count) {
2753
2902
  if (count === 1) return `[neat] 1 extracted edge dropped below precision floor`;
@@ -2770,21 +2919,21 @@ function workspaceGlobs(pkg) {
2770
2919
  return null;
2771
2920
  }
2772
2921
  async function loadGitignore(scanPath) {
2773
- const gitignorePath = import_node_path8.default.join(scanPath, ".gitignore");
2922
+ const gitignorePath = import_node_path9.default.join(scanPath, ".gitignore");
2774
2923
  if (!await exists(gitignorePath)) return null;
2775
- const raw = await import_node_fs8.promises.readFile(gitignorePath, "utf8");
2924
+ const raw = await import_node_fs9.promises.readFile(gitignorePath, "utf8");
2776
2925
  return (0, import_ignore.default)().add(raw);
2777
2926
  }
2778
2927
  async function walkDirs(start, scanPath, options, visit) {
2779
2928
  async function recurse(current, depth) {
2780
2929
  if (depth > options.maxDepth) return;
2781
- const entries = await import_node_fs8.promises.readdir(current, { withFileTypes: true }).catch(() => []);
2930
+ const entries = await import_node_fs9.promises.readdir(current, { withFileTypes: true }).catch(() => []);
2782
2931
  for (const entry2 of entries) {
2783
2932
  if (!entry2.isDirectory()) continue;
2784
2933
  if (IGNORED_DIRS.has(entry2.name)) continue;
2785
- const child = import_node_path8.default.join(current, entry2.name);
2934
+ const child = import_node_path9.default.join(current, entry2.name);
2786
2935
  if (options.ig) {
2787
- const rel = import_node_path8.default.relative(scanPath, child).split(import_node_path8.default.sep).join("/");
2936
+ const rel = import_node_path9.default.relative(scanPath, child).split(import_node_path9.default.sep).join("/");
2788
2937
  if (rel && options.ig.ignores(rel + "/")) continue;
2789
2938
  }
2790
2939
  if (await isPythonVenvDir(child)) continue;
@@ -2800,8 +2949,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
2800
2949
  for (const raw of globs) {
2801
2950
  const pattern = raw.replace(/^\.\//, "");
2802
2951
  if (!pattern.includes("*")) {
2803
- const candidate = import_node_path8.default.join(scanPath, pattern);
2804
- if (await exists(import_node_path8.default.join(candidate, "package.json"))) found.add(candidate);
2952
+ const candidate = import_node_path9.default.join(scanPath, pattern);
2953
+ if (await exists(import_node_path9.default.join(candidate, "package.json"))) found.add(candidate);
2805
2954
  continue;
2806
2955
  }
2807
2956
  const segments = pattern.split("/");
@@ -2810,13 +2959,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
2810
2959
  if (seg.includes("*")) break;
2811
2960
  staticSegments.push(seg);
2812
2961
  }
2813
- const start = import_node_path8.default.join(scanPath, ...staticSegments);
2962
+ const start = import_node_path9.default.join(scanPath, ...staticSegments);
2814
2963
  if (!await exists(start)) continue;
2815
2964
  const hasDoubleStar = pattern.includes("**");
2816
2965
  const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
2817
2966
  await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
2818
- const rel = import_node_path8.default.relative(scanPath, dir).split(import_node_path8.default.sep).join("/");
2819
- if ((0, import_minimatch2.minimatch)(rel, pattern) && await exists(import_node_path8.default.join(dir, "package.json"))) {
2967
+ const rel = import_node_path9.default.relative(scanPath, dir).split(import_node_path9.default.sep).join("/");
2968
+ if ((0, import_minimatch2.minimatch)(rel, pattern) && await exists(import_node_path9.default.join(dir, "package.json"))) {
2820
2969
  found.add(dir);
2821
2970
  }
2822
2971
  });
@@ -2836,13 +2985,13 @@ function detectJsFramework(pkg) {
2836
2985
  return void 0;
2837
2986
  }
2838
2987
  async function discoverNodeService(scanPath, dir) {
2839
- const pkgPath = import_node_path8.default.join(dir, "package.json");
2988
+ const pkgPath = import_node_path9.default.join(dir, "package.json");
2840
2989
  if (!await exists(pkgPath)) return null;
2841
2990
  let pkg;
2842
2991
  try {
2843
2992
  pkg = await readJson(pkgPath);
2844
2993
  } catch (err) {
2845
- recordExtractionError("services", import_node_path8.default.relative(scanPath, pkgPath), err);
2994
+ recordExtractionError("services", import_node_path9.default.relative(scanPath, pkgPath), err);
2846
2995
  return null;
2847
2996
  }
2848
2997
  if (!pkg.name) return null;
@@ -2854,7 +3003,7 @@ async function discoverNodeService(scanPath, dir) {
2854
3003
  language: "javascript",
2855
3004
  version: pkg.version,
2856
3005
  dependencies: pkg.dependencies ?? {},
2857
- repoPath: import_node_path8.default.relative(scanPath, dir),
3006
+ repoPath: import_node_path9.default.relative(scanPath, dir),
2858
3007
  ...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {},
2859
3008
  ...framework ? { framework } : {}
2860
3009
  };
@@ -2871,12 +3020,12 @@ async function discoverPyService(scanPath, dir) {
2871
3020
  language: "python",
2872
3021
  version: py.version,
2873
3022
  dependencies: py.dependencies,
2874
- repoPath: import_node_path8.default.relative(scanPath, dir)
3023
+ repoPath: import_node_path9.default.relative(scanPath, dir)
2875
3024
  };
2876
3025
  return { pkg, dir, node };
2877
3026
  }
2878
3027
  async function discoverServices(scanPath) {
2879
- const rootPkgPath = import_node_path8.default.join(scanPath, "package.json");
3028
+ const rootPkgPath = import_node_path9.default.join(scanPath, "package.json");
2880
3029
  let rootPkg = null;
2881
3030
  if (await exists(rootPkgPath)) {
2882
3031
  try {
@@ -2884,7 +3033,7 @@ async function discoverServices(scanPath) {
2884
3033
  } catch (err) {
2885
3034
  recordExtractionError(
2886
3035
  "services workspaces",
2887
- import_node_path8.default.relative(scanPath, rootPkgPath),
3036
+ import_node_path9.default.relative(scanPath, rootPkgPath),
2888
3037
  err
2889
3038
  );
2890
3039
  }
@@ -2901,9 +3050,9 @@ async function discoverServices(scanPath) {
2901
3050
  scanPath,
2902
3051
  { maxDepth: parseScanDepth(), ig },
2903
3052
  async (dir) => {
2904
- if (await exists(import_node_path8.default.join(dir, "package.json"))) {
3053
+ if (await exists(import_node_path9.default.join(dir, "package.json"))) {
2905
3054
  candidateDirs.push(dir);
2906
- } else if (await exists(import_node_path8.default.join(dir, "pyproject.toml")) || await exists(import_node_path8.default.join(dir, "requirements.txt")) || await exists(import_node_path8.default.join(dir, "setup.py"))) {
3055
+ } else if (await exists(import_node_path9.default.join(dir, "pyproject.toml")) || await exists(import_node_path9.default.join(dir, "requirements.txt")) || await exists(import_node_path9.default.join(dir, "setup.py"))) {
2907
3056
  candidateDirs.push(dir);
2908
3057
  }
2909
3058
  }
@@ -2917,8 +3066,8 @@ async function discoverServices(scanPath) {
2917
3066
  if (!service) continue;
2918
3067
  const existingDir = seen.get(service.node.name);
2919
3068
  if (existingDir !== void 0) {
2920
- const a = import_node_path8.default.relative(scanPath, existingDir) || ".";
2921
- const b = import_node_path8.default.relative(scanPath, dir) || ".";
3069
+ const a = import_node_path9.default.relative(scanPath, existingDir) || ".";
3070
+ const b = import_node_path9.default.relative(scanPath, dir) || ".";
2922
3071
  console.warn(
2923
3072
  `[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
2924
3073
  );
@@ -2955,8 +3104,8 @@ function addServiceNodes(graph, services) {
2955
3104
 
2956
3105
  // src/extract/aliases.ts
2957
3106
  init_cjs_shims();
2958
- var import_node_path9 = __toESM(require("path"), 1);
2959
- var import_node_fs9 = require("fs");
3107
+ var import_node_path10 = __toESM(require("path"), 1);
3108
+ var import_node_fs10 = require("fs");
2960
3109
  var import_yaml2 = require("yaml");
2961
3110
  var import_types6 = require("@neat.is/types");
2962
3111
  var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
@@ -2983,14 +3132,14 @@ function indexServicesByName(services) {
2983
3132
  const map = /* @__PURE__ */ new Map();
2984
3133
  for (const s of services) {
2985
3134
  map.set(s.node.name, s.node.id);
2986
- map.set(import_node_path9.default.basename(s.dir), s.node.id);
3135
+ map.set(import_node_path10.default.basename(s.dir), s.node.id);
2987
3136
  }
2988
3137
  return map;
2989
3138
  }
2990
3139
  async function collectComposeAliases(graph, scanPath, serviceIndex) {
2991
3140
  let composePath = null;
2992
3141
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
2993
- const abs = import_node_path9.default.join(scanPath, name);
3142
+ const abs = import_node_path10.default.join(scanPath, name);
2994
3143
  if (await exists(abs)) {
2995
3144
  composePath = abs;
2996
3145
  break;
@@ -3003,7 +3152,7 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
3003
3152
  } catch (err) {
3004
3153
  recordExtractionError(
3005
3154
  "aliases compose",
3006
- import_node_path9.default.relative(scanPath, composePath),
3155
+ import_node_path10.default.relative(scanPath, composePath),
3007
3156
  err
3008
3157
  );
3009
3158
  return;
@@ -3046,11 +3195,11 @@ function parseDockerfileLabels(content) {
3046
3195
  }
3047
3196
  async function collectDockerfileAliases(graph, services) {
3048
3197
  for (const service of services) {
3049
- const dockerfilePath = import_node_path9.default.join(service.dir, "Dockerfile");
3198
+ const dockerfilePath = import_node_path10.default.join(service.dir, "Dockerfile");
3050
3199
  if (!await exists(dockerfilePath)) continue;
3051
3200
  let content;
3052
3201
  try {
3053
- content = await import_node_fs9.promises.readFile(dockerfilePath, "utf8");
3202
+ content = await import_node_fs10.promises.readFile(dockerfilePath, "utf8");
3054
3203
  } catch (err) {
3055
3204
  recordExtractionError("aliases dockerfile", dockerfilePath, err);
3056
3205
  continue;
@@ -3062,15 +3211,15 @@ async function collectDockerfileAliases(graph, services) {
3062
3211
  async function walkYamlFiles(start, depth = 0, max = 5) {
3063
3212
  if (depth > max) return [];
3064
3213
  const out = [];
3065
- const entries = await import_node_fs9.promises.readdir(start, { withFileTypes: true }).catch(() => []);
3214
+ const entries = await import_node_fs10.promises.readdir(start, { withFileTypes: true }).catch(() => []);
3066
3215
  for (const entry2 of entries) {
3067
3216
  if (entry2.isDirectory()) {
3068
3217
  if (IGNORED_DIRS.has(entry2.name)) continue;
3069
- const child = import_node_path9.default.join(start, entry2.name);
3218
+ const child = import_node_path10.default.join(start, entry2.name);
3070
3219
  if (await isPythonVenvDir(child)) continue;
3071
3220
  out.push(...await walkYamlFiles(child, depth + 1, max));
3072
- } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path9.default.extname(entry2.name))) {
3073
- out.push(import_node_path9.default.join(start, entry2.name));
3221
+ } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path10.default.extname(entry2.name))) {
3222
+ out.push(import_node_path10.default.join(start, entry2.name));
3074
3223
  }
3075
3224
  }
3076
3225
  return out;
@@ -3097,7 +3246,7 @@ function k8sServiceTarget(doc, byName) {
3097
3246
  async function collectK8sAliases(graph, scanPath, serviceIndex) {
3098
3247
  const files = await walkYamlFiles(scanPath);
3099
3248
  for (const file of files) {
3100
- const content = await import_node_fs9.promises.readFile(file, "utf8");
3249
+ const content = await import_node_fs10.promises.readFile(file, "utf8");
3101
3250
  let docs;
3102
3251
  try {
3103
3252
  docs = (0, import_yaml2.parseAllDocuments)(content).map((d) => d.toJSON());
@@ -3125,20 +3274,20 @@ init_cjs_shims();
3125
3274
 
3126
3275
  // src/extract/calls/shared.ts
3127
3276
  init_cjs_shims();
3128
- var import_node_fs10 = require("fs");
3129
- var import_node_path10 = __toESM(require("path"), 1);
3277
+ var import_node_fs11 = require("fs");
3278
+ var import_node_path11 = __toESM(require("path"), 1);
3130
3279
  var import_types7 = require("@neat.is/types");
3131
3280
  async function walkSourceFiles(dir) {
3132
3281
  const out = [];
3133
3282
  async function walk(current) {
3134
- const entries = await import_node_fs10.promises.readdir(current, { withFileTypes: true }).catch(() => []);
3283
+ const entries = await import_node_fs11.promises.readdir(current, { withFileTypes: true }).catch(() => []);
3135
3284
  for (const entry2 of entries) {
3136
- const full = import_node_path10.default.join(current, entry2.name);
3285
+ const full = import_node_path11.default.join(current, entry2.name);
3137
3286
  if (entry2.isDirectory()) {
3138
3287
  if (IGNORED_DIRS.has(entry2.name)) continue;
3139
3288
  if (await isPythonVenvDir(full)) continue;
3140
3289
  await walk(full);
3141
- } else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path10.default.extname(entry2.name))) {
3290
+ } else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path11.default.extname(entry2.name))) {
3142
3291
  out.push(full);
3143
3292
  }
3144
3293
  }
@@ -3151,7 +3300,7 @@ async function loadSourceFiles(dir) {
3151
3300
  const out = [];
3152
3301
  for (const p of paths) {
3153
3302
  try {
3154
- const content = await import_node_fs10.promises.readFile(p, "utf8");
3303
+ const content = await import_node_fs11.promises.readFile(p, "utf8");
3155
3304
  out.push({ path: p, content });
3156
3305
  } catch {
3157
3306
  }
@@ -3171,7 +3320,7 @@ function toPosix2(p) {
3171
3320
  return p.split("\\").join("/");
3172
3321
  }
3173
3322
  function languageForPath(relPath) {
3174
- switch (import_node_path10.default.extname(relPath).toLowerCase()) {
3323
+ switch (import_node_path11.default.extname(relPath).toLowerCase()) {
3175
3324
  case ".py":
3176
3325
  return "python";
3177
3326
  case ".ts":
@@ -3221,14 +3370,14 @@ function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
3221
3370
  }
3222
3371
 
3223
3372
  // src/extract/files.ts
3224
- var import_node_path11 = __toESM(require("path"), 1);
3373
+ var import_node_path12 = __toESM(require("path"), 1);
3225
3374
  async function addFiles(graph, services) {
3226
3375
  let nodesAdded = 0;
3227
3376
  let edgesAdded = 0;
3228
3377
  for (const service of services) {
3229
3378
  const filePaths = await walkSourceFiles(service.dir);
3230
3379
  for (const filePath of filePaths) {
3231
- const relPath = toPosix2(import_node_path11.default.relative(service.dir, filePath));
3380
+ const relPath = toPosix2(import_node_path12.default.relative(service.dir, filePath));
3232
3381
  const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
3233
3382
  graph,
3234
3383
  service.pkg.name,
@@ -3244,8 +3393,8 @@ async function addFiles(graph, services) {
3244
3393
 
3245
3394
  // src/extract/imports.ts
3246
3395
  init_cjs_shims();
3247
- var import_node_path12 = __toESM(require("path"), 1);
3248
- var import_node_fs11 = require("fs");
3396
+ var import_node_path13 = __toESM(require("path"), 1);
3397
+ var import_node_fs12 = require("fs");
3249
3398
  var import_tree_sitter = __toESM(require("tree-sitter"), 1);
3250
3399
  var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
3251
3400
  var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
@@ -3349,15 +3498,15 @@ function collectPyImports(node, out) {
3349
3498
  }
3350
3499
  async function fileExists(p) {
3351
3500
  try {
3352
- await import_node_fs11.promises.access(p);
3501
+ await import_node_fs12.promises.access(p);
3353
3502
  return true;
3354
3503
  } catch {
3355
3504
  return false;
3356
3505
  }
3357
3506
  }
3358
3507
  function isWithinServiceDir(candidate, serviceDir) {
3359
- const rel = import_node_path12.default.relative(serviceDir, candidate);
3360
- return rel !== "" && !rel.startsWith("..") && !import_node_path12.default.isAbsolute(rel);
3508
+ const rel = import_node_path13.default.relative(serviceDir, candidate);
3509
+ return rel !== "" && !rel.startsWith("..") && !import_node_path13.default.isAbsolute(rel);
3361
3510
  }
3362
3511
  var JS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
3363
3512
  var JS_INDEX_FILES = JS_EXTENSIONS.map((ext) => `index${ext}`);
@@ -3365,22 +3514,22 @@ async function firstExistingCandidate(base, serviceDir) {
3365
3514
  for (const ext of JS_EXTENSIONS) {
3366
3515
  const candidate = base + ext;
3367
3516
  if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
3368
- return toPosix2(import_node_path12.default.relative(serviceDir, candidate));
3517
+ return toPosix2(import_node_path13.default.relative(serviceDir, candidate));
3369
3518
  }
3370
3519
  }
3371
3520
  for (const indexFile of JS_INDEX_FILES) {
3372
- const candidate = import_node_path12.default.join(base, indexFile);
3521
+ const candidate = import_node_path13.default.join(base, indexFile);
3373
3522
  if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
3374
- return toPosix2(import_node_path12.default.relative(serviceDir, candidate));
3523
+ return toPosix2(import_node_path13.default.relative(serviceDir, candidate));
3375
3524
  }
3376
3525
  }
3377
3526
  return null;
3378
3527
  }
3379
3528
  async function loadTsPathConfig(serviceDir) {
3380
- const tsconfigPath = import_node_path12.default.join(serviceDir, "tsconfig.json");
3529
+ const tsconfigPath = import_node_path13.default.join(serviceDir, "tsconfig.json");
3381
3530
  let raw;
3382
3531
  try {
3383
- raw = await import_node_fs11.promises.readFile(tsconfigPath, "utf8");
3532
+ raw = await import_node_fs12.promises.readFile(tsconfigPath, "utf8");
3384
3533
  } catch {
3385
3534
  return null;
3386
3535
  }
@@ -3389,7 +3538,7 @@ async function loadTsPathConfig(serviceDir) {
3389
3538
  const paths = parsed.compilerOptions?.paths;
3390
3539
  if (!paths || Object.keys(paths).length === 0) return null;
3391
3540
  const baseUrl = parsed.compilerOptions?.baseUrl;
3392
- return { paths, baseDir: baseUrl ? import_node_path12.default.resolve(serviceDir, baseUrl) : serviceDir };
3541
+ return { paths, baseDir: baseUrl ? import_node_path13.default.resolve(serviceDir, baseUrl) : serviceDir };
3393
3542
  } catch (err) {
3394
3543
  recordExtractionError("import alias resolution", tsconfigPath, err);
3395
3544
  return null;
@@ -3407,11 +3556,11 @@ async function resolveTsAlias(specifier, config, serviceDir) {
3407
3556
  if (suffix === null) continue;
3408
3557
  for (const target of targets) {
3409
3558
  const targetBase = target.endsWith("/*") ? target.slice(0, -2) : target.replace(/\*$/, "");
3410
- const resolvedBase = import_node_path12.default.resolve(config.baseDir, targetBase, suffix);
3559
+ const resolvedBase = import_node_path13.default.resolve(config.baseDir, targetBase, suffix);
3411
3560
  const hit = await firstExistingCandidate(resolvedBase, serviceDir);
3412
3561
  if (hit) return hit;
3413
3562
  if (isWithinServiceDir(resolvedBase, serviceDir) && await fileExists(resolvedBase)) {
3414
- return toPosix2(import_node_path12.default.relative(serviceDir, resolvedBase));
3563
+ return toPosix2(import_node_path13.default.relative(serviceDir, resolvedBase));
3415
3564
  }
3416
3565
  }
3417
3566
  }
@@ -3420,18 +3569,18 @@ async function resolveTsAlias(specifier, config, serviceDir) {
3420
3569
  async function resolveJsImport(specifier, importerDir, serviceDir, tsPaths) {
3421
3570
  if (!specifier) return null;
3422
3571
  if (specifier.startsWith("./") || specifier.startsWith("../")) {
3423
- const base = import_node_path12.default.resolve(importerDir, specifier);
3424
- const ext = import_node_path12.default.extname(specifier);
3572
+ const base = import_node_path13.default.resolve(importerDir, specifier);
3573
+ const ext = import_node_path13.default.extname(specifier);
3425
3574
  if (ext) {
3426
3575
  if (ext === ".js" || ext === ".jsx") {
3427
3576
  const tsExt = ext === ".jsx" ? ".tsx" : ".ts";
3428
3577
  const tsSibling = base.slice(0, -ext.length) + tsExt;
3429
3578
  if (isWithinServiceDir(tsSibling, serviceDir) && await fileExists(tsSibling)) {
3430
- return toPosix2(import_node_path12.default.relative(serviceDir, tsSibling));
3579
+ return toPosix2(import_node_path13.default.relative(serviceDir, tsSibling));
3431
3580
  }
3432
3581
  }
3433
3582
  if (isWithinServiceDir(base, serviceDir) && await fileExists(base)) {
3434
- return toPosix2(import_node_path12.default.relative(serviceDir, base));
3583
+ return toPosix2(import_node_path13.default.relative(serviceDir, base));
3435
3584
  }
3436
3585
  return null;
3437
3586
  }
@@ -3445,15 +3594,15 @@ async function resolvePyImport(imp, importerPath, serviceDir) {
3445
3594
  const relPath = imp.modulePath.split(".").join("/");
3446
3595
  let baseDir;
3447
3596
  if (imp.level > 0) {
3448
- baseDir = import_node_path12.default.dirname(importerPath);
3449
- for (let i = 1; i < imp.level; i++) baseDir = import_node_path12.default.dirname(baseDir);
3597
+ baseDir = import_node_path13.default.dirname(importerPath);
3598
+ for (let i = 1; i < imp.level; i++) baseDir = import_node_path13.default.dirname(baseDir);
3450
3599
  } else {
3451
3600
  baseDir = serviceDir;
3452
3601
  }
3453
- const candidates = [import_node_path12.default.join(baseDir, `${relPath}.py`), import_node_path12.default.join(baseDir, relPath, "__init__.py")];
3602
+ const candidates = [import_node_path13.default.join(baseDir, `${relPath}.py`), import_node_path13.default.join(baseDir, relPath, "__init__.py")];
3454
3603
  for (const candidate of candidates) {
3455
3604
  if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
3456
- return toPosix2(import_node_path12.default.relative(serviceDir, candidate));
3605
+ return toPosix2(import_node_path13.default.relative(serviceDir, candidate));
3457
3606
  }
3458
3607
  }
3459
3608
  return null;
@@ -3484,9 +3633,9 @@ async function addImports(graph, services) {
3484
3633
  const files = await loadSourceFiles(service.dir);
3485
3634
  for (const file of files) {
3486
3635
  if (isTestPath(file.path)) continue;
3487
- const relFile = toPosix2(import_node_path12.default.relative(service.dir, file.path));
3636
+ const relFile = toPosix2(import_node_path13.default.relative(service.dir, file.path));
3488
3637
  const importerFileId = (0, import_types8.fileId)(service.pkg.name, relFile);
3489
- const isPython = import_node_path12.default.extname(file.path) === ".py";
3638
+ const isPython = import_node_path13.default.extname(file.path) === ".py";
3490
3639
  if (isPython) {
3491
3640
  let pyImports = [];
3492
3641
  try {
@@ -3520,7 +3669,7 @@ async function addImports(graph, services) {
3520
3669
  continue;
3521
3670
  }
3522
3671
  for (const imp of jsImports) {
3523
- const resolved = await resolveJsImport(imp.specifier, import_node_path12.default.dirname(file.path), service.dir, tsPaths);
3672
+ const resolved = await resolveJsImport(imp.specifier, import_node_path13.default.dirname(file.path), service.dir, tsPaths);
3524
3673
  if (!resolved) continue;
3525
3674
  edgesAdded += emitImportEdge(
3526
3675
  graph,
@@ -3539,14 +3688,14 @@ async function addImports(graph, services) {
3539
3688
 
3540
3689
  // src/extract/databases/index.ts
3541
3690
  init_cjs_shims();
3542
- var import_node_path20 = __toESM(require("path"), 1);
3691
+ var import_node_path21 = __toESM(require("path"), 1);
3543
3692
  var import_types9 = require("@neat.is/types");
3544
3693
 
3545
3694
  // src/extract/databases/db-config-yaml.ts
3546
3695
  init_cjs_shims();
3547
- var import_node_path13 = __toESM(require("path"), 1);
3696
+ var import_node_path14 = __toESM(require("path"), 1);
3548
3697
  async function parse(serviceDir) {
3549
- const yamlPath = import_node_path13.default.join(serviceDir, "db-config.yaml");
3698
+ const yamlPath = import_node_path14.default.join(serviceDir, "db-config.yaml");
3550
3699
  if (!await exists(yamlPath)) return [];
3551
3700
  const raw = await readYaml(yamlPath);
3552
3701
  return [
@@ -3564,13 +3713,13 @@ var dbConfigYamlParser = { name: "db-config.yaml", parse };
3564
3713
 
3565
3714
  // src/extract/databases/dotenv.ts
3566
3715
  init_cjs_shims();
3567
- var import_node_fs13 = require("fs");
3568
- var import_node_path15 = __toESM(require("path"), 1);
3716
+ var import_node_fs14 = require("fs");
3717
+ var import_node_path16 = __toESM(require("path"), 1);
3569
3718
 
3570
3719
  // src/extract/databases/shared.ts
3571
3720
  init_cjs_shims();
3572
- var import_node_fs12 = require("fs");
3573
- var import_node_path14 = __toESM(require("path"), 1);
3721
+ var import_node_fs13 = require("fs");
3722
+ var import_node_path15 = __toESM(require("path"), 1);
3574
3723
  function schemeToEngine(scheme) {
3575
3724
  const s = scheme.toLowerCase().split("+")[0];
3576
3725
  switch (s) {
@@ -3609,14 +3758,14 @@ function parseConnectionString(url) {
3609
3758
  }
3610
3759
  async function readIfExists(filePath) {
3611
3760
  try {
3612
- return await import_node_fs12.promises.readFile(filePath, "utf8");
3761
+ return await import_node_fs13.promises.readFile(filePath, "utf8");
3613
3762
  } catch {
3614
3763
  return null;
3615
3764
  }
3616
3765
  }
3617
3766
  async function findFirst(serviceDir, candidates) {
3618
3767
  for (const rel of candidates) {
3619
- const abs = import_node_path14.default.join(serviceDir, rel);
3768
+ const abs = import_node_path15.default.join(serviceDir, rel);
3620
3769
  const content = await readIfExists(abs);
3621
3770
  if (content !== null) return abs;
3622
3771
  }
@@ -3667,15 +3816,15 @@ function parseDotenvLine(line) {
3667
3816
  return { key, value };
3668
3817
  }
3669
3818
  async function parse2(serviceDir) {
3670
- const entries = await import_node_fs13.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
3819
+ const entries = await import_node_fs14.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
3671
3820
  const configs = [];
3672
3821
  const seen = /* @__PURE__ */ new Set();
3673
3822
  for (const entry2 of entries) {
3674
3823
  if (!entry2.isFile()) continue;
3675
3824
  const match = isConfigFile(entry2.name);
3676
3825
  if (!match.match || match.fileType !== "env") continue;
3677
- const filePath = import_node_path15.default.join(serviceDir, entry2.name);
3678
- const content = await import_node_fs13.promises.readFile(filePath, "utf8");
3826
+ const filePath = import_node_path16.default.join(serviceDir, entry2.name);
3827
+ const content = await import_node_fs14.promises.readFile(filePath, "utf8");
3679
3828
  for (const line of content.split("\n")) {
3680
3829
  const parsed = parseDotenvLine(line);
3681
3830
  if (!parsed) continue;
@@ -3694,9 +3843,9 @@ var dotenvParser = { name: ".env", parse: parse2 };
3694
3843
 
3695
3844
  // src/extract/databases/prisma.ts
3696
3845
  init_cjs_shims();
3697
- var import_node_path16 = __toESM(require("path"), 1);
3846
+ var import_node_path17 = __toESM(require("path"), 1);
3698
3847
  async function parse3(serviceDir) {
3699
- const schemaPath = import_node_path16.default.join(serviceDir, "prisma", "schema.prisma");
3848
+ const schemaPath = import_node_path17.default.join(serviceDir, "prisma", "schema.prisma");
3700
3849
  const content = await readIfExists(schemaPath);
3701
3850
  if (!content) return [];
3702
3851
  const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
@@ -3828,10 +3977,10 @@ var knexParser = { name: "knex", parse: parse5 };
3828
3977
 
3829
3978
  // src/extract/databases/ormconfig.ts
3830
3979
  init_cjs_shims();
3831
- var import_node_path17 = __toESM(require("path"), 1);
3980
+ var import_node_path18 = __toESM(require("path"), 1);
3832
3981
  async function parse6(serviceDir) {
3833
3982
  for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
3834
- const abs = import_node_path17.default.join(serviceDir, candidate);
3983
+ const abs = import_node_path18.default.join(serviceDir, candidate);
3835
3984
  if (!await exists(abs)) continue;
3836
3985
  const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
3837
3986
  const entries = Array.isArray(raw) ? raw : [raw];
@@ -3891,9 +4040,9 @@ var typeormParser = { name: "typeorm", parse: parse7 };
3891
4040
 
3892
4041
  // src/extract/databases/sequelize.ts
3893
4042
  init_cjs_shims();
3894
- var import_node_path18 = __toESM(require("path"), 1);
4043
+ var import_node_path19 = __toESM(require("path"), 1);
3895
4044
  async function parse8(serviceDir) {
3896
- const configPath = import_node_path18.default.join(serviceDir, "config", "config.json");
4045
+ const configPath = import_node_path19.default.join(serviceDir, "config", "config.json");
3897
4046
  if (!await exists(configPath)) return [];
3898
4047
  const raw = await readJson(configPath);
3899
4048
  const out = [];
@@ -3920,7 +4069,7 @@ var sequelizeParser = { name: "sequelize", parse: parse8 };
3920
4069
 
3921
4070
  // src/extract/databases/docker-compose.ts
3922
4071
  init_cjs_shims();
3923
- var import_node_path19 = __toESM(require("path"), 1);
4072
+ var import_node_path20 = __toESM(require("path"), 1);
3924
4073
  function portFromService(svc) {
3925
4074
  for (const raw of svc.ports ?? []) {
3926
4075
  const str = String(raw);
@@ -3947,7 +4096,7 @@ function databaseFromEnv(svc) {
3947
4096
  }
3948
4097
  async function parse9(serviceDir) {
3949
4098
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
3950
- const abs = import_node_path19.default.join(serviceDir, name);
4099
+ const abs = import_node_path20.default.join(serviceDir, name);
3951
4100
  if (!await exists(abs)) continue;
3952
4101
  const raw = await readYaml(abs);
3953
4102
  if (!raw?.services) return [];
@@ -4119,7 +4268,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
4119
4268
  discoveredVia: mergedDiscoveredVia
4120
4269
  });
4121
4270
  }
4122
- const relConfigFile = toPosix2(import_node_path20.default.relative(service.dir, config.sourceFile));
4271
+ const relConfigFile = toPosix2(import_node_path21.default.relative(service.dir, config.sourceFile));
4123
4272
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
4124
4273
  graph,
4125
4274
  service.pkg.name,
@@ -4128,7 +4277,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
4128
4277
  );
4129
4278
  nodesAdded += fn;
4130
4279
  edgesAdded += fe;
4131
- const evidenceFile = toPosix2(import_node_path20.default.relative(scanPath, config.sourceFile));
4280
+ const evidenceFile = toPosix2(import_node_path21.default.relative(scanPath, config.sourceFile));
4132
4281
  const edge = {
4133
4282
  id: (0, import_types4.extractedEdgeId)(fileNodeId, dbNode.id, import_types9.EdgeType.CONNECTS_TO),
4134
4283
  source: fileNodeId,
@@ -4162,15 +4311,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
4162
4311
 
4163
4312
  // src/extract/configs.ts
4164
4313
  init_cjs_shims();
4165
- var import_node_fs14 = require("fs");
4166
- var import_node_path21 = __toESM(require("path"), 1);
4314
+ var import_node_fs15 = require("fs");
4315
+ var import_node_path22 = __toESM(require("path"), 1);
4167
4316
  var import_types10 = require("@neat.is/types");
4168
4317
  async function walkConfigFiles(dir) {
4169
4318
  const out = [];
4170
4319
  async function walk(current) {
4171
- const entries = await import_node_fs14.promises.readdir(current, { withFileTypes: true });
4320
+ const entries = await import_node_fs15.promises.readdir(current, { withFileTypes: true });
4172
4321
  for (const entry2 of entries) {
4173
- const full = import_node_path21.default.join(current, entry2.name);
4322
+ const full = import_node_path22.default.join(current, entry2.name);
4174
4323
  if (entry2.isDirectory()) {
4175
4324
  if (IGNORED_DIRS.has(entry2.name)) continue;
4176
4325
  if (await isPythonVenvDir(full)) continue;
@@ -4189,19 +4338,19 @@ async function addConfigNodes(graph, services, scanPath) {
4189
4338
  for (const service of services) {
4190
4339
  const configFiles = await walkConfigFiles(service.dir);
4191
4340
  for (const file of configFiles) {
4192
- const relPath = import_node_path21.default.relative(scanPath, file);
4341
+ const relPath = import_node_path22.default.relative(scanPath, file);
4193
4342
  const node = {
4194
4343
  id: (0, import_types10.configId)(relPath),
4195
4344
  type: import_types10.NodeType.ConfigNode,
4196
- name: import_node_path21.default.basename(file),
4345
+ name: import_node_path22.default.basename(file),
4197
4346
  path: relPath,
4198
- fileType: isConfigFile(import_node_path21.default.basename(file)).fileType
4347
+ fileType: isConfigFile(import_node_path22.default.basename(file)).fileType
4199
4348
  };
4200
4349
  if (!graph.hasNode(node.id)) {
4201
4350
  graph.addNode(node.id, node);
4202
4351
  nodesAdded++;
4203
4352
  }
4204
- const relToService = toPosix2(import_node_path21.default.relative(service.dir, file));
4353
+ const relToService = toPosix2(import_node_path22.default.relative(service.dir, file));
4205
4354
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
4206
4355
  graph,
4207
4356
  service.pkg.name,
@@ -4217,7 +4366,7 @@ async function addConfigNodes(graph, services, scanPath) {
4217
4366
  type: import_types10.EdgeType.CONFIGURED_BY,
4218
4367
  provenance: import_types10.Provenance.EXTRACTED,
4219
4368
  confidence: (0, import_types10.confidenceForExtracted)("structural"),
4220
- evidence: { file: relPath.split(import_node_path21.default.sep).join("/") }
4369
+ evidence: { file: relPath.split(import_node_path22.default.sep).join("/") }
4221
4370
  };
4222
4371
  if (!graph.hasEdge(edge.id)) {
4223
4372
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -4230,11 +4379,11 @@ async function addConfigNodes(graph, services, scanPath) {
4230
4379
 
4231
4380
  // src/extract/calls/index.ts
4232
4381
  init_cjs_shims();
4233
- var import_types16 = require("@neat.is/types");
4382
+ var import_types17 = require("@neat.is/types");
4234
4383
 
4235
4384
  // src/extract/calls/http.ts
4236
4385
  init_cjs_shims();
4237
- var import_node_path22 = __toESM(require("path"), 1);
4386
+ var import_node_path23 = __toESM(require("path"), 1);
4238
4387
  var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
4239
4388
  var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
4240
4389
  var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
@@ -4303,9 +4452,9 @@ async function addHttpCallEdges(graph, services) {
4303
4452
  const knownHosts = /* @__PURE__ */ new Set();
4304
4453
  const hostToNodeId = /* @__PURE__ */ new Map();
4305
4454
  for (const service of services) {
4306
- knownHosts.add(import_node_path22.default.basename(service.dir));
4455
+ knownHosts.add(import_node_path23.default.basename(service.dir));
4307
4456
  knownHosts.add(service.pkg.name);
4308
- hostToNodeId.set(import_node_path22.default.basename(service.dir), service.node.id);
4457
+ hostToNodeId.set(import_node_path23.default.basename(service.dir), service.node.id);
4309
4458
  hostToNodeId.set(service.pkg.name, service.node.id);
4310
4459
  }
4311
4460
  let nodesAdded = 0;
@@ -4315,7 +4464,7 @@ async function addHttpCallEdges(graph, services) {
4315
4464
  const seen = /* @__PURE__ */ new Set();
4316
4465
  for (const file of files) {
4317
4466
  if (isTestPath(file.path)) continue;
4318
- const parser = import_node_path22.default.extname(file.path) === ".py" ? pyParser : jsParser;
4467
+ const parser = import_node_path23.default.extname(file.path) === ".py" ? pyParser : jsParser;
4319
4468
  let sites;
4320
4469
  try {
4321
4470
  sites = callsFromSource(file.content, parser, knownHosts);
@@ -4324,7 +4473,7 @@ async function addHttpCallEdges(graph, services) {
4324
4473
  continue;
4325
4474
  }
4326
4475
  if (sites.length === 0) continue;
4327
- const relFile = toPosix2(import_node_path22.default.relative(service.dir, file.path));
4476
+ const relFile = toPosix2(import_node_path23.default.relative(service.dir, file.path));
4328
4477
  for (const site of sites) {
4329
4478
  const targetId = hostToNodeId.get(site.host);
4330
4479
  if (!targetId || targetId === service.node.id) continue;
@@ -4378,7 +4527,7 @@ async function addHttpCallEdges(graph, services) {
4378
4527
 
4379
4528
  // src/extract/calls/kafka.ts
4380
4529
  init_cjs_shims();
4381
- var import_node_path23 = __toESM(require("path"), 1);
4530
+ var import_node_path24 = __toESM(require("path"), 1);
4382
4531
  var import_types12 = require("@neat.is/types");
4383
4532
  var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
4384
4533
  var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
@@ -4409,7 +4558,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
4409
4558
  // tier (ADR-066).
4410
4559
  confidenceKind: "verified-call-site",
4411
4560
  evidence: {
4412
- file: import_node_path23.default.relative(serviceDir, file.path),
4561
+ file: import_node_path24.default.relative(serviceDir, file.path),
4413
4562
  line,
4414
4563
  snippet: snippet(file.content, line)
4415
4564
  }
@@ -4422,7 +4571,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
4422
4571
 
4423
4572
  // src/extract/calls/redis.ts
4424
4573
  init_cjs_shims();
4425
- var import_node_path24 = __toESM(require("path"), 1);
4574
+ var import_node_path25 = __toESM(require("path"), 1);
4426
4575
  var import_types13 = require("@neat.is/types");
4427
4576
  var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
4428
4577
  function redisEndpointsFromFile(file, serviceDir) {
@@ -4445,7 +4594,7 @@ function redisEndpointsFromFile(file, serviceDir) {
4445
4594
  // support tier (ADR-066).
4446
4595
  confidenceKind: "url-with-structural-support",
4447
4596
  evidence: {
4448
- file: import_node_path24.default.relative(serviceDir, file.path),
4597
+ file: import_node_path25.default.relative(serviceDir, file.path),
4449
4598
  line,
4450
4599
  snippet: snippet(file.content, line)
4451
4600
  }
@@ -4456,7 +4605,7 @@ function redisEndpointsFromFile(file, serviceDir) {
4456
4605
 
4457
4606
  // src/extract/calls/aws.ts
4458
4607
  init_cjs_shims();
4459
- var import_node_path25 = __toESM(require("path"), 1);
4608
+ var import_node_path26 = __toESM(require("path"), 1);
4460
4609
  var import_types14 = require("@neat.is/types");
4461
4610
  var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
4462
4611
  var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
@@ -4490,7 +4639,7 @@ function awsEndpointsFromFile(file, serviceDir) {
4490
4639
  // (ADR-066).
4491
4640
  confidenceKind: "verified-call-site",
4492
4641
  evidence: {
4493
- file: import_node_path25.default.relative(serviceDir, file.path),
4642
+ file: import_node_path26.default.relative(serviceDir, file.path),
4494
4643
  line,
4495
4644
  snippet: snippet(file.content, line)
4496
4645
  }
@@ -4515,7 +4664,7 @@ function awsEndpointsFromFile(file, serviceDir) {
4515
4664
 
4516
4665
  // src/extract/calls/grpc.ts
4517
4666
  init_cjs_shims();
4518
- var import_node_path26 = __toESM(require("path"), 1);
4667
+ var import_node_path27 = __toESM(require("path"), 1);
4519
4668
  var import_types15 = require("@neat.is/types");
4520
4669
  var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
4521
4670
  var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
@@ -4574,7 +4723,67 @@ function grpcEndpointsFromFile(file, serviceDir) {
4574
4723
  // tier (ADR-066).
4575
4724
  confidenceKind: "verified-call-site",
4576
4725
  evidence: {
4577
- file: import_node_path26.default.relative(serviceDir, file.path),
4726
+ file: import_node_path27.default.relative(serviceDir, file.path),
4727
+ line,
4728
+ snippet: snippet(file.content, line)
4729
+ }
4730
+ });
4731
+ }
4732
+ return out;
4733
+ }
4734
+
4735
+ // src/extract/calls/supabase.ts
4736
+ init_cjs_shims();
4737
+ var import_node_path28 = __toESM(require("path"), 1);
4738
+ var import_types16 = require("@neat.is/types");
4739
+ var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
4740
+ var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
4741
+ var SUPABASE_CLIENT_RE = /\b(createClient|createServerClient|createBrowserClient)\s*\(\s*(?:['"`]([^'"`]*)['"`])?/g;
4742
+ function hostFromLiteral(literal) {
4743
+ if (!literal) return null;
4744
+ const m = /^https?:\/\/([^/:'"`\s]+)/.exec(literal.trim());
4745
+ if (!m) return null;
4746
+ const host = m[1];
4747
+ if (!/\.supabase\.(co|in)$/i.test(host)) return null;
4748
+ return host;
4749
+ }
4750
+ function readImports2(content) {
4751
+ return {
4752
+ hasSupabaseJs: SUPABASE_JS_IMPORT_RE.test(content),
4753
+ hasSupabaseSsr: SUPABASE_SSR_IMPORT_RE.test(content)
4754
+ };
4755
+ }
4756
+ function constructorMatchesImport(name, ctx) {
4757
+ if (name === "createClient") return ctx.hasSupabaseJs;
4758
+ return ctx.hasSupabaseSsr;
4759
+ }
4760
+ function supabaseEndpointsFromFile(file, serviceDir) {
4761
+ const ctx = readImports2(file.content);
4762
+ if (!ctx.hasSupabaseJs && !ctx.hasSupabaseSsr) return [];
4763
+ const out = [];
4764
+ const seen = /* @__PURE__ */ new Set();
4765
+ SUPABASE_CLIENT_RE.lastIndex = 0;
4766
+ let m;
4767
+ while ((m = SUPABASE_CLIENT_RE.exec(file.content)) !== null) {
4768
+ const ctor = m[1];
4769
+ if (!constructorMatchesImport(ctor, ctx)) continue;
4770
+ const host = hostFromLiteral(m[2]);
4771
+ const name = host ?? "env";
4772
+ if (seen.has(name)) continue;
4773
+ seen.add(name);
4774
+ const line = lineOf(file.content, m[0]);
4775
+ out.push({
4776
+ infraId: (0, import_types16.infraId)("supabase", name),
4777
+ name,
4778
+ kind: "supabase",
4779
+ edgeType: "CALLS",
4780
+ // `createClient(...)` from @supabase/supabase-js (or createServerClient /
4781
+ // createBrowserClient from @supabase/ssr) with the import in scope — a
4782
+ // framework-aware recognizer matched the SDK shape. Verified-call-site
4783
+ // tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
4784
+ confidenceKind: "verified-call-site",
4785
+ evidence: {
4786
+ file: import_node_path28.default.relative(serviceDir, file.path),
4578
4787
  line,
4579
4788
  snippet: snippet(file.content, line)
4580
4789
  }
@@ -4587,11 +4796,11 @@ function grpcEndpointsFromFile(file, serviceDir) {
4587
4796
  function edgeTypeFromEndpoint(ep) {
4588
4797
  switch (ep.edgeType) {
4589
4798
  case "PUBLISHES_TO":
4590
- return import_types16.EdgeType.PUBLISHES_TO;
4799
+ return import_types17.EdgeType.PUBLISHES_TO;
4591
4800
  case "CONSUMES_FROM":
4592
- return import_types16.EdgeType.CONSUMES_FROM;
4801
+ return import_types17.EdgeType.CONSUMES_FROM;
4593
4802
  default:
4594
- return import_types16.EdgeType.CALLS;
4803
+ return import_types17.EdgeType.CALLS;
4595
4804
  }
4596
4805
  }
4597
4806
  function isAwsKind(kind) {
@@ -4611,6 +4820,7 @@ async function addExternalEndpointEdges(graph, services) {
4611
4820
  endpoints.push(...redisEndpointsFromFile(maskedFile, service.dir));
4612
4821
  endpoints.push(...awsEndpointsFromFile(maskedFile, service.dir));
4613
4822
  endpoints.push(...grpcEndpointsFromFile(maskedFile, service.dir));
4823
+ endpoints.push(...supabaseEndpointsFromFile(maskedFile, service.dir));
4614
4824
  }
4615
4825
  if (endpoints.length === 0) continue;
4616
4826
  const seenEdges = /* @__PURE__ */ new Set();
@@ -4618,7 +4828,7 @@ async function addExternalEndpointEdges(graph, services) {
4618
4828
  if (!graph.hasNode(ep.infraId)) {
4619
4829
  const node = {
4620
4830
  id: ep.infraId,
4621
- type: import_types16.NodeType.InfraNode,
4831
+ type: import_types17.NodeType.InfraNode,
4622
4832
  name: ep.name,
4623
4833
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
4624
4834
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -4630,7 +4840,7 @@ async function addExternalEndpointEdges(graph, services) {
4630
4840
  nodesAdded++;
4631
4841
  }
4632
4842
  const edgeType = edgeTypeFromEndpoint(ep);
4633
- const confidence = (0, import_types16.confidenceForExtracted)(ep.confidenceKind);
4843
+ const confidence = (0, import_types17.confidenceForExtracted)(ep.confidenceKind);
4634
4844
  const relFile = toPosix2(ep.evidence.file);
4635
4845
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
4636
4846
  graph,
@@ -4640,7 +4850,7 @@ async function addExternalEndpointEdges(graph, services) {
4640
4850
  );
4641
4851
  nodesAdded += n;
4642
4852
  edgesAdded += e;
4643
- if (!(0, import_types16.passesExtractedFloor)(confidence)) {
4853
+ if (!(0, import_types17.passesExtractedFloor)(confidence)) {
4644
4854
  noteExtractedDropped({
4645
4855
  source: fileNodeId,
4646
4856
  target: ep.infraId,
@@ -4660,7 +4870,7 @@ async function addExternalEndpointEdges(graph, services) {
4660
4870
  source: fileNodeId,
4661
4871
  target: ep.infraId,
4662
4872
  type: edgeType,
4663
- provenance: import_types16.Provenance.EXTRACTED,
4873
+ provenance: import_types17.Provenance.EXTRACTED,
4664
4874
  confidence,
4665
4875
  evidence: ep.evidence
4666
4876
  };
@@ -4685,16 +4895,16 @@ init_cjs_shims();
4685
4895
 
4686
4896
  // src/extract/infra/docker-compose.ts
4687
4897
  init_cjs_shims();
4688
- var import_node_path27 = __toESM(require("path"), 1);
4689
- var import_types18 = require("@neat.is/types");
4898
+ var import_node_path29 = __toESM(require("path"), 1);
4899
+ var import_types19 = require("@neat.is/types");
4690
4900
 
4691
4901
  // src/extract/infra/shared.ts
4692
4902
  init_cjs_shims();
4693
- var import_types17 = require("@neat.is/types");
4903
+ var import_types18 = require("@neat.is/types");
4694
4904
  function makeInfraNode(kind, name, provider = "self", extras) {
4695
4905
  return {
4696
- id: (0, import_types17.infraId)(kind, name),
4697
- type: import_types17.NodeType.InfraNode,
4906
+ id: (0, import_types18.infraId)(kind, name),
4907
+ type: import_types18.NodeType.InfraNode,
4698
4908
  name,
4699
4909
  provider,
4700
4910
  kind,
@@ -4723,7 +4933,7 @@ function dependsOnList(value) {
4723
4933
  }
4724
4934
  function serviceNameToServiceNode(name, services) {
4725
4935
  for (const s of services) {
4726
- if (s.node.name === name || import_node_path27.default.basename(s.dir) === name) return s.node.id;
4936
+ if (s.node.name === name || import_node_path29.default.basename(s.dir) === name) return s.node.id;
4727
4937
  }
4728
4938
  return null;
4729
4939
  }
@@ -4732,7 +4942,7 @@ async function addComposeInfra(graph, scanPath, services) {
4732
4942
  let edgesAdded = 0;
4733
4943
  let composePath = null;
4734
4944
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
4735
- const abs = import_node_path27.default.join(scanPath, name);
4945
+ const abs = import_node_path29.default.join(scanPath, name);
4736
4946
  if (await exists(abs)) {
4737
4947
  composePath = abs;
4738
4948
  break;
@@ -4745,13 +4955,13 @@ async function addComposeInfra(graph, scanPath, services) {
4745
4955
  } catch (err) {
4746
4956
  recordExtractionError(
4747
4957
  "infra docker-compose",
4748
- import_node_path27.default.relative(scanPath, composePath),
4958
+ import_node_path29.default.relative(scanPath, composePath),
4749
4959
  err
4750
4960
  );
4751
4961
  return { nodesAdded, edgesAdded };
4752
4962
  }
4753
4963
  if (!compose?.services) return { nodesAdded, edgesAdded };
4754
- const evidenceFile = import_node_path27.default.relative(scanPath, composePath).split(import_node_path27.default.sep).join("/");
4964
+ const evidenceFile = import_node_path29.default.relative(scanPath, composePath).split(import_node_path29.default.sep).join("/");
4755
4965
  const composeNameToNodeId = /* @__PURE__ */ new Map();
4756
4966
  for (const [composeName, svc] of Object.entries(compose.services)) {
4757
4967
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -4773,15 +4983,15 @@ async function addComposeInfra(graph, scanPath, services) {
4773
4983
  for (const dep of dependsOnList(svc.depends_on)) {
4774
4984
  const targetId = composeNameToNodeId.get(dep);
4775
4985
  if (!targetId) continue;
4776
- const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types18.EdgeType.DEPENDS_ON);
4986
+ const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types19.EdgeType.DEPENDS_ON);
4777
4987
  if (graph.hasEdge(edgeId)) continue;
4778
4988
  const edge = {
4779
4989
  id: edgeId,
4780
4990
  source: sourceId,
4781
4991
  target: targetId,
4782
- type: import_types18.EdgeType.DEPENDS_ON,
4783
- provenance: import_types18.Provenance.EXTRACTED,
4784
- confidence: (0, import_types18.confidenceForExtracted)("structural"),
4992
+ type: import_types19.EdgeType.DEPENDS_ON,
4993
+ provenance: import_types19.Provenance.EXTRACTED,
4994
+ confidence: (0, import_types19.confidenceForExtracted)("structural"),
4785
4995
  evidence: { file: evidenceFile }
4786
4996
  };
4787
4997
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -4793,9 +5003,9 @@ async function addComposeInfra(graph, scanPath, services) {
4793
5003
 
4794
5004
  // src/extract/infra/dockerfile.ts
4795
5005
  init_cjs_shims();
4796
- var import_node_path28 = __toESM(require("path"), 1);
4797
- var import_node_fs15 = require("fs");
4798
- var import_types19 = require("@neat.is/types");
5006
+ var import_node_path30 = __toESM(require("path"), 1);
5007
+ var import_node_fs16 = require("fs");
5008
+ var import_types20 = require("@neat.is/types");
4799
5009
  function runtimeImage(content) {
4800
5010
  const lines = content.split("\n");
4801
5011
  let last = null;
@@ -4814,15 +5024,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4814
5024
  let nodesAdded = 0;
4815
5025
  let edgesAdded = 0;
4816
5026
  for (const service of services) {
4817
- const dockerfilePath = import_node_path28.default.join(service.dir, "Dockerfile");
5027
+ const dockerfilePath = import_node_path30.default.join(service.dir, "Dockerfile");
4818
5028
  if (!await exists(dockerfilePath)) continue;
4819
5029
  let content;
4820
5030
  try {
4821
- content = await import_node_fs15.promises.readFile(dockerfilePath, "utf8");
5031
+ content = await import_node_fs16.promises.readFile(dockerfilePath, "utf8");
4822
5032
  } catch (err) {
4823
5033
  recordExtractionError(
4824
5034
  "infra dockerfile",
4825
- import_node_path28.default.relative(scanPath, dockerfilePath),
5035
+ import_node_path30.default.relative(scanPath, dockerfilePath),
4826
5036
  err
4827
5037
  );
4828
5038
  continue;
@@ -4834,7 +5044,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4834
5044
  graph.addNode(node.id, node);
4835
5045
  nodesAdded++;
4836
5046
  }
4837
- const relDockerfile = toPosix2(import_node_path28.default.relative(service.dir, dockerfilePath));
5047
+ const relDockerfile = toPosix2(import_node_path30.default.relative(service.dir, dockerfilePath));
4838
5048
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
4839
5049
  graph,
4840
5050
  service.pkg.name,
@@ -4843,17 +5053,17 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4843
5053
  );
4844
5054
  nodesAdded += fn;
4845
5055
  edgesAdded += fe;
4846
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types19.EdgeType.RUNS_ON);
5056
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types20.EdgeType.RUNS_ON);
4847
5057
  if (!graph.hasEdge(edgeId)) {
4848
5058
  const edge = {
4849
5059
  id: edgeId,
4850
5060
  source: fileNodeId,
4851
5061
  target: node.id,
4852
- type: import_types19.EdgeType.RUNS_ON,
4853
- provenance: import_types19.Provenance.EXTRACTED,
4854
- confidence: (0, import_types19.confidenceForExtracted)("structural"),
5062
+ type: import_types20.EdgeType.RUNS_ON,
5063
+ provenance: import_types20.Provenance.EXTRACTED,
5064
+ confidence: (0, import_types20.confidenceForExtracted)("structural"),
4855
5065
  evidence: {
4856
- file: toPosix2(import_node_path28.default.relative(scanPath, dockerfilePath))
5066
+ file: toPosix2(import_node_path30.default.relative(scanPath, dockerfilePath))
4857
5067
  }
4858
5068
  };
4859
5069
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -4865,21 +5075,21 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4865
5075
 
4866
5076
  // src/extract/infra/terraform.ts
4867
5077
  init_cjs_shims();
4868
- var import_node_fs16 = require("fs");
4869
- var import_node_path29 = __toESM(require("path"), 1);
5078
+ var import_node_fs17 = require("fs");
5079
+ var import_node_path31 = __toESM(require("path"), 1);
4870
5080
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
4871
5081
  async function walkTfFiles(start, depth = 0, max = 5) {
4872
5082
  if (depth > max) return [];
4873
5083
  const out = [];
4874
- const entries = await import_node_fs16.promises.readdir(start, { withFileTypes: true }).catch(() => []);
5084
+ const entries = await import_node_fs17.promises.readdir(start, { withFileTypes: true }).catch(() => []);
4875
5085
  for (const entry2 of entries) {
4876
5086
  if (entry2.isDirectory()) {
4877
5087
  if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
4878
- const child = import_node_path29.default.join(start, entry2.name);
5088
+ const child = import_node_path31.default.join(start, entry2.name);
4879
5089
  if (await isPythonVenvDir(child)) continue;
4880
5090
  out.push(...await walkTfFiles(child, depth + 1, max));
4881
5091
  } else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
4882
- out.push(import_node_path29.default.join(start, entry2.name));
5092
+ out.push(import_node_path31.default.join(start, entry2.name));
4883
5093
  }
4884
5094
  }
4885
5095
  return out;
@@ -4888,7 +5098,7 @@ async function addTerraformResources(graph, scanPath) {
4888
5098
  let nodesAdded = 0;
4889
5099
  const files = await walkTfFiles(scanPath);
4890
5100
  for (const file of files) {
4891
- const content = await import_node_fs16.promises.readFile(file, "utf8");
5101
+ const content = await import_node_fs17.promises.readFile(file, "utf8");
4892
5102
  RESOURCE_RE.lastIndex = 0;
4893
5103
  let m;
4894
5104
  while ((m = RESOURCE_RE.exec(content)) !== null) {
@@ -4906,8 +5116,8 @@ async function addTerraformResources(graph, scanPath) {
4906
5116
 
4907
5117
  // src/extract/infra/k8s.ts
4908
5118
  init_cjs_shims();
4909
- var import_node_fs17 = require("fs");
4910
- var import_node_path30 = __toESM(require("path"), 1);
5119
+ var import_node_fs18 = require("fs");
5120
+ var import_node_path32 = __toESM(require("path"), 1);
4911
5121
  var import_yaml3 = require("yaml");
4912
5122
  var K8S_KIND_TO_INFRA_KIND = {
4913
5123
  Service: "k8s-service",
@@ -4921,15 +5131,15 @@ var K8S_KIND_TO_INFRA_KIND = {
4921
5131
  async function walkYamlFiles2(start, depth = 0, max = 5) {
4922
5132
  if (depth > max) return [];
4923
5133
  const out = [];
4924
- const entries = await import_node_fs17.promises.readdir(start, { withFileTypes: true }).catch(() => []);
5134
+ const entries = await import_node_fs18.promises.readdir(start, { withFileTypes: true }).catch(() => []);
4925
5135
  for (const entry2 of entries) {
4926
5136
  if (entry2.isDirectory()) {
4927
5137
  if (IGNORED_DIRS.has(entry2.name)) continue;
4928
- const child = import_node_path30.default.join(start, entry2.name);
5138
+ const child = import_node_path32.default.join(start, entry2.name);
4929
5139
  if (await isPythonVenvDir(child)) continue;
4930
5140
  out.push(...await walkYamlFiles2(child, depth + 1, max));
4931
- } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path30.default.extname(entry2.name))) {
4932
- out.push(import_node_path30.default.join(start, entry2.name));
5141
+ } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path32.default.extname(entry2.name))) {
5142
+ out.push(import_node_path32.default.join(start, entry2.name));
4933
5143
  }
4934
5144
  }
4935
5145
  return out;
@@ -4938,7 +5148,7 @@ async function addK8sResources(graph, scanPath) {
4938
5148
  let nodesAdded = 0;
4939
5149
  const files = await walkYamlFiles2(scanPath);
4940
5150
  for (const file of files) {
4941
- const content = await import_node_fs17.promises.readFile(file, "utf8");
5151
+ const content = await import_node_fs18.promises.readFile(file, "utf8");
4942
5152
  let docs;
4943
5153
  try {
4944
5154
  docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
@@ -4973,17 +5183,17 @@ async function addInfra(graph, scanPath, services) {
4973
5183
  }
4974
5184
 
4975
5185
  // src/extract/index.ts
4976
- var import_node_path32 = __toESM(require("path"), 1);
5186
+ var import_node_path34 = __toESM(require("path"), 1);
4977
5187
 
4978
5188
  // src/extract/retire.ts
4979
5189
  init_cjs_shims();
4980
- var import_node_fs18 = require("fs");
4981
- var import_node_path31 = __toESM(require("path"), 1);
4982
- var import_types20 = require("@neat.is/types");
5190
+ var import_node_fs19 = require("fs");
5191
+ var import_node_path33 = __toESM(require("path"), 1);
5192
+ var import_types21 = require("@neat.is/types");
4983
5193
  function dropOrphanedFileNodes(graph) {
4984
5194
  const orphans = [];
4985
5195
  graph.forEachNode((id, attrs) => {
4986
- if (attrs.type !== import_types20.NodeType.FileNode) return;
5196
+ if (attrs.type !== import_types21.NodeType.FileNode) return;
4987
5197
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
4988
5198
  orphans.push(id);
4989
5199
  }
@@ -4996,7 +5206,7 @@ function retireEdgesByFile(graph, file) {
4996
5206
  const toDrop = [];
4997
5207
  graph.forEachEdge((id, attrs) => {
4998
5208
  const edge = attrs;
4999
- if (edge.provenance !== import_types20.Provenance.EXTRACTED) return;
5209
+ if (edge.provenance !== import_types21.Provenance.EXTRACTED) return;
5000
5210
  if (!edge.evidence?.file) return;
5001
5211
  if (edge.evidence.file === normalized) toDrop.push(id);
5002
5212
  });
@@ -5009,14 +5219,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
5009
5219
  const bases = [scanPath, ...serviceDirs];
5010
5220
  graph.forEachEdge((id, attrs) => {
5011
5221
  const edge = attrs;
5012
- if (edge.provenance !== import_types20.Provenance.EXTRACTED) return;
5222
+ if (edge.provenance !== import_types21.Provenance.EXTRACTED) return;
5013
5223
  const evidenceFile = edge.evidence?.file;
5014
5224
  if (!evidenceFile) return;
5015
- if (import_node_path31.default.isAbsolute(evidenceFile)) {
5016
- if (!(0, import_node_fs18.existsSync)(evidenceFile)) toDrop.push(id);
5225
+ if (import_node_path33.default.isAbsolute(evidenceFile)) {
5226
+ if (!(0, import_node_fs19.existsSync)(evidenceFile)) toDrop.push(id);
5017
5227
  return;
5018
5228
  }
5019
- const found = bases.some((base) => (0, import_node_fs18.existsSync)(import_node_path31.default.join(base, evidenceFile)));
5229
+ const found = bases.some((base) => (0, import_node_fs19.existsSync)(import_node_path33.default.join(base, evidenceFile)));
5020
5230
  if (!found) toDrop.push(id);
5021
5231
  });
5022
5232
  for (const id of toDrop) graph.dropEdge(id);
@@ -5056,7 +5266,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
5056
5266
  }
5057
5267
  const droppedEntries = drainDroppedExtracted();
5058
5268
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
5059
- const rejectedPath = import_node_path32.default.join(import_node_path32.default.dirname(opts.errorsPath), "rejected.ndjson");
5269
+ const rejectedPath = import_node_path34.default.join(import_node_path34.default.dirname(opts.errorsPath), "rejected.ndjson");
5060
5270
  try {
5061
5271
  await writeRejectedExtracted(droppedEntries, rejectedPath);
5062
5272
  } catch (err) {
@@ -5090,7 +5300,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
5090
5300
 
5091
5301
  // src/divergences.ts
5092
5302
  init_cjs_shims();
5093
- var import_types21 = require("@neat.is/types");
5303
+ var import_types22 = require("@neat.is/types");
5094
5304
  function bucketKey(source, target, type) {
5095
5305
  return `${type}|${source}|${target}`;
5096
5306
  }
@@ -5098,22 +5308,22 @@ function bucketEdges(graph) {
5098
5308
  const buckets = /* @__PURE__ */ new Map();
5099
5309
  graph.forEachEdge((id, attrs) => {
5100
5310
  const e = attrs;
5101
- const parsed = (0, import_types21.parseEdgeId)(id);
5311
+ const parsed = (0, import_types22.parseEdgeId)(id);
5102
5312
  const provenance = parsed?.provenance ?? e.provenance;
5103
5313
  const key = bucketKey(e.source, e.target, e.type);
5104
5314
  const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
5105
5315
  switch (provenance) {
5106
- case import_types21.Provenance.EXTRACTED:
5316
+ case import_types22.Provenance.EXTRACTED:
5107
5317
  cur.extracted = e;
5108
5318
  break;
5109
- case import_types21.Provenance.OBSERVED:
5319
+ case import_types22.Provenance.OBSERVED:
5110
5320
  cur.observed = e;
5111
5321
  break;
5112
- case import_types21.Provenance.INFERRED:
5322
+ case import_types22.Provenance.INFERRED:
5113
5323
  cur.inferred = e;
5114
5324
  break;
5115
5325
  default:
5116
- if (e.provenance === import_types21.Provenance.STALE) cur.stale = e;
5326
+ if (e.provenance === import_types22.Provenance.STALE) cur.stale = e;
5117
5327
  }
5118
5328
  buckets.set(key, cur);
5119
5329
  });
@@ -5122,7 +5332,7 @@ function bucketEdges(graph) {
5122
5332
  function nodeIsFrontier(graph, nodeId) {
5123
5333
  if (!graph.hasNode(nodeId)) return false;
5124
5334
  const attrs = graph.getNodeAttributes(nodeId);
5125
- return attrs.type === import_types21.NodeType.FrontierNode;
5335
+ return attrs.type === import_types22.NodeType.FrontierNode;
5126
5336
  }
5127
5337
  function clampConfidence(n) {
5128
5338
  if (!Number.isFinite(n)) return 0;
@@ -5143,7 +5353,7 @@ function gradedConfidence(edge) {
5143
5353
  }
5144
5354
  function detectMissingDivergences(graph, bucket) {
5145
5355
  const out = [];
5146
- if (bucket.type === import_types21.EdgeType.CONTAINS) return out;
5356
+ if (bucket.type === import_types22.EdgeType.CONTAINS) return out;
5147
5357
  if (bucket.extracted && !bucket.observed) {
5148
5358
  if (!nodeIsFrontier(graph, bucket.target)) {
5149
5359
  out.push({
@@ -5184,7 +5394,7 @@ function declaredHostFor(svc) {
5184
5394
  function hasExtractedConfiguredBy(graph, svcId) {
5185
5395
  for (const edgeId of graph.outboundEdges(svcId)) {
5186
5396
  const e = graph.getEdgeAttributes(edgeId);
5187
- if (e.type === import_types21.EdgeType.CONFIGURED_BY && e.provenance === import_types21.Provenance.EXTRACTED) {
5397
+ if (e.type === import_types22.EdgeType.CONFIGURED_BY && e.provenance === import_types22.Provenance.EXTRACTED) {
5188
5398
  return true;
5189
5399
  }
5190
5400
  }
@@ -5197,10 +5407,10 @@ function detectHostMismatch(graph, svcId, svc) {
5197
5407
  const out = [];
5198
5408
  for (const edgeId of graph.outboundEdges(svcId)) {
5199
5409
  const edge = graph.getEdgeAttributes(edgeId);
5200
- if (edge.type !== import_types21.EdgeType.CONNECTS_TO) continue;
5201
- if (edge.provenance !== import_types21.Provenance.OBSERVED) continue;
5410
+ if (edge.type !== import_types22.EdgeType.CONNECTS_TO) continue;
5411
+ if (edge.provenance !== import_types22.Provenance.OBSERVED) continue;
5202
5412
  const target = graph.getNodeAttributes(edge.target);
5203
- if (target.type !== import_types21.NodeType.DatabaseNode) continue;
5413
+ if (target.type !== import_types22.NodeType.DatabaseNode) continue;
5204
5414
  const observedHost = target.host?.trim();
5205
5415
  if (!observedHost) continue;
5206
5416
  if (observedHost === declaredHost) continue;
@@ -5222,10 +5432,10 @@ function detectCompatDivergences(graph, svcId, svc) {
5222
5432
  const deps = svc.dependencies ?? {};
5223
5433
  for (const edgeId of graph.outboundEdges(svcId)) {
5224
5434
  const edge = graph.getEdgeAttributes(edgeId);
5225
- if (edge.type !== import_types21.EdgeType.CONNECTS_TO) continue;
5226
- if (edge.provenance !== import_types21.Provenance.OBSERVED) continue;
5435
+ if (edge.type !== import_types22.EdgeType.CONNECTS_TO) continue;
5436
+ if (edge.provenance !== import_types22.Provenance.OBSERVED) continue;
5227
5437
  const target = graph.getNodeAttributes(edge.target);
5228
- if (target.type !== import_types21.NodeType.DatabaseNode) continue;
5438
+ if (target.type !== import_types22.NodeType.DatabaseNode) continue;
5229
5439
  for (const pair of compatPairs()) {
5230
5440
  if (pair.engine !== target.engine) continue;
5231
5441
  const declared = deps[pair.driver];
@@ -5286,7 +5496,7 @@ function computeDivergences(graph, opts = {}) {
5286
5496
  }
5287
5497
  graph.forEachNode((nodeId, attrs) => {
5288
5498
  const n = attrs;
5289
- if (n.type !== import_types21.NodeType.ServiceNode) return;
5499
+ if (n.type !== import_types22.NodeType.ServiceNode) return;
5290
5500
  const svc = n;
5291
5501
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
5292
5502
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
@@ -5319,7 +5529,7 @@ function computeDivergences(graph, opts = {}) {
5319
5529
  if (a.source !== b.source) return a.source.localeCompare(b.source);
5320
5530
  return a.target.localeCompare(b.target);
5321
5531
  });
5322
- return import_types21.DivergenceResultSchema.parse({
5532
+ return import_types22.DivergenceResultSchema.parse({
5323
5533
  divergences: filtered,
5324
5534
  totalAffected: filtered.length,
5325
5535
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -5328,9 +5538,9 @@ function computeDivergences(graph, opts = {}) {
5328
5538
 
5329
5539
  // src/persist.ts
5330
5540
  init_cjs_shims();
5331
- var import_node_fs19 = require("fs");
5332
- var import_node_path33 = __toESM(require("path"), 1);
5333
- var import_types22 = require("@neat.is/types");
5541
+ var import_node_fs20 = require("fs");
5542
+ var import_node_path35 = __toESM(require("path"), 1);
5543
+ var import_types23 = require("@neat.is/types");
5334
5544
  var SCHEMA_VERSION = 4;
5335
5545
  function migrateV1ToV2(payload) {
5336
5546
  const nodes = payload.graph.nodes;
@@ -5352,12 +5562,12 @@ function migrateV2ToV3(payload) {
5352
5562
  for (const edge of edges) {
5353
5563
  const attrs = edge.attributes;
5354
5564
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
5355
- attrs.provenance = import_types22.Provenance.OBSERVED;
5565
+ attrs.provenance = import_types23.Provenance.OBSERVED;
5356
5566
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
5357
5567
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
5358
5568
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
5359
5569
  if (type && source && target) {
5360
- const newId = (0, import_types22.observedEdgeId)(source, target, type);
5570
+ const newId = (0, import_types23.observedEdgeId)(source, target, type);
5361
5571
  attrs.id = newId;
5362
5572
  if (edge.key) edge.key = newId;
5363
5573
  }
@@ -5366,7 +5576,7 @@ function migrateV2ToV3(payload) {
5366
5576
  return { ...payload, schemaVersion: 3 };
5367
5577
  }
5368
5578
  async function ensureDir(filePath) {
5369
- await import_node_fs19.promises.mkdir(import_node_path33.default.dirname(filePath), { recursive: true });
5579
+ await import_node_fs20.promises.mkdir(import_node_path35.default.dirname(filePath), { recursive: true });
5370
5580
  }
5371
5581
  async function saveGraphToDisk(graph, outPath) {
5372
5582
  await ensureDir(outPath);
@@ -5376,13 +5586,13 @@ async function saveGraphToDisk(graph, outPath) {
5376
5586
  graph: graph.export()
5377
5587
  };
5378
5588
  const tmp = `${outPath}.tmp`;
5379
- await import_node_fs19.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
5380
- await import_node_fs19.promises.rename(tmp, outPath);
5589
+ await import_node_fs20.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
5590
+ await import_node_fs20.promises.rename(tmp, outPath);
5381
5591
  }
5382
5592
  async function loadGraphFromDisk(graph, outPath) {
5383
5593
  let raw;
5384
5594
  try {
5385
- raw = await import_node_fs19.promises.readFile(outPath, "utf8");
5595
+ raw = await import_node_fs20.promises.readFile(outPath, "utf8");
5386
5596
  } catch (err) {
5387
5597
  if (err.code === "ENOENT") return;
5388
5598
  throw err;
@@ -5441,8 +5651,8 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
5441
5651
 
5442
5652
  // src/gitignore.ts
5443
5653
  init_cjs_shims();
5444
- var import_node_fs20 = require("fs");
5445
- var import_node_path34 = __toESM(require("path"), 1);
5654
+ var import_node_fs21 = require("fs");
5655
+ var import_node_path36 = __toESM(require("path"), 1);
5446
5656
  var NEAT_OUT_LINE = "neat-out/";
5447
5657
  var NEAT_HEADER = "# NEAT \u2014 machine-local snapshots and events";
5448
5658
  function isNeatOutLine(line) {
@@ -5450,15 +5660,15 @@ function isNeatOutLine(line) {
5450
5660
  return trimmed === "neat-out/" || trimmed === "neat-out";
5451
5661
  }
5452
5662
  async function ensureNeatOutIgnored(projectDir) {
5453
- const file = import_node_path34.default.join(projectDir, ".gitignore");
5663
+ const file = import_node_path36.default.join(projectDir, ".gitignore");
5454
5664
  let existing = null;
5455
5665
  try {
5456
- existing = await import_node_fs20.promises.readFile(file, "utf8");
5666
+ existing = await import_node_fs21.promises.readFile(file, "utf8");
5457
5667
  } catch (err) {
5458
5668
  if (err.code !== "ENOENT") throw err;
5459
5669
  }
5460
5670
  if (existing === null) {
5461
- await import_node_fs20.promises.writeFile(file, `${NEAT_HEADER}
5671
+ await import_node_fs21.promises.writeFile(file, `${NEAT_HEADER}
5462
5672
  ${NEAT_OUT_LINE}
5463
5673
  `, "utf8");
5464
5674
  return { action: "created", file };
@@ -5471,13 +5681,13 @@ ${NEAT_OUT_LINE}
5471
5681
  ${NEAT_HEADER}
5472
5682
  ${NEAT_OUT_LINE}
5473
5683
  `;
5474
- await import_node_fs20.promises.writeFile(file, existing + appended, "utf8");
5684
+ await import_node_fs21.promises.writeFile(file, existing + appended, "utf8");
5475
5685
  return { action: "added", file };
5476
5686
  }
5477
5687
 
5478
5688
  // src/summary.ts
5479
5689
  init_cjs_shims();
5480
- var import_types23 = require("@neat.is/types");
5690
+ var import_types24 = require("@neat.is/types");
5481
5691
  function renderOtelEnvBlock() {
5482
5692
  return [
5483
5693
  "for prod OTel routing, set these in your deploy platform's env:",
@@ -5487,19 +5697,19 @@ function renderOtelEnvBlock() {
5487
5697
  }
5488
5698
  function findIncompatServices(nodes) {
5489
5699
  return nodes.filter(
5490
- (n) => n.type === import_types23.NodeType.ServiceNode && Array.isArray(n.incompatibilities) && (n.incompatibilities ?? []).length > 0
5700
+ (n) => n.type === import_types24.NodeType.ServiceNode && Array.isArray(n.incompatibilities) && (n.incompatibilities ?? []).length > 0
5491
5701
  );
5492
5702
  }
5493
5703
  function servicesWithoutObserved(nodes, edges) {
5494
5704
  const seen = /* @__PURE__ */ new Set();
5495
5705
  for (const e of edges) {
5496
- if (e.provenance === import_types23.Provenance.OBSERVED) {
5706
+ if (e.provenance === import_types24.Provenance.OBSERVED) {
5497
5707
  seen.add(e.source);
5498
5708
  seen.add(e.target);
5499
5709
  }
5500
5710
  }
5501
5711
  return nodes.filter(
5502
- (n) => n.type === import_types23.NodeType.ServiceNode && !seen.has(n.id)
5712
+ (n) => n.type === import_types24.NodeType.ServiceNode && !seen.has(n.id)
5503
5713
  );
5504
5714
  }
5505
5715
  function formatDivergence(d) {
@@ -5573,27 +5783,27 @@ function formatIncompat(inc) {
5573
5783
 
5574
5784
  // src/watch.ts
5575
5785
  init_cjs_shims();
5576
- var import_node_fs26 = __toESM(require("fs"), 1);
5577
- var import_node_path42 = __toESM(require("path"), 1);
5786
+ var import_node_fs27 = __toESM(require("fs"), 1);
5787
+ var import_node_path44 = __toESM(require("path"), 1);
5578
5788
  var import_chokidar = __toESM(require("chokidar"), 1);
5579
5789
 
5580
5790
  // src/api.ts
5581
5791
  init_cjs_shims();
5582
5792
  var import_fastify = __toESM(require("fastify"), 1);
5583
5793
  var import_cors = __toESM(require("@fastify/cors"), 1);
5584
- var import_types25 = require("@neat.is/types");
5794
+ var import_types26 = require("@neat.is/types");
5585
5795
 
5586
5796
  // src/extend/index.ts
5587
5797
  init_cjs_shims();
5588
- var import_node_fs22 = require("fs");
5589
- var import_node_path36 = __toESM(require("path"), 1);
5798
+ var import_node_fs23 = require("fs");
5799
+ var import_node_path38 = __toESM(require("path"), 1);
5590
5800
  var import_node_os2 = __toESM(require("os"), 1);
5591
5801
  var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
5592
5802
 
5593
5803
  // src/installers/package-manager.ts
5594
5804
  init_cjs_shims();
5595
- var import_node_fs21 = require("fs");
5596
- var import_node_path35 = __toESM(require("path"), 1);
5805
+ var import_node_fs22 = require("fs");
5806
+ var import_node_path37 = __toESM(require("path"), 1);
5597
5807
  var import_node_child_process = require("child_process");
5598
5808
  var LOCKFILE_PRIORITY = [
5599
5809
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -5608,29 +5818,29 @@ var LOCKFILE_PRIORITY = [
5608
5818
  var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
5609
5819
  async function exists2(p) {
5610
5820
  try {
5611
- await import_node_fs21.promises.access(p);
5821
+ await import_node_fs22.promises.access(p);
5612
5822
  return true;
5613
5823
  } catch {
5614
5824
  return false;
5615
5825
  }
5616
5826
  }
5617
5827
  async function detectPackageManager(serviceDir) {
5618
- let dir = import_node_path35.default.resolve(serviceDir);
5828
+ let dir = import_node_path37.default.resolve(serviceDir);
5619
5829
  const stops = /* @__PURE__ */ new Set();
5620
5830
  for (let i = 0; i < 64; i++) {
5621
5831
  if (stops.has(dir)) break;
5622
5832
  stops.add(dir);
5623
5833
  for (const candidate of LOCKFILE_PRIORITY) {
5624
- const lockPath = import_node_path35.default.join(dir, candidate.lockfile);
5834
+ const lockPath = import_node_path37.default.join(dir, candidate.lockfile);
5625
5835
  if (await exists2(lockPath)) {
5626
5836
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
5627
5837
  }
5628
5838
  }
5629
- const parent = import_node_path35.default.dirname(dir);
5839
+ const parent = import_node_path37.default.dirname(dir);
5630
5840
  if (parent === dir) break;
5631
5841
  dir = parent;
5632
5842
  }
5633
- return { pm: "npm", cwd: import_node_path35.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
5843
+ return { pm: "npm", cwd: import_node_path37.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
5634
5844
  }
5635
5845
  async function runPackageManagerInstall(cmd) {
5636
5846
  return new Promise((resolve) => {
@@ -5672,30 +5882,30 @@ ${err.message}`
5672
5882
  // src/extend/index.ts
5673
5883
  async function fileExists2(p) {
5674
5884
  try {
5675
- await import_node_fs22.promises.access(p);
5885
+ await import_node_fs23.promises.access(p);
5676
5886
  return true;
5677
5887
  } catch {
5678
5888
  return false;
5679
5889
  }
5680
5890
  }
5681
5891
  async function readPackageJson(scanPath) {
5682
- const pkgPath = import_node_path36.default.join(scanPath, "package.json");
5683
- const raw = await import_node_fs22.promises.readFile(pkgPath, "utf8");
5892
+ const pkgPath = import_node_path38.default.join(scanPath, "package.json");
5893
+ const raw = await import_node_fs23.promises.readFile(pkgPath, "utf8");
5684
5894
  return JSON.parse(raw);
5685
5895
  }
5686
5896
  async function findHookFiles(scanPath) {
5687
- const entries = await import_node_fs22.promises.readdir(scanPath);
5897
+ const entries = await import_node_fs23.promises.readdir(scanPath);
5688
5898
  return entries.filter(
5689
5899
  (e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
5690
5900
  ).sort();
5691
5901
  }
5692
5902
  function extendLogPath() {
5693
- return process.env.NEAT_EXTEND_LOG ?? import_node_path36.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
5903
+ return process.env.NEAT_EXTEND_LOG ?? import_node_path38.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
5694
5904
  }
5695
5905
  async function appendExtendLog(entry2) {
5696
5906
  const logPath = extendLogPath();
5697
- await import_node_fs22.promises.mkdir(import_node_path36.default.dirname(logPath), { recursive: true });
5698
- await import_node_fs22.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
5907
+ await import_node_fs23.promises.mkdir(import_node_path38.default.dirname(logPath), { recursive: true });
5908
+ await import_node_fs23.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
5699
5909
  }
5700
5910
  function splicedContent(fileContent, snippet2) {
5701
5911
  if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
@@ -5753,7 +5963,7 @@ function lookupInstrumentation(library, installedVersion) {
5753
5963
  }
5754
5964
  async function describeProjectInstrumentation(ctx) {
5755
5965
  const hookFiles = await findHookFiles(ctx.scanPath);
5756
- const envNeat = await fileExists2(import_node_path36.default.join(ctx.scanPath, ".env.neat"));
5966
+ const envNeat = await fileExists2(import_node_path38.default.join(ctx.scanPath, ".env.neat"));
5757
5967
  const registryInstrPackages = new Set(
5758
5968
  (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
5759
5969
  );
@@ -5775,31 +5985,31 @@ async function applyExtension(ctx, args, options) {
5775
5985
  );
5776
5986
  }
5777
5987
  for (const file of hookFiles) {
5778
- const content = await import_node_fs22.promises.readFile(import_node_path36.default.join(ctx.scanPath, file), "utf8");
5988
+ const content = await import_node_fs23.promises.readFile(import_node_path38.default.join(ctx.scanPath, file), "utf8");
5779
5989
  if (content.includes(args.registration_snippet)) {
5780
5990
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
5781
5991
  }
5782
5992
  }
5783
5993
  const primaryFile = hookFiles[0];
5784
- const primaryPath = import_node_path36.default.join(ctx.scanPath, primaryFile);
5994
+ const primaryPath = import_node_path38.default.join(ctx.scanPath, primaryFile);
5785
5995
  const filesTouched = [];
5786
5996
  const depsAdded = [];
5787
- const pkgPath = import_node_path36.default.join(ctx.scanPath, "package.json");
5997
+ const pkgPath = import_node_path38.default.join(ctx.scanPath, "package.json");
5788
5998
  const pkg = await readPackageJson(ctx.scanPath);
5789
5999
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
5790
6000
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
5791
- await import_node_fs22.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
6001
+ await import_node_fs23.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
5792
6002
  filesTouched.push("package.json");
5793
6003
  depsAdded.push(`${args.instrumentation_package}@${args.version}`);
5794
6004
  }
5795
- const hookContent = await import_node_fs22.promises.readFile(primaryPath, "utf8");
6005
+ const hookContent = await import_node_fs23.promises.readFile(primaryPath, "utf8");
5796
6006
  const patched = splicedContent(hookContent, args.registration_snippet);
5797
6007
  if (!patched) {
5798
6008
  throw new Error(
5799
6009
  `Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
5800
6010
  );
5801
6011
  }
5802
- await import_node_fs22.promises.writeFile(primaryPath, patched, "utf8");
6012
+ await import_node_fs23.promises.writeFile(primaryPath, patched, "utf8");
5803
6013
  filesTouched.push(primaryFile);
5804
6014
  const cmd = await detectPackageManager(ctx.scanPath);
5805
6015
  const installer = options?.runInstall ?? runPackageManagerInstall;
@@ -5830,7 +6040,7 @@ async function dryRunExtension(ctx, args) {
5830
6040
  };
5831
6041
  }
5832
6042
  for (const file of hookFiles) {
5833
- const content = await import_node_fs22.promises.readFile(import_node_path36.default.join(ctx.scanPath, file), "utf8");
6043
+ const content = await import_node_fs23.promises.readFile(import_node_path38.default.join(ctx.scanPath, file), "utf8");
5834
6044
  if (content.includes(args.registration_snippet)) {
5835
6045
  return {
5836
6046
  library: args.library,
@@ -5852,7 +6062,7 @@ async function dryRunExtension(ctx, args) {
5852
6062
  depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
5853
6063
  filesTouched.push("package.json");
5854
6064
  }
5855
- const hookContent = await import_node_fs22.promises.readFile(import_node_path36.default.join(ctx.scanPath, primaryFile), "utf8");
6065
+ const hookContent = await import_node_fs23.promises.readFile(import_node_path38.default.join(ctx.scanPath, primaryFile), "utf8");
5856
6066
  const patched = splicedContent(hookContent, args.registration_snippet);
5857
6067
  if (patched) {
5858
6068
  filesTouched.push(primaryFile);
@@ -5867,28 +6077,28 @@ async function rollbackExtension(ctx, args) {
5867
6077
  if (!await fileExists2(logPath)) {
5868
6078
  return { undone: false, message: "no apply found for library" };
5869
6079
  }
5870
- const raw = await import_node_fs22.promises.readFile(logPath, "utf8");
6080
+ const raw = await import_node_fs23.promises.readFile(logPath, "utf8");
5871
6081
  const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
5872
6082
  const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
5873
6083
  if (!match) {
5874
6084
  return { undone: false, message: "no apply found for library" };
5875
6085
  }
5876
- const pkgPath = import_node_path36.default.join(ctx.scanPath, "package.json");
6086
+ const pkgPath = import_node_path38.default.join(ctx.scanPath, "package.json");
5877
6087
  if (await fileExists2(pkgPath)) {
5878
6088
  const pkg = await readPackageJson(ctx.scanPath);
5879
6089
  if (pkg.dependencies?.[match.instrumentation_package]) {
5880
6090
  const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
5881
6091
  pkg.dependencies = rest;
5882
- await import_node_fs22.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
6092
+ await import_node_fs23.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
5883
6093
  }
5884
6094
  }
5885
6095
  const hookFiles = await findHookFiles(ctx.scanPath);
5886
6096
  for (const file of hookFiles) {
5887
- const filePath = import_node_path36.default.join(ctx.scanPath, file);
5888
- const content = await import_node_fs22.promises.readFile(filePath, "utf8");
6097
+ const filePath = import_node_path38.default.join(ctx.scanPath, file);
6098
+ const content = await import_node_fs23.promises.readFile(filePath, "utf8");
5889
6099
  if (content.includes(match.registration_snippet)) {
5890
6100
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
5891
- await import_node_fs22.promises.writeFile(filePath, filtered, "utf8");
6101
+ await import_node_fs23.promises.writeFile(filePath, filtered, "utf8");
5892
6102
  break;
5893
6103
  }
5894
6104
  }
@@ -5900,7 +6110,7 @@ async function rollbackExtension(ctx, args) {
5900
6110
 
5901
6111
  // src/diff.ts
5902
6112
  init_cjs_shims();
5903
- var import_node_fs23 = require("fs");
6113
+ var import_node_fs24 = require("fs");
5904
6114
  async function loadSnapshotForDiff(target) {
5905
6115
  if (/^https?:\/\//i.test(target)) {
5906
6116
  const res = await fetch(target);
@@ -5909,7 +6119,7 @@ async function loadSnapshotForDiff(target) {
5909
6119
  }
5910
6120
  return await res.json();
5911
6121
  }
5912
- const raw = await import_node_fs23.promises.readFile(target, "utf8");
6122
+ const raw = await import_node_fs24.promises.readFile(target, "utf8");
5913
6123
  return JSON.parse(raw);
5914
6124
  }
5915
6125
  function indexEntries(entries) {
@@ -5977,23 +6187,23 @@ function canonicalJson(value) {
5977
6187
 
5978
6188
  // src/projects.ts
5979
6189
  init_cjs_shims();
5980
- var import_node_path37 = __toESM(require("path"), 1);
6190
+ var import_node_path39 = __toESM(require("path"), 1);
5981
6191
  function pathsForProject(project, baseDir) {
5982
6192
  if (project === DEFAULT_PROJECT) {
5983
6193
  return {
5984
- snapshotPath: import_node_path37.default.join(baseDir, "graph.json"),
5985
- errorsPath: import_node_path37.default.join(baseDir, "errors.ndjson"),
5986
- staleEventsPath: import_node_path37.default.join(baseDir, "stale-events.ndjson"),
5987
- embeddingsCachePath: import_node_path37.default.join(baseDir, "embeddings.json"),
5988
- policyViolationsPath: import_node_path37.default.join(baseDir, "policy-violations.ndjson")
6194
+ snapshotPath: import_node_path39.default.join(baseDir, "graph.json"),
6195
+ errorsPath: import_node_path39.default.join(baseDir, "errors.ndjson"),
6196
+ staleEventsPath: import_node_path39.default.join(baseDir, "stale-events.ndjson"),
6197
+ embeddingsCachePath: import_node_path39.default.join(baseDir, "embeddings.json"),
6198
+ policyViolationsPath: import_node_path39.default.join(baseDir, "policy-violations.ndjson")
5989
6199
  };
5990
6200
  }
5991
6201
  return {
5992
- snapshotPath: import_node_path37.default.join(baseDir, `${project}.json`),
5993
- errorsPath: import_node_path37.default.join(baseDir, `errors.${project}.ndjson`),
5994
- staleEventsPath: import_node_path37.default.join(baseDir, `stale-events.${project}.ndjson`),
5995
- embeddingsCachePath: import_node_path37.default.join(baseDir, `embeddings.${project}.json`),
5996
- policyViolationsPath: import_node_path37.default.join(baseDir, `policy-violations.${project}.ndjson`)
6202
+ snapshotPath: import_node_path39.default.join(baseDir, `${project}.json`),
6203
+ errorsPath: import_node_path39.default.join(baseDir, `errors.${project}.ndjson`),
6204
+ staleEventsPath: import_node_path39.default.join(baseDir, `stale-events.${project}.ndjson`),
6205
+ embeddingsCachePath: import_node_path39.default.join(baseDir, `embeddings.${project}.json`),
6206
+ policyViolationsPath: import_node_path39.default.join(baseDir, `policy-violations.${project}.ndjson`)
5997
6207
  };
5998
6208
  }
5999
6209
  var Projects = class {
@@ -6029,25 +6239,25 @@ var Projects = class {
6029
6239
 
6030
6240
  // src/registry.ts
6031
6241
  init_cjs_shims();
6032
- var import_node_fs24 = require("fs");
6242
+ var import_node_fs25 = require("fs");
6033
6243
  var import_node_os3 = __toESM(require("os"), 1);
6034
- var import_node_path38 = __toESM(require("path"), 1);
6035
- var import_types24 = require("@neat.is/types");
6244
+ var import_node_path40 = __toESM(require("path"), 1);
6245
+ var import_types25 = require("@neat.is/types");
6036
6246
  var LOCK_TIMEOUT_MS = 5e3;
6037
6247
  var LOCK_RETRY_MS = 50;
6038
6248
  function neatHome() {
6039
6249
  const override = process.env.NEAT_HOME;
6040
- if (override && override.length > 0) return import_node_path38.default.resolve(override);
6041
- return import_node_path38.default.join(import_node_os3.default.homedir(), ".neat");
6250
+ if (override && override.length > 0) return import_node_path40.default.resolve(override);
6251
+ return import_node_path40.default.join(import_node_os3.default.homedir(), ".neat");
6042
6252
  }
6043
6253
  function registryPath() {
6044
- return import_node_path38.default.join(neatHome(), "projects.json");
6254
+ return import_node_path40.default.join(neatHome(), "projects.json");
6045
6255
  }
6046
6256
  function registryLockPath() {
6047
- return import_node_path38.default.join(neatHome(), "projects.json.lock");
6257
+ return import_node_path40.default.join(neatHome(), "projects.json.lock");
6048
6258
  }
6049
6259
  function daemonPidPath() {
6050
- return import_node_path38.default.join(neatHome(), "neatd.pid");
6260
+ return import_node_path40.default.join(neatHome(), "neatd.pid");
6051
6261
  }
6052
6262
  function isPidAliveDefault(pid) {
6053
6263
  try {
@@ -6059,7 +6269,7 @@ function isPidAliveDefault(pid) {
6059
6269
  }
6060
6270
  async function readPidFile(file) {
6061
6271
  try {
6062
- const raw = await import_node_fs24.promises.readFile(file, "utf8");
6272
+ const raw = await import_node_fs25.promises.readFile(file, "utf8");
6063
6273
  const pid = Number.parseInt(raw.trim(), 10);
6064
6274
  return Number.isInteger(pid) && pid > 0 ? pid : void 0;
6065
6275
  } catch {
@@ -6107,32 +6317,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
6107
6317
  }
6108
6318
  }
6109
6319
  async function normalizeProjectPath(input) {
6110
- const resolved = import_node_path38.default.resolve(input);
6320
+ const resolved = import_node_path40.default.resolve(input);
6111
6321
  try {
6112
- return await import_node_fs24.promises.realpath(resolved);
6322
+ return await import_node_fs25.promises.realpath(resolved);
6113
6323
  } catch {
6114
6324
  return resolved;
6115
6325
  }
6116
6326
  }
6117
6327
  async function writeAtomically(target, contents) {
6118
- await import_node_fs24.promises.mkdir(import_node_path38.default.dirname(target), { recursive: true });
6328
+ await import_node_fs25.promises.mkdir(import_node_path40.default.dirname(target), { recursive: true });
6119
6329
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
6120
- const fd = await import_node_fs24.promises.open(tmp, "w");
6330
+ const fd = await import_node_fs25.promises.open(tmp, "w");
6121
6331
  try {
6122
6332
  await fd.writeFile(contents, "utf8");
6123
6333
  await fd.sync();
6124
6334
  } finally {
6125
6335
  await fd.close();
6126
6336
  }
6127
- await import_node_fs24.promises.rename(tmp, target);
6337
+ await import_node_fs25.promises.rename(tmp, target);
6128
6338
  }
6129
6339
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
6130
6340
  const deadline = Date.now() + timeoutMs;
6131
- await import_node_fs24.promises.mkdir(import_node_path38.default.dirname(lockPath), { recursive: true });
6341
+ await import_node_fs25.promises.mkdir(import_node_path40.default.dirname(lockPath), { recursive: true });
6132
6342
  let probedHolder = false;
6133
6343
  while (true) {
6134
6344
  try {
6135
- const fd = await import_node_fs24.promises.open(lockPath, "wx");
6345
+ const fd = await import_node_fs25.promises.open(lockPath, "wx");
6136
6346
  try {
6137
6347
  await fd.writeFile(`${process.pid}
6138
6348
  `, "utf8");
@@ -6157,7 +6367,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
6157
6367
  }
6158
6368
  }
6159
6369
  async function releaseLock(lockPath) {
6160
- await import_node_fs24.promises.unlink(lockPath).catch(() => {
6370
+ await import_node_fs25.promises.unlink(lockPath).catch(() => {
6161
6371
  });
6162
6372
  }
6163
6373
  async function withLock(fn) {
@@ -6173,7 +6383,7 @@ async function readRegistry() {
6173
6383
  const file = registryPath();
6174
6384
  let raw;
6175
6385
  try {
6176
- raw = await import_node_fs24.promises.readFile(file, "utf8");
6386
+ raw = await import_node_fs25.promises.readFile(file, "utf8");
6177
6387
  } catch (err) {
6178
6388
  if (err.code === "ENOENT") {
6179
6389
  return { version: 1, projects: [] };
@@ -6181,10 +6391,10 @@ async function readRegistry() {
6181
6391
  throw err;
6182
6392
  }
6183
6393
  const parsed = JSON.parse(raw);
6184
- return import_types24.RegistryFileSchema.parse(parsed);
6394
+ return import_types25.RegistryFileSchema.parse(parsed);
6185
6395
  }
6186
6396
  async function writeRegistry(reg) {
6187
- const validated = import_types24.RegistryFileSchema.parse(reg);
6397
+ const validated = import_types25.RegistryFileSchema.parse(reg);
6188
6398
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
6189
6399
  }
6190
6400
  var ProjectNameCollisionError = class extends Error {
@@ -6255,6 +6465,58 @@ async function removeProject(name) {
6255
6465
  return removed;
6256
6466
  });
6257
6467
  }
6468
+ var DAY_MS2 = 24 * 60 * 60 * 1e3;
6469
+ var DEFAULT_PRUNE_TTL_MS = 7 * DAY_MS2;
6470
+ function pruneTtlMs() {
6471
+ const raw = process.env.NEAT_REGISTRY_PRUNE_TTL_MS;
6472
+ if (!raw) return DEFAULT_PRUNE_TTL_MS;
6473
+ const n = Number.parseInt(raw, 10);
6474
+ if (Number.isFinite(n) && n >= 0) return n;
6475
+ console.warn(
6476
+ `[neat] NEAT_REGISTRY_PRUNE_TTL_MS could not be parsed (${raw}); using default ${DEFAULT_PRUNE_TTL_MS}ms`
6477
+ );
6478
+ return DEFAULT_PRUNE_TTL_MS;
6479
+ }
6480
+ async function statPathStatus(p) {
6481
+ try {
6482
+ const stat = await import_node_fs25.promises.stat(p);
6483
+ return stat.isDirectory() ? "present" : "unknown";
6484
+ } catch (err) {
6485
+ return err.code === "ENOENT" ? "gone" : "unknown";
6486
+ }
6487
+ }
6488
+ async function pruneRegistry(opts = {}) {
6489
+ const ttlMs = opts.ttlMs ?? pruneTtlMs();
6490
+ const statPath = opts.statPath ?? statPathStatus;
6491
+ const now = opts.now ?? Date.now;
6492
+ return withLock(async () => {
6493
+ const reg = await readRegistry();
6494
+ const removed = [];
6495
+ const kept = [];
6496
+ for (const entry2 of reg.projects) {
6497
+ const status2 = await statPath(entry2.path);
6498
+ if (status2 !== "gone") {
6499
+ kept.push(entry2);
6500
+ continue;
6501
+ }
6502
+ if (ttlMs <= 0) {
6503
+ removed.push(entry2);
6504
+ continue;
6505
+ }
6506
+ const lastSeen = Date.parse(entry2.lastSeenAt ?? entry2.registeredAt);
6507
+ const age = now() - (Number.isFinite(lastSeen) ? lastSeen : 0);
6508
+ if (age > ttlMs) {
6509
+ removed.push(entry2);
6510
+ } else {
6511
+ kept.push(entry2);
6512
+ }
6513
+ }
6514
+ if (removed.length === 0) return [];
6515
+ reg.projects = kept;
6516
+ await writeRegistry(reg);
6517
+ return removed;
6518
+ });
6519
+ }
6258
6520
 
6259
6521
  // src/streaming.ts
6260
6522
  init_cjs_shims();
@@ -6268,6 +6530,7 @@ function handleSse(req, reply, opts) {
6268
6530
  reply.raw.setHeader("Connection", "keep-alive");
6269
6531
  reply.raw.setHeader("X-Accel-Buffering", "no");
6270
6532
  reply.raw.flushHeaders?.();
6533
+ reply.raw.write(":open\n\n");
6271
6534
  let pending = 0;
6272
6535
  let dropped = false;
6273
6536
  const closeConnection = () => {
@@ -6447,11 +6710,11 @@ function registerRoutes(scope, ctx) {
6447
6710
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
6448
6711
  const parsed = [];
6449
6712
  for (const c of candidates) {
6450
- const r = import_types25.DivergenceTypeSchema.safeParse(c);
6713
+ const r = import_types26.DivergenceTypeSchema.safeParse(c);
6451
6714
  if (!r.success) {
6452
6715
  return reply.code(400).send({
6453
6716
  error: `unknown divergence type "${c}"`,
6454
- allowed: import_types25.DivergenceTypeSchema.options
6717
+ allowed: import_types26.DivergenceTypeSchema.options
6455
6718
  });
6456
6719
  }
6457
6720
  parsed.push(r.data);
@@ -6664,7 +6927,7 @@ function registerRoutes(scope, ctx) {
6664
6927
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
6665
6928
  let violations = await log.readAll();
6666
6929
  if (req.query.severity) {
6667
- const sev = import_types25.PolicySeveritySchema.safeParse(req.query.severity);
6930
+ const sev = import_types26.PolicySeveritySchema.safeParse(req.query.severity);
6668
6931
  if (!sev.success) {
6669
6932
  return reply.code(400).send({
6670
6933
  error: "invalid severity",
@@ -6681,7 +6944,7 @@ function registerRoutes(scope, ctx) {
6681
6944
  scope.post("/policies/check", async (req, reply) => {
6682
6945
  const proj = resolveProject(registry, req, reply, ctx.bootstrap);
6683
6946
  if (!proj) return;
6684
- const parsed = import_types25.PoliciesCheckBodySchema.safeParse(req.body ?? {});
6947
+ const parsed = import_types26.PoliciesCheckBodySchema.safeParse(req.body ?? {});
6685
6948
  if (!parsed.success) {
6686
6949
  return reply.code(400).send({
6687
6950
  error: "invalid /policies/check body",
@@ -6924,8 +7187,8 @@ init_otel_grpc();
6924
7187
 
6925
7188
  // src/search.ts
6926
7189
  init_cjs_shims();
6927
- var import_node_fs25 = require("fs");
6928
- var import_node_path41 = __toESM(require("path"), 1);
7190
+ var import_node_fs26 = require("fs");
7191
+ var import_node_path43 = __toESM(require("path"), 1);
6929
7192
  var import_node_crypto3 = require("crypto");
6930
7193
  var DEFAULT_LIMIT = 10;
6931
7194
  var NOMIC_DIM = 768;
@@ -7055,7 +7318,7 @@ async function pickEmbedder() {
7055
7318
  }
7056
7319
  async function readCache(cachePath) {
7057
7320
  try {
7058
- const raw = await import_node_fs25.promises.readFile(cachePath, "utf8");
7321
+ const raw = await import_node_fs26.promises.readFile(cachePath, "utf8");
7059
7322
  const parsed = JSON.parse(raw);
7060
7323
  if (parsed.version !== 1) return null;
7061
7324
  return parsed;
@@ -7064,8 +7327,8 @@ async function readCache(cachePath) {
7064
7327
  }
7065
7328
  }
7066
7329
  async function writeCache(cachePath, cache) {
7067
- await import_node_fs25.promises.mkdir(import_node_path41.default.dirname(cachePath), { recursive: true });
7068
- await import_node_fs25.promises.writeFile(cachePath, JSON.stringify(cache));
7330
+ await import_node_fs26.promises.mkdir(import_node_path43.default.dirname(cachePath), { recursive: true });
7331
+ await import_node_fs26.promises.writeFile(cachePath, JSON.stringify(cache));
7069
7332
  }
7070
7333
  var VectorIndex = class {
7071
7334
  constructor(embedder, cachePath) {
@@ -7221,8 +7484,8 @@ var ALL_PHASES = [
7221
7484
  ];
7222
7485
  function classifyChange(relPath) {
7223
7486
  const phases = /* @__PURE__ */ new Set();
7224
- const base = import_node_path42.default.basename(relPath).toLowerCase();
7225
- const segments = relPath.split(import_node_path42.default.sep).map((s) => s.toLowerCase());
7487
+ const base = import_node_path44.default.basename(relPath).toLowerCase();
7488
+ const segments = relPath.split(import_node_path44.default.sep).map((s) => s.toLowerCase());
7226
7489
  if (base === "package.json" || base === "requirements.txt" || base === "pyproject.toml" || base === "setup.py") {
7227
7490
  phases.add("services");
7228
7491
  phases.add("aliases");
@@ -7336,16 +7599,16 @@ function countWatchableDirs(scanPath, limit) {
7336
7599
  if (count >= limit) return;
7337
7600
  let entries;
7338
7601
  try {
7339
- entries = import_node_fs26.default.readdirSync(dir, { withFileTypes: true });
7602
+ entries = import_node_fs27.default.readdirSync(dir, { withFileTypes: true });
7340
7603
  } catch {
7341
7604
  return;
7342
7605
  }
7343
7606
  for (const e of entries) {
7344
7607
  if (count >= limit) return;
7345
7608
  if (!e.isDirectory()) continue;
7346
- if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path42.default.join(dir, e.name) + import_node_path42.default.sep))) continue;
7609
+ if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path44.default.join(dir, e.name) + import_node_path44.default.sep))) continue;
7347
7610
  count++;
7348
- if (depth < 2) visit(import_node_path42.default.join(dir, e.name), depth + 1);
7611
+ if (depth < 2) visit(import_node_path44.default.join(dir, e.name), depth + 1);
7349
7612
  }
7350
7613
  };
7351
7614
  visit(scanPath, 0);
@@ -7363,8 +7626,8 @@ async function startWatch(graph, opts) {
7363
7626
  const projectName = opts.project ?? DEFAULT_PROJECT;
7364
7627
  await loadGraphFromDisk(graph, opts.outPath);
7365
7628
  const detachEventBus = attachGraphToEventBus(graph, { project: projectName });
7366
- const policyFilePath = import_node_path42.default.join(opts.scanPath, "policy.json");
7367
- const policyViolationsPath = import_node_path42.default.join(import_node_path42.default.dirname(opts.outPath), "policy-violations.ndjson");
7629
+ const policyFilePath = import_node_path44.default.join(opts.scanPath, "policy.json");
7630
+ const policyViolationsPath = import_node_path44.default.join(import_node_path44.default.dirname(opts.outPath), "policy-violations.ndjson");
7368
7631
  let policies = [];
7369
7632
  try {
7370
7633
  policies = await loadPolicyFile(policyFilePath);
@@ -7415,7 +7678,7 @@ async function startWatch(graph, opts) {
7415
7678
  assertBindAuthority(host, auth.authToken);
7416
7679
  const port = opts.port ?? 8080;
7417
7680
  const otelPort = opts.otelPort ?? 4318;
7418
- const cachePath = opts.embeddingsCachePath ?? import_node_path42.default.join(import_node_path42.default.dirname(opts.outPath), "embeddings.json");
7681
+ const cachePath = opts.embeddingsCachePath ?? import_node_path44.default.join(import_node_path44.default.dirname(opts.outPath), "embeddings.json");
7419
7682
  let searchIndex;
7420
7683
  try {
7421
7684
  searchIndex = await buildSearchIndex(graph, { cachePath });
@@ -7433,7 +7696,7 @@ async function startWatch(graph, opts) {
7433
7696
  // Paths are derived from the explicit options the watch caller passes
7434
7697
  // — pathsForProject is only used to fill in the embeddings/snapshot
7435
7698
  // fields so the registry shape is complete.
7436
- ...pathsForProject(projectName, import_node_path42.default.dirname(opts.outPath)),
7699
+ ...pathsForProject(projectName, import_node_path44.default.dirname(opts.outPath)),
7437
7700
  snapshotPath: opts.outPath,
7438
7701
  errorsPath: opts.errorsPath,
7439
7702
  staleEventsPath: opts.staleEventsPath
@@ -7520,9 +7783,9 @@ async function startWatch(graph, opts) {
7520
7783
  };
7521
7784
  const onPath = (absPath) => {
7522
7785
  if (shouldIgnore(absPath)) return;
7523
- const rel = import_node_path42.default.relative(opts.scanPath, absPath);
7786
+ const rel = import_node_path44.default.relative(opts.scanPath, absPath);
7524
7787
  if (!rel || rel.startsWith("..")) return;
7525
- pendingPaths.add(rel.split(import_node_path42.default.sep).join("/"));
7788
+ pendingPaths.add(rel.split(import_node_path44.default.sep).join("/"));
7526
7789
  const phases = classifyChange(rel);
7527
7790
  if (phases.size === 0) {
7528
7791
  for (const p of ALL_PHASES) pending.add(p);
@@ -7576,8 +7839,8 @@ async function startWatch(graph, opts) {
7576
7839
 
7577
7840
  // src/deploy/detect.ts
7578
7841
  init_cjs_shims();
7579
- var import_node_fs27 = require("fs");
7580
- var import_node_path43 = __toESM(require("path"), 1);
7842
+ var import_node_fs28 = require("fs");
7843
+ var import_node_path45 = __toESM(require("path"), 1);
7581
7844
  var import_node_child_process2 = require("child_process");
7582
7845
  var import_node_crypto4 = require("crypto");
7583
7846
  function generateToken() {
@@ -7677,21 +7940,21 @@ async function runDeploy(opts = {}) {
7677
7940
  const token = generateToken();
7678
7941
  switch (substrate) {
7679
7942
  case "docker-compose": {
7680
- const artifactPath = import_node_path43.default.join(cwd, "docker-compose.neat.yml");
7943
+ const artifactPath = import_node_path45.default.join(cwd, "docker-compose.neat.yml");
7681
7944
  const contents = emitDockerCompose(cwd);
7682
- await import_node_fs27.promises.writeFile(artifactPath, contents, "utf8");
7945
+ await import_node_fs28.promises.writeFile(artifactPath, contents, "utf8");
7683
7946
  return {
7684
7947
  substrate,
7685
7948
  artifactPath,
7686
7949
  token,
7687
7950
  contents,
7688
- startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path43.default.basename(artifactPath)} up -d`
7951
+ startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path45.default.basename(artifactPath)} up -d`
7689
7952
  };
7690
7953
  }
7691
7954
  case "systemd": {
7692
- const artifactPath = import_node_path43.default.join(cwd, "neat.service");
7955
+ const artifactPath = import_node_path45.default.join(cwd, "neat.service");
7693
7956
  const contents = emitSystemdUnit(cwd);
7694
- await import_node_fs27.promises.writeFile(artifactPath, contents, "utf8");
7957
+ await import_node_fs28.promises.writeFile(artifactPath, contents, "utf8");
7695
7958
  return {
7696
7959
  substrate,
7697
7960
  artifactPath,
@@ -7724,8 +7987,8 @@ init_cjs_shims();
7724
7987
 
7725
7988
  // src/installers/javascript.ts
7726
7989
  init_cjs_shims();
7727
- var import_node_fs28 = require("fs");
7728
- var import_node_path44 = __toESM(require("path"), 1);
7990
+ var import_node_fs29 = require("fs");
7991
+ var import_node_path46 = __toESM(require("path"), 1);
7729
7992
  var import_semver2 = __toESM(require("semver"), 1);
7730
7993
 
7731
7994
  // src/installers/templates.ts
@@ -8272,15 +8535,15 @@ var OTEL_ENV = {
8272
8535
  value: "http://localhost:4318/projects/<project>/v1/traces"
8273
8536
  };
8274
8537
  function serviceNodeName(pkg, serviceDir) {
8275
- return pkg.name ?? import_node_path44.default.basename(serviceDir);
8538
+ return pkg.name ?? import_node_path46.default.basename(serviceDir);
8276
8539
  }
8277
8540
  function projectToken(pkg, serviceDir, project) {
8278
8541
  if (project && project.length > 0) return project;
8279
- return pkg.name ?? import_node_path44.default.basename(serviceDir);
8542
+ return pkg.name ?? import_node_path46.default.basename(serviceDir);
8280
8543
  }
8281
8544
  async function readJsonFile(p) {
8282
8545
  try {
8283
- const raw = await import_node_fs28.promises.readFile(p, "utf8");
8546
+ const raw = await import_node_fs29.promises.readFile(p, "utf8");
8284
8547
  return JSON.parse(raw);
8285
8548
  } catch {
8286
8549
  return null;
@@ -8289,16 +8552,16 @@ async function readJsonFile(p) {
8289
8552
  async function detectRuntimeKind(pkgRoot, pkg) {
8290
8553
  const deps = allDeps(pkg);
8291
8554
  if ("react-native" in deps || "expo" in deps) return "react-native";
8292
- const appJson = await readJsonFile(import_node_path44.default.join(pkgRoot, "app.json"));
8555
+ const appJson = await readJsonFile(import_node_path46.default.join(pkgRoot, "app.json"));
8293
8556
  if (appJson && typeof appJson === "object" && "expo" in appJson) {
8294
8557
  return "react-native";
8295
8558
  }
8296
- if (await exists3(import_node_path44.default.join(pkgRoot, "vite.config.js")) || await exists3(import_node_path44.default.join(pkgRoot, "vite.config.ts")) || await exists3(import_node_path44.default.join(pkgRoot, "vite.config.mjs")) || "vite" in deps) {
8559
+ if (await exists3(import_node_path46.default.join(pkgRoot, "vite.config.js")) || await exists3(import_node_path46.default.join(pkgRoot, "vite.config.ts")) || await exists3(import_node_path46.default.join(pkgRoot, "vite.config.mjs")) || "vite" in deps) {
8297
8560
  return "browser-bundle";
8298
8561
  }
8299
- if (await exists3(import_node_path44.default.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
8300
- if (await exists3(import_node_path44.default.join(pkgRoot, "bun.lockb"))) return "bun";
8301
- if (await exists3(import_node_path44.default.join(pkgRoot, "deno.json")) || await exists3(import_node_path44.default.join(pkgRoot, "deno.lock"))) {
8562
+ if (await exists3(import_node_path46.default.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
8563
+ if (await exists3(import_node_path46.default.join(pkgRoot, "bun.lockb"))) return "bun";
8564
+ if (await exists3(import_node_path46.default.join(pkgRoot, "deno.json")) || await exists3(import_node_path46.default.join(pkgRoot, "deno.lock"))) {
8302
8565
  return "deno";
8303
8566
  }
8304
8567
  const engines = pkg.engines ?? {};
@@ -8307,7 +8570,7 @@ async function detectRuntimeKind(pkgRoot, pkg) {
8307
8570
  }
8308
8571
  async function readPackageJson2(serviceDir) {
8309
8572
  try {
8310
- const raw = await import_node_fs28.promises.readFile(import_node_path44.default.join(serviceDir, "package.json"), "utf8");
8573
+ const raw = await import_node_fs29.promises.readFile(import_node_path46.default.join(serviceDir, "package.json"), "utf8");
8311
8574
  return JSON.parse(raw);
8312
8575
  } catch {
8313
8576
  return null;
@@ -8315,7 +8578,7 @@ async function readPackageJson2(serviceDir) {
8315
8578
  }
8316
8579
  async function exists3(p) {
8317
8580
  try {
8318
- await import_node_fs28.promises.stat(p);
8581
+ await import_node_fs29.promises.stat(p);
8319
8582
  return true;
8320
8583
  } catch {
8321
8584
  return false;
@@ -8323,7 +8586,7 @@ async function exists3(p) {
8323
8586
  }
8324
8587
  async function readFileMaybe(p) {
8325
8588
  try {
8326
- return await import_node_fs28.promises.readFile(p, "utf8");
8589
+ return await import_node_fs29.promises.readFile(p, "utf8");
8327
8590
  } catch {
8328
8591
  return null;
8329
8592
  }
@@ -8351,7 +8614,7 @@ function needsVersionUpgrade(installed, expected) {
8351
8614
  var NEXT_CONFIG_CANDIDATES = ["next.config.js", "next.config.ts", "next.config.mjs"];
8352
8615
  async function findNextConfig(serviceDir) {
8353
8616
  for (const name of NEXT_CONFIG_CANDIDATES) {
8354
- const candidate = import_node_path44.default.join(serviceDir, name);
8617
+ const candidate = import_node_path46.default.join(serviceDir, name);
8355
8618
  if (await exists3(candidate)) return candidate;
8356
8619
  }
8357
8620
  return null;
@@ -8378,7 +8641,7 @@ function hasRemixDependency(pkg) {
8378
8641
  }
8379
8642
  async function findRemixEntry(serviceDir) {
8380
8643
  for (const rel of REMIX_ENTRY_CANDIDATES) {
8381
- const candidate = import_node_path44.default.join(serviceDir, rel);
8644
+ const candidate = import_node_path46.default.join(serviceDir, rel);
8382
8645
  if (await exists3(candidate)) return candidate;
8383
8646
  }
8384
8647
  return null;
@@ -8390,14 +8653,14 @@ function hasSvelteKitDependency(pkg) {
8390
8653
  }
8391
8654
  async function findSvelteKitHooks(serviceDir) {
8392
8655
  for (const rel of SVELTEKIT_HOOKS_CANDIDATES) {
8393
- const candidate = import_node_path44.default.join(serviceDir, rel);
8656
+ const candidate = import_node_path46.default.join(serviceDir, rel);
8394
8657
  if (await exists3(candidate)) return candidate;
8395
8658
  }
8396
8659
  return null;
8397
8660
  }
8398
8661
  async function findSvelteKitConfig(serviceDir) {
8399
8662
  for (const rel of SVELTEKIT_CONFIG_CANDIDATES) {
8400
- const candidate = import_node_path44.default.join(serviceDir, rel);
8663
+ const candidate = import_node_path46.default.join(serviceDir, rel);
8401
8664
  if (await exists3(candidate)) return candidate;
8402
8665
  }
8403
8666
  return null;
@@ -8408,7 +8671,7 @@ function hasNuxtDependency(pkg) {
8408
8671
  }
8409
8672
  async function findNuxtConfig(serviceDir) {
8410
8673
  for (const name of NUXT_CONFIG_CANDIDATES) {
8411
- const candidate = import_node_path44.default.join(serviceDir, name);
8674
+ const candidate = import_node_path46.default.join(serviceDir, name);
8412
8675
  if (await exists3(candidate)) return candidate;
8413
8676
  }
8414
8677
  return null;
@@ -8419,7 +8682,7 @@ function hasAstroDependency(pkg) {
8419
8682
  }
8420
8683
  async function findAstroConfig(serviceDir) {
8421
8684
  for (const name of ASTRO_CONFIG_CANDIDATES) {
8422
- const candidate = import_node_path44.default.join(serviceDir, name);
8685
+ const candidate = import_node_path46.default.join(serviceDir, name);
8423
8686
  if (await exists3(candidate)) return candidate;
8424
8687
  }
8425
8688
  return null;
@@ -8433,7 +8696,7 @@ function parseNextMajor(range) {
8433
8696
  return Number.isFinite(n) ? n : null;
8434
8697
  }
8435
8698
  async function isTypeScriptProject(serviceDir) {
8436
- return exists3(import_node_path44.default.join(serviceDir, "tsconfig.json"));
8699
+ return exists3(import_node_path46.default.join(serviceDir, "tsconfig.json"));
8437
8700
  }
8438
8701
  var INDEX_EXTENSIONS = [".ts", ".tsx", ".js", ".mjs", ".cjs"];
8439
8702
  var INDEX_CANDIDATES = INDEX_EXTENSIONS.map((ext) => `index${ext}`);
@@ -8479,7 +8742,7 @@ function entryFromScript(script) {
8479
8742
  }
8480
8743
  async function resolveEntry(serviceDir, pkg) {
8481
8744
  if (typeof pkg.main === "string" && pkg.main.length > 0) {
8482
- const candidate = import_node_path44.default.resolve(serviceDir, pkg.main);
8745
+ const candidate = import_node_path46.default.resolve(serviceDir, pkg.main);
8483
8746
  if (await exists3(candidate)) return candidate;
8484
8747
  }
8485
8748
  if (pkg.bin) {
@@ -8493,36 +8756,36 @@ async function resolveEntry(serviceDir, pkg) {
8493
8756
  if (typeof first === "string") binEntry = first;
8494
8757
  }
8495
8758
  if (binEntry) {
8496
- const candidate = import_node_path44.default.resolve(serviceDir, binEntry);
8759
+ const candidate = import_node_path46.default.resolve(serviceDir, binEntry);
8497
8760
  if (await exists3(candidate)) return candidate;
8498
8761
  }
8499
8762
  }
8500
8763
  const startEntry = entryFromScript(pkg.scripts?.start);
8501
8764
  if (startEntry) {
8502
- const candidate = import_node_path44.default.resolve(serviceDir, startEntry);
8765
+ const candidate = import_node_path46.default.resolve(serviceDir, startEntry);
8503
8766
  if (await exists3(candidate)) return candidate;
8504
8767
  }
8505
8768
  const devEntry = entryFromScript(pkg.scripts?.dev);
8506
8769
  if (devEntry) {
8507
- const candidate = import_node_path44.default.resolve(serviceDir, devEntry);
8770
+ const candidate = import_node_path46.default.resolve(serviceDir, devEntry);
8508
8771
  if (await exists3(candidate)) return candidate;
8509
8772
  }
8510
8773
  for (const rel of SRC_INDEX_CANDIDATES) {
8511
- const candidate = import_node_path44.default.join(serviceDir, rel);
8774
+ const candidate = import_node_path46.default.join(serviceDir, rel);
8512
8775
  if (await exists3(candidate)) return candidate;
8513
8776
  }
8514
8777
  for (const rel of SRC_NAMED_CANDIDATES) {
8515
- const candidate = import_node_path44.default.join(serviceDir, rel);
8778
+ const candidate = import_node_path46.default.join(serviceDir, rel);
8516
8779
  if (await exists3(candidate)) return candidate;
8517
8780
  }
8518
8781
  for (const name of INDEX_CANDIDATES) {
8519
- const candidate = import_node_path44.default.join(serviceDir, name);
8782
+ const candidate = import_node_path46.default.join(serviceDir, name);
8520
8783
  if (await exists3(candidate)) return candidate;
8521
8784
  }
8522
8785
  return null;
8523
8786
  }
8524
8787
  function dispatchEntry(entryFile, pkg) {
8525
- const ext = import_node_path44.default.extname(entryFile).toLowerCase();
8788
+ const ext = import_node_path46.default.extname(entryFile).toLowerCase();
8526
8789
  if (ext === ".ts" || ext === ".tsx") return "ts";
8527
8790
  if (ext === ".mjs") return "esm";
8528
8791
  if (ext === ".cjs") return "cjs";
@@ -8539,9 +8802,9 @@ function otelInitContents(flavor) {
8539
8802
  return OTEL_INIT_CJS;
8540
8803
  }
8541
8804
  function injectionLine(flavor, entryFile, otelInitFile) {
8542
- let rel = import_node_path44.default.relative(import_node_path44.default.dirname(entryFile), otelInitFile);
8805
+ let rel = import_node_path46.default.relative(import_node_path46.default.dirname(entryFile), otelInitFile);
8543
8806
  if (!rel.startsWith(".")) rel = `./${rel}`;
8544
- rel = rel.split(import_node_path44.default.sep).join("/");
8807
+ rel = rel.split(import_node_path46.default.sep).join("/");
8545
8808
  if (flavor === "cjs") return `require('${rel}')`;
8546
8809
  if (flavor === "esm") return `import '${rel}'`;
8547
8810
  const tsRel = rel.replace(/\.ts$/, "");
@@ -8554,23 +8817,23 @@ function lineIsOtelInjection(line) {
8554
8817
  }
8555
8818
  async function detectsSrcLayout(serviceDir) {
8556
8819
  const [hasSrcApp, hasSrcPages, hasRootApp, hasRootPages] = await Promise.all([
8557
- exists3(import_node_path44.default.join(serviceDir, "src", "app")),
8558
- exists3(import_node_path44.default.join(serviceDir, "src", "pages")),
8559
- exists3(import_node_path44.default.join(serviceDir, "app")),
8560
- exists3(import_node_path44.default.join(serviceDir, "pages"))
8820
+ exists3(import_node_path46.default.join(serviceDir, "src", "app")),
8821
+ exists3(import_node_path46.default.join(serviceDir, "src", "pages")),
8822
+ exists3(import_node_path46.default.join(serviceDir, "app")),
8823
+ exists3(import_node_path46.default.join(serviceDir, "pages"))
8561
8824
  ]);
8562
8825
  return (hasSrcApp || hasSrcPages) && !hasRootApp && !hasRootPages;
8563
8826
  }
8564
8827
  async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project) {
8565
8828
  const useTs = await isTypeScriptProject(serviceDir);
8566
8829
  const srcLayout = await detectsSrcLayout(serviceDir);
8567
- const baseDir = srcLayout ? import_node_path44.default.join(serviceDir, "src") : serviceDir;
8568
- const instrumentationFile = import_node_path44.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
8569
- const instrumentationNodeFile = import_node_path44.default.join(
8830
+ const baseDir = srcLayout ? import_node_path46.default.join(serviceDir, "src") : serviceDir;
8831
+ const instrumentationFile = import_node_path46.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
8832
+ const instrumentationNodeFile = import_node_path46.default.join(
8570
8833
  baseDir,
8571
8834
  useTs ? "instrumentation.node.ts" : "instrumentation.node.js"
8572
8835
  );
8573
- const envNeatFile = import_node_path44.default.join(baseDir, ".env.neat");
8836
+ const envNeatFile = import_node_path46.default.join(baseDir, ".env.neat");
8574
8837
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
8575
8838
  const dependencyEdits = [];
8576
8839
  for (const sdk of SDK_PACKAGES) {
@@ -8627,7 +8890,7 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
8627
8890
  const nextMajor = parseNextMajor(nextRange);
8628
8891
  if (nextMajor !== null && nextMajor < 15) {
8629
8892
  try {
8630
- const raw = await import_node_fs28.promises.readFile(nextConfigPath, "utf8");
8893
+ const raw = await import_node_fs29.promises.readFile(nextConfigPath, "utf8");
8631
8894
  if (!raw.includes("instrumentationHook")) {
8632
8895
  nextConfigEdit = {
8633
8896
  file: nextConfigPath,
@@ -8675,7 +8938,7 @@ function buildDependencyEdits(pkg, manifestPath) {
8675
8938
  return edits;
8676
8939
  }
8677
8940
  async function queueEnvNeat(serviceDir, pkg, project, generatedFiles) {
8678
- const envNeatFile = import_node_path44.default.join(serviceDir, ".env.neat");
8941
+ const envNeatFile = import_node_path46.default.join(serviceDir, ".env.neat");
8679
8942
  if (!await exists3(envNeatFile)) {
8680
8943
  generatedFiles.push({
8681
8944
  file: envNeatFile,
@@ -8710,7 +8973,7 @@ function fileImportsOtelHook(raw, specifiers) {
8710
8973
  }
8711
8974
  async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
8712
8975
  const useTs = await isTypeScriptProject(serviceDir);
8713
- const otelServerFile = import_node_path44.default.join(
8976
+ const otelServerFile = import_node_path46.default.join(
8714
8977
  serviceDir,
8715
8978
  useTs ? "app/otel.server.ts" : "app/otel.server.js"
8716
8979
  );
@@ -8731,7 +8994,7 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
8731
8994
  await queueEnvNeat(serviceDir, pkg, project, generatedFiles);
8732
8995
  const entrypointEdits = [];
8733
8996
  try {
8734
- const raw = await import_node_fs28.promises.readFile(entryFile, "utf8");
8997
+ const raw = await import_node_fs29.promises.readFile(entryFile, "utf8");
8735
8998
  if (!fileImportsOtelHook(raw, ["./otel.server"])) {
8736
8999
  const lines = raw.split(/\r?\n/);
8737
9000
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
@@ -8767,11 +9030,11 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
8767
9030
  }
8768
9031
  async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project) {
8769
9032
  const useTs = await isTypeScriptProject(serviceDir);
8770
- const otelInitFile = import_node_path44.default.join(
9033
+ const otelInitFile = import_node_path46.default.join(
8771
9034
  serviceDir,
8772
9035
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
8773
9036
  );
8774
- const resolvedHooksFile = hooksFile ?? import_node_path44.default.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
9037
+ const resolvedHooksFile = hooksFile ?? import_node_path46.default.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
8775
9038
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
8776
9039
  const generatedFiles = [];
8777
9040
  const entrypointEdits = [];
@@ -8796,7 +9059,7 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
8796
9059
  });
8797
9060
  } else {
8798
9061
  try {
8799
- const raw = await import_node_fs28.promises.readFile(hooksFile, "utf8");
9062
+ const raw = await import_node_fs29.promises.readFile(hooksFile, "utf8");
8800
9063
  if (!fileImportsOtelHook(raw, ["./otel-init"])) {
8801
9064
  const lines = raw.split(/\r?\n/);
8802
9065
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
@@ -8833,11 +9096,11 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
8833
9096
  }
8834
9097
  async function planNuxt(serviceDir, pkg, manifestPath, project) {
8835
9098
  const useTs = await isTypeScriptProject(serviceDir);
8836
- const otelPluginFile = import_node_path44.default.join(
9099
+ const otelPluginFile = import_node_path46.default.join(
8837
9100
  serviceDir,
8838
9101
  useTs ? "server/plugins/otel.ts" : "server/plugins/otel.js"
8839
9102
  );
8840
- const otelInitFile = import_node_path44.default.join(
9103
+ const otelInitFile = import_node_path46.default.join(
8841
9104
  serviceDir,
8842
9105
  useTs ? "server/plugins/otel-init.ts" : "server/plugins/otel-init.js"
8843
9106
  );
@@ -8888,19 +9151,19 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
8888
9151
  var ASTRO_MIDDLEWARE_CANDIDATES = ["src/middleware.ts", "src/middleware.js"];
8889
9152
  async function findAstroMiddleware(serviceDir) {
8890
9153
  for (const rel of ASTRO_MIDDLEWARE_CANDIDATES) {
8891
- const candidate = import_node_path44.default.join(serviceDir, rel);
9154
+ const candidate = import_node_path46.default.join(serviceDir, rel);
8892
9155
  if (await exists3(candidate)) return candidate;
8893
9156
  }
8894
9157
  return null;
8895
9158
  }
8896
9159
  async function planAstro(serviceDir, pkg, manifestPath, project) {
8897
9160
  const useTs = await isTypeScriptProject(serviceDir);
8898
- const otelInitFile = import_node_path44.default.join(
9161
+ const otelInitFile = import_node_path46.default.join(
8899
9162
  serviceDir,
8900
9163
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
8901
9164
  );
8902
9165
  const existingMiddleware = await findAstroMiddleware(serviceDir);
8903
- const middlewareFile = existingMiddleware ?? import_node_path44.default.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
9166
+ const middlewareFile = existingMiddleware ?? import_node_path46.default.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
8904
9167
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
8905
9168
  const generatedFiles = [];
8906
9169
  const entrypointEdits = [];
@@ -8925,7 +9188,7 @@ async function planAstro(serviceDir, pkg, manifestPath, project) {
8925
9188
  });
8926
9189
  } else {
8927
9190
  try {
8928
- const raw = await import_node_fs28.promises.readFile(existingMiddleware, "utf8");
9191
+ const raw = await import_node_fs29.promises.readFile(existingMiddleware, "utf8");
8929
9192
  if (!fileImportsOtelHook(raw, ["./otel-init"])) {
8930
9193
  const lines = raw.split(/\r?\n/);
8931
9194
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
@@ -8996,7 +9259,7 @@ async function findFrameworkDispatch(serviceDir, pkg, manifestPath, project) {
8996
9259
  }
8997
9260
  async function plan(serviceDir, opts) {
8998
9261
  const pkg = await readPackageJson2(serviceDir);
8999
- const manifestPath = import_node_path44.default.join(serviceDir, "package.json");
9262
+ const manifestPath = import_node_path46.default.join(serviceDir, "package.json");
9000
9263
  const project = opts?.project;
9001
9264
  const empty = {
9002
9265
  language: "javascript",
@@ -9031,8 +9294,8 @@ async function plan(serviceDir, opts) {
9031
9294
  return { ...empty, libOnly: true };
9032
9295
  }
9033
9296
  const flavor = dispatchEntry(entryFile, pkg);
9034
- const otelInitFile = import_node_path44.default.join(import_node_path44.default.dirname(entryFile), otelInitFilename(flavor));
9035
- const envNeatFile = import_node_path44.default.join(serviceDir, ".env.neat");
9297
+ const otelInitFile = import_node_path46.default.join(import_node_path46.default.dirname(entryFile), otelInitFilename(flavor));
9298
+ const envNeatFile = import_node_path46.default.join(serviceDir, ".env.neat");
9036
9299
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
9037
9300
  const dependencyEdits = [];
9038
9301
  for (const sdk of SDK_PACKAGES) {
@@ -9056,7 +9319,7 @@ async function plan(serviceDir, opts) {
9056
9319
  }
9057
9320
  const entrypointEdits = [];
9058
9321
  try {
9059
- const raw = await import_node_fs28.promises.readFile(entryFile, "utf8");
9322
+ const raw = await import_node_fs29.promises.readFile(entryFile, "utf8");
9060
9323
  const lines = raw.split(/\r?\n/);
9061
9324
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
9062
9325
  if (!lineIsOtelInjection(firstReal)) {
@@ -9101,13 +9364,13 @@ async function plan(serviceDir, opts) {
9101
9364
  };
9102
9365
  }
9103
9366
  function isAllowedWritePath(serviceDir, target) {
9104
- const rel = import_node_path44.default.relative(serviceDir, target);
9367
+ const rel = import_node_path46.default.relative(serviceDir, target);
9105
9368
  if (rel.startsWith("..")) return false;
9106
- const base = import_node_path44.default.basename(target);
9369
+ const base = import_node_path46.default.basename(target);
9107
9370
  if (base === "package.json") return true;
9108
9371
  if (base === ".env.neat") return true;
9109
9372
  if (/^otel-init\.(?:js|cjs|mjs|ts)$/.test(base)) return true;
9110
- const relPosix = rel.split(import_node_path44.default.sep).join("/");
9373
+ const relPosix = rel.split(import_node_path46.default.sep).join("/");
9111
9374
  if (/^instrumentation(?:\.node)?\.(?:js|cjs|mjs|ts)$/.test(base)) {
9112
9375
  if (relPosix === base) return true;
9113
9376
  if (relPosix === `src/${base}`) return true;
@@ -9124,10 +9387,10 @@ function isAllowedWritePath(serviceDir, target) {
9124
9387
  return false;
9125
9388
  }
9126
9389
  async function writeAtomic(file, contents) {
9127
- await import_node_fs28.promises.mkdir(import_node_path44.default.dirname(file), { recursive: true });
9390
+ await import_node_fs29.promises.mkdir(import_node_path46.default.dirname(file), { recursive: true });
9128
9391
  const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
9129
- await import_node_fs28.promises.writeFile(tmp, contents, "utf8");
9130
- await import_node_fs28.promises.rename(tmp, file);
9392
+ await import_node_fs29.promises.writeFile(tmp, contents, "utf8");
9393
+ await import_node_fs29.promises.rename(tmp, file);
9131
9394
  }
9132
9395
  async function apply(installPlan) {
9133
9396
  const { serviceDir } = installPlan;
@@ -9203,7 +9466,7 @@ async function apply(installPlan) {
9203
9466
  for (const target of allTargets) {
9204
9467
  if (await exists3(target)) {
9205
9468
  try {
9206
- originals.set(target, await import_node_fs28.promises.readFile(target, "utf8"));
9469
+ originals.set(target, await import_node_fs29.promises.readFile(target, "utf8"));
9207
9470
  } catch {
9208
9471
  }
9209
9472
  }
@@ -9286,14 +9549,14 @@ async function rollback(installPlan, originals, createdFiles) {
9286
9549
  const removed = [];
9287
9550
  for (const [file, raw] of originals.entries()) {
9288
9551
  try {
9289
- await import_node_fs28.promises.writeFile(file, raw, "utf8");
9552
+ await import_node_fs29.promises.writeFile(file, raw, "utf8");
9290
9553
  restored.push(file);
9291
9554
  } catch {
9292
9555
  }
9293
9556
  }
9294
9557
  for (const file of createdFiles) {
9295
9558
  try {
9296
- await import_node_fs28.promises.unlink(file);
9559
+ await import_node_fs29.promises.unlink(file);
9297
9560
  removed.push(file);
9298
9561
  } catch {
9299
9562
  }
@@ -9308,8 +9571,8 @@ async function rollback(installPlan, originals, createdFiles) {
9308
9571
  ...removed.map((f) => `removed: ${f}`),
9309
9572
  ""
9310
9573
  ];
9311
- const rollbackPath = import_node_path44.default.join(installPlan.serviceDir, "neat-rollback.patch");
9312
- await import_node_fs28.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
9574
+ const rollbackPath = import_node_path46.default.join(installPlan.serviceDir, "neat-rollback.patch");
9575
+ await import_node_fs29.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
9313
9576
  }
9314
9577
  function injectInstrumentationHook(raw) {
9315
9578
  if (raw.includes("instrumentationHook")) return raw;
@@ -9338,8 +9601,8 @@ var javascriptInstaller = {
9338
9601
 
9339
9602
  // src/installers/python.ts
9340
9603
  init_cjs_shims();
9341
- var import_node_fs29 = require("fs");
9342
- var import_node_path45 = __toESM(require("path"), 1);
9604
+ var import_node_fs30 = require("fs");
9605
+ var import_node_path47 = __toESM(require("path"), 1);
9343
9606
  var SDK_PACKAGES2 = [
9344
9607
  { name: "opentelemetry-distro", version: ">=0.49b0" },
9345
9608
  { name: "opentelemetry-exporter-otlp", version: ">=1.28.0" }
@@ -9351,7 +9614,7 @@ var OTEL_ENV2 = {
9351
9614
  };
9352
9615
  async function exists4(p) {
9353
9616
  try {
9354
- await import_node_fs29.promises.stat(p);
9617
+ await import_node_fs30.promises.stat(p);
9355
9618
  return true;
9356
9619
  } catch {
9357
9620
  return false;
@@ -9360,7 +9623,7 @@ async function exists4(p) {
9360
9623
  async function detect2(serviceDir) {
9361
9624
  const markers = ["requirements.txt", "pyproject.toml", "setup.py"];
9362
9625
  for (const m of markers) {
9363
- if (await exists4(import_node_path45.default.join(serviceDir, m))) return true;
9626
+ if (await exists4(import_node_path47.default.join(serviceDir, m))) return true;
9364
9627
  }
9365
9628
  return false;
9366
9629
  }
@@ -9370,9 +9633,9 @@ function reqPackageName(line) {
9370
9633
  return head.replace(/[<>=!~].*$/, "").toLowerCase();
9371
9634
  }
9372
9635
  async function planRequirementsTxtEdits(serviceDir) {
9373
- const file = import_node_path45.default.join(serviceDir, "requirements.txt");
9636
+ const file = import_node_path47.default.join(serviceDir, "requirements.txt");
9374
9637
  if (!await exists4(file)) return null;
9375
- const raw = await import_node_fs29.promises.readFile(file, "utf8");
9638
+ const raw = await import_node_fs30.promises.readFile(file, "utf8");
9376
9639
  const presentNames = new Set(
9377
9640
  raw.split(/\r?\n/).map(reqPackageName).filter((n) => n.length > 0)
9378
9641
  );
@@ -9380,9 +9643,9 @@ async function planRequirementsTxtEdits(serviceDir) {
9380
9643
  return { manifest: file, missing: [...missing] };
9381
9644
  }
9382
9645
  async function planProcfileEdits(serviceDir) {
9383
- const procfile = import_node_path45.default.join(serviceDir, "Procfile");
9646
+ const procfile = import_node_path47.default.join(serviceDir, "Procfile");
9384
9647
  if (!await exists4(procfile)) return [];
9385
- const raw = await import_node_fs29.promises.readFile(procfile, "utf8");
9648
+ const raw = await import_node_fs30.promises.readFile(procfile, "utf8");
9386
9649
  const edits = [];
9387
9650
  for (const line of raw.split(/\r?\n/)) {
9388
9651
  if (line.length === 0) continue;
@@ -9434,8 +9697,8 @@ async function applyRequirementsTxt(manifest, edits, original) {
9434
9697
  const next = `${original}${trailing}${newlines.join("\n")}
9435
9698
  `;
9436
9699
  const tmp = `${manifest}.${process.pid}.${Date.now()}.tmp`;
9437
- await import_node_fs29.promises.writeFile(tmp, next, "utf8");
9438
- await import_node_fs29.promises.rename(tmp, manifest);
9700
+ await import_node_fs30.promises.writeFile(tmp, next, "utf8");
9701
+ await import_node_fs30.promises.rename(tmp, manifest);
9439
9702
  }
9440
9703
  async function applyProcfile(procfile, edits, original) {
9441
9704
  let next = original;
@@ -9444,8 +9707,8 @@ async function applyProcfile(procfile, edits, original) {
9444
9707
  next = next.replace(e.before, e.after);
9445
9708
  }
9446
9709
  const tmp = `${procfile}.${process.pid}.${Date.now()}.tmp`;
9447
- await import_node_fs29.promises.writeFile(tmp, next, "utf8");
9448
- await import_node_fs29.promises.rename(tmp, procfile);
9710
+ await import_node_fs30.promises.writeFile(tmp, next, "utf8");
9711
+ await import_node_fs30.promises.rename(tmp, procfile);
9449
9712
  }
9450
9713
  async function apply2(installPlan) {
9451
9714
  const { serviceDir } = installPlan;
@@ -9458,7 +9721,7 @@ async function apply2(installPlan) {
9458
9721
  const originals = /* @__PURE__ */ new Map();
9459
9722
  for (const file of touched) {
9460
9723
  try {
9461
- originals.set(file, await import_node_fs29.promises.readFile(file, "utf8"));
9724
+ originals.set(file, await import_node_fs30.promises.readFile(file, "utf8"));
9462
9725
  } catch {
9463
9726
  }
9464
9727
  }
@@ -9469,7 +9732,7 @@ async function apply2(installPlan) {
9469
9732
  if (raw === void 0) {
9470
9733
  throw new Error(`python installer: cannot read ${file} during apply`);
9471
9734
  }
9472
- const base = import_node_path45.default.basename(file);
9735
+ const base = import_node_path47.default.basename(file);
9473
9736
  if (base === "requirements.txt") {
9474
9737
  const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
9475
9738
  if (edits.length > 0) {
@@ -9494,7 +9757,7 @@ async function rollback2(installPlan, originals) {
9494
9757
  const restored = [];
9495
9758
  for (const [file, raw] of originals.entries()) {
9496
9759
  try {
9497
- await import_node_fs29.promises.writeFile(file, raw, "utf8");
9760
+ await import_node_fs30.promises.writeFile(file, raw, "utf8");
9498
9761
  restored.push(file);
9499
9762
  } catch {
9500
9763
  }
@@ -9508,8 +9771,8 @@ async function rollback2(installPlan, originals) {
9508
9771
  ...restored.map((f) => `restored: ${f}`),
9509
9772
  ""
9510
9773
  ];
9511
- const rollbackPath = import_node_path45.default.join(installPlan.serviceDir, "neat-rollback.patch");
9512
- await import_node_fs29.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
9774
+ const rollbackPath = import_node_path47.default.join(installPlan.serviceDir, "neat-rollback.patch");
9775
+ await import_node_fs30.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
9513
9776
  }
9514
9777
  var pythonInstaller = {
9515
9778
  name: "python",
@@ -9632,10 +9895,10 @@ function renderPatch(sections) {
9632
9895
 
9633
9896
  // src/orchestrator.ts
9634
9897
  init_cjs_shims();
9635
- var import_node_fs30 = require("fs");
9898
+ var import_node_fs31 = require("fs");
9636
9899
  var import_node_http = __toESM(require("http"), 1);
9637
9900
  var import_node_net = __toESM(require("net"), 1);
9638
- var import_node_path46 = __toESM(require("path"), 1);
9901
+ var import_node_path48 = __toESM(require("path"), 1);
9639
9902
  var import_node_child_process3 = require("child_process");
9640
9903
  var import_node_readline = __toESM(require("readline"), 1);
9641
9904
  async function extractAndPersist(opts) {
@@ -9644,7 +9907,7 @@ async function extractAndPersist(opts) {
9644
9907
  const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
9645
9908
  resetGraph(graphKey);
9646
9909
  const graph = getGraph(graphKey);
9647
- const projectPaths = pathsForProject(graphKey, import_node_path46.default.join(opts.scanPath, "neat-out"));
9910
+ const projectPaths = pathsForProject(graphKey, import_node_path48.default.join(opts.scanPath, "neat-out"));
9648
9911
  const extraction = await extractFromDirectory(graph, opts.scanPath, {
9649
9912
  errorsPath: projectPaths.errorsPath
9650
9913
  });
@@ -9698,7 +9961,7 @@ async function applyInstallersOver(services, project, options = {}) {
9698
9961
  console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
9699
9962
  } else if (outcome.outcome === "react-native") {
9700
9963
  reactNative++;
9701
- const svcName = import_node_path46.default.basename(svc.dir);
9964
+ const svcName = import_node_path48.default.basename(svc.dir);
9702
9965
  console.log(
9703
9966
  `neat: ${svc.dir} detected as React Native / Expo
9704
9967
  The installer doesn't cover this runtime deterministically.
@@ -9709,7 +9972,7 @@ async function applyInstallersOver(services, project, options = {}) {
9709
9972
  );
9710
9973
  } else if (outcome.outcome === "bun") {
9711
9974
  bun++;
9712
- const svcName = import_node_path46.default.basename(svc.dir);
9975
+ const svcName = import_node_path48.default.basename(svc.dir);
9713
9976
  console.log(
9714
9977
  `neat: ${svc.dir} detected as Bun
9715
9978
  The installer doesn't cover this runtime deterministically.
@@ -9720,7 +9983,7 @@ async function applyInstallersOver(services, project, options = {}) {
9720
9983
  );
9721
9984
  } else if (outcome.outcome === "deno") {
9722
9985
  deno++;
9723
- const svcName = import_node_path46.default.basename(svc.dir);
9986
+ const svcName = import_node_path48.default.basename(svc.dir);
9724
9987
  console.log(
9725
9988
  `neat: ${svc.dir} detected as Deno
9726
9989
  The installer doesn't cover this runtime deterministically.
@@ -9731,7 +9994,7 @@ async function applyInstallersOver(services, project, options = {}) {
9731
9994
  );
9732
9995
  } else if (outcome.outcome === "cloudflare-workers") {
9733
9996
  cloudflareWorkers++;
9734
- const svcName = import_node_path46.default.basename(svc.dir);
9997
+ const svcName = import_node_path48.default.basename(svc.dir);
9735
9998
  console.log(
9736
9999
  `neat: ${svc.dir} detected as Cloudflare Workers
9737
10000
  The installer doesn't cover this runtime deterministically.
@@ -9742,7 +10005,7 @@ async function applyInstallersOver(services, project, options = {}) {
9742
10005
  );
9743
10006
  } else if (outcome.outcome === "electron") {
9744
10007
  electron++;
9745
- const svcName = import_node_path46.default.basename(svc.dir);
10008
+ const svcName = import_node_path48.default.basename(svc.dir);
9746
10009
  console.log(
9747
10010
  `neat: ${svc.dir} detected as Electron
9748
10011
  The installer doesn't cover this runtime deterministically.
@@ -9916,10 +10179,10 @@ function formatPortCollisionMessage(port) {
9916
10179
  ];
9917
10180
  }
9918
10181
  function spawnDaemonDetached() {
9919
- const here = import_node_path46.default.dirname(new URL(importMetaUrl).pathname);
10182
+ const here = import_node_path48.default.dirname(new URL(importMetaUrl).pathname);
9920
10183
  const candidates = [
9921
- import_node_path46.default.join(here, "neatd.cjs"),
9922
- import_node_path46.default.join(here, "neatd.js")
10184
+ import_node_path48.default.join(here, "neatd.cjs"),
10185
+ import_node_path48.default.join(here, "neatd.js")
9923
10186
  ];
9924
10187
  let entry2 = null;
9925
10188
  const fsSync = require("fs");
@@ -9977,12 +10240,13 @@ async function runOrchestrator(opts) {
9977
10240
  browser: "skipped"
9978
10241
  }
9979
10242
  };
9980
- const stat = await import_node_fs30.promises.stat(opts.scanPath).catch(() => null);
10243
+ const stat = await import_node_fs31.promises.stat(opts.scanPath).catch(() => null);
9981
10244
  if (!stat || !stat.isDirectory()) {
9982
10245
  console.error(`neat: ${opts.scanPath} is not a directory`);
9983
10246
  result.exitCode = 2;
9984
10247
  return result;
9985
10248
  }
10249
+ printBanner();
9986
10250
  console.log(`neat: ${opts.scanPath}`);
9987
10251
  console.log("");
9988
10252
  const persisted = await extractAndPersist({
@@ -9993,6 +10257,13 @@ async function runOrchestrator(opts) {
9993
10257
  const { graph, services, languages } = persisted;
9994
10258
  result.steps.discovery = { services: services.length, languages };
9995
10259
  console.log(`discovered ${services.length} service(s) across ${languages.length} language(s)`);
10260
+ if (services.length === 0) {
10261
+ console.error(
10262
+ `neat: no services found in ${opts.scanPath} \u2014 run from inside your project root, or \`npx neat.is <path>\``
10263
+ );
10264
+ result.exitCode = 2;
10265
+ return result;
10266
+ }
9996
10267
  let runApply = !opts.noInstrument;
9997
10268
  if (runApply && !opts.yes && process.stdout.isTTY && process.stdin.isTTY) {
9998
10269
  runApply = await promptYesNo("instrument your services and open the dashboard?");
@@ -10116,15 +10387,21 @@ function printSummary(result, graph, dashboardUrl) {
10116
10387
  for (const [t, c] of [...byEdge.entries()].sort()) console.log(` ${t}: ${c}`);
10117
10388
  console.log("");
10118
10389
  console.log(`dashboard: ${dashboardUrl}`);
10390
+ const token = process.env.NEAT_AUTH_TOKEN;
10391
+ if (typeof token === "string" && token.length > 0) {
10392
+ console.log(`auth token: ${token}`);
10393
+ } else {
10394
+ console.log("running locally \u2014 open the dashboard, no token needed");
10395
+ }
10119
10396
  }
10120
10397
 
10121
10398
  // src/cli-verbs.ts
10122
10399
  init_cjs_shims();
10123
- var import_node_path47 = __toESM(require("path"), 1);
10400
+ var import_node_path49 = __toESM(require("path"), 1);
10124
10401
 
10125
10402
  // src/cli-client.ts
10126
10403
  init_cjs_shims();
10127
- var import_types26 = require("@neat.is/types");
10404
+ var import_types27 = require("@neat.is/types");
10128
10405
  var HttpError = class extends Error {
10129
10406
  constructor(status2, message, responseBody = "") {
10130
10407
  super(message);
@@ -10149,10 +10426,10 @@ function createHttpClient(baseUrl, bearerToken) {
10149
10426
  const root = baseUrl.replace(/\/$/, "");
10150
10427
  const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
10151
10428
  return {
10152
- async get(path49) {
10429
+ async get(path51) {
10153
10430
  let res;
10154
10431
  try {
10155
- res = await fetch(`${root}${path49}`, {
10432
+ res = await fetch(`${root}${path51}`, {
10156
10433
  headers: { ...authHeader }
10157
10434
  });
10158
10435
  } catch (err) {
@@ -10164,16 +10441,16 @@ function createHttpClient(baseUrl, bearerToken) {
10164
10441
  const body = await res.text().catch(() => "");
10165
10442
  throw new HttpError(
10166
10443
  res.status,
10167
- `${res.status} ${res.statusText} on GET ${path49}: ${body}`,
10444
+ `${res.status} ${res.statusText} on GET ${path51}: ${body}`,
10168
10445
  body
10169
10446
  );
10170
10447
  }
10171
10448
  return await res.json();
10172
10449
  },
10173
- async post(path49, body) {
10450
+ async post(path51, body) {
10174
10451
  let res;
10175
10452
  try {
10176
- res = await fetch(`${root}${path49}`, {
10453
+ res = await fetch(`${root}${path51}`, {
10177
10454
  method: "POST",
10178
10455
  headers: { "content-type": "application/json", ...authHeader },
10179
10456
  body: JSON.stringify(body)
@@ -10187,7 +10464,7 @@ function createHttpClient(baseUrl, bearerToken) {
10187
10464
  const text = await res.text().catch(() => "");
10188
10465
  throw new HttpError(
10189
10466
  res.status,
10190
- `${res.status} ${res.statusText} on POST ${path49}: ${text}`,
10467
+ `${res.status} ${res.statusText} on POST ${path51}: ${text}`,
10191
10468
  text
10192
10469
  );
10193
10470
  }
@@ -10201,12 +10478,12 @@ function projectPath(project, suffix) {
10201
10478
  }
10202
10479
  async function runRootCause(client, input) {
10203
10480
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
10204
- const path49 = projectPath(
10481
+ const path51 = projectPath(
10205
10482
  input.project,
10206
10483
  `/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
10207
10484
  );
10208
10485
  try {
10209
- const result = await client.get(path49);
10486
+ const result = await client.get(path51);
10210
10487
  const arrowPath = result.traversalPath.join(" \u2190 ");
10211
10488
  const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
10212
10489
  const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
@@ -10232,12 +10509,12 @@ async function runRootCause(client, input) {
10232
10509
  }
10233
10510
  async function runBlastRadius(client, input) {
10234
10511
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
10235
- const path49 = projectPath(
10512
+ const path51 = projectPath(
10236
10513
  input.project,
10237
10514
  `/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
10238
10515
  );
10239
10516
  try {
10240
- const result = await client.get(path49);
10517
+ const result = await client.get(path51);
10241
10518
  if (result.totalAffected === 0) {
10242
10519
  return {
10243
10520
  summary: `${result.origin} has no downstream dependencies. Nothing else would break if it failed.`
@@ -10266,17 +10543,17 @@ async function runBlastRadius(client, input) {
10266
10543
  }
10267
10544
  }
10268
10545
  function formatBlastEntry(n) {
10269
- const tag = n.edgeProvenance === import_types26.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
10546
+ const tag = n.edgeProvenance === import_types27.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
10270
10547
  return ` \u2022 ${n.nodeId} (distance ${n.distance}, ${n.edgeProvenance})${tag}`;
10271
10548
  }
10272
10549
  async function runDependencies(client, input) {
10273
10550
  const depth = input.depth ?? 3;
10274
- const path49 = projectPath(
10551
+ const path51 = projectPath(
10275
10552
  input.project,
10276
10553
  `/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
10277
10554
  );
10278
10555
  try {
10279
- const result = await client.get(path49);
10556
+ const result = await client.get(path51);
10280
10557
  if (result.total === 0) {
10281
10558
  return {
10282
10559
  summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
@@ -10312,9 +10589,9 @@ async function runObservedDependencies(client, input) {
10312
10589
  const edges = await client.get(
10313
10590
  projectPath(input.project, `/graph/edges/${encodeURIComponent(input.nodeId)}`)
10314
10591
  );
10315
- const observed = edges.outbound.filter((e) => e.provenance === import_types26.Provenance.OBSERVED);
10592
+ const observed = edges.outbound.filter((e) => e.provenance === import_types27.Provenance.OBSERVED);
10316
10593
  if (observed.length === 0) {
10317
- const hasExtracted = edges.outbound.some((e) => e.provenance === import_types26.Provenance.EXTRACTED);
10594
+ const hasExtracted = edges.outbound.some((e) => e.provenance === import_types27.Provenance.EXTRACTED);
10318
10595
  const note = hasExtracted ? " Static (EXTRACTED) dependencies exist but no runtime traffic has been seen \u2014 is OTel running?" : "";
10319
10596
  return { summary: `No OBSERVED dependencies for ${input.nodeId}.${note}` };
10320
10597
  }
@@ -10322,7 +10599,7 @@ async function runObservedDependencies(client, input) {
10322
10599
  return {
10323
10600
  summary: `${input.nodeId} has ${observed.length} runtime dependenc${observed.length === 1 ? "y" : "ies"} confirmed by OTel.`,
10324
10601
  block: blockLines.join("\n"),
10325
- provenance: import_types26.Provenance.OBSERVED
10602
+ provenance: import_types27.Provenance.OBSERVED
10326
10603
  };
10327
10604
  } catch (err) {
10328
10605
  if (err instanceof HttpError && err.status === 404) {
@@ -10357,9 +10634,9 @@ function formatDuration(ms) {
10357
10634
  return `${Math.round(h / 24)}d`;
10358
10635
  }
10359
10636
  async function runIncidents(client, input) {
10360
- const path49 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
10637
+ const path51 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
10361
10638
  try {
10362
- const body = await client.get(path49);
10639
+ const body = await client.get(path51);
10363
10640
  const events = body.events;
10364
10641
  if (events.length === 0) {
10365
10642
  return {
@@ -10376,7 +10653,7 @@ async function runIncidents(client, input) {
10376
10653
  return {
10377
10654
  summary: `${target} has ${body.total} recorded incident${body.total === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
10378
10655
  block: blockLines.join("\n"),
10379
- provenance: import_types26.Provenance.OBSERVED
10656
+ provenance: import_types27.Provenance.OBSERVED
10380
10657
  };
10381
10658
  } catch (err) {
10382
10659
  if (err instanceof HttpError && err.status === 404) {
@@ -10485,7 +10762,7 @@ async function runStaleEdges(client, input) {
10485
10762
  return {
10486
10763
  summary: `${events.length} stale-edge transition${events.length === 1 ? "" : "s"} recorded${input.edgeType ? ` for ${input.edgeType}` : ""}.`,
10487
10764
  block: blockLines.join("\n"),
10488
- provenance: import_types26.Provenance.STALE
10765
+ provenance: import_types27.Provenance.STALE
10489
10766
  };
10490
10767
  }
10491
10768
  async function runPolicies(client, input) {
@@ -10649,7 +10926,7 @@ async function resolveProjectEntry(opts) {
10649
10926
  const cwd = opts.cwd ?? process.cwd();
10650
10927
  const resolvedCwd = await normalizeProjectPath(cwd);
10651
10928
  for (const entry2 of entries) {
10652
- if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path47.default.sep}`)) {
10929
+ if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path49.default.sep}`)) {
10653
10930
  return entry2;
10654
10931
  }
10655
10932
  }
@@ -10799,9 +11076,25 @@ async function runSync(opts) {
10799
11076
  }
10800
11077
 
10801
11078
  // src/cli.ts
10802
- var import_types27 = require("@neat.is/types");
11079
+ var import_types28 = require("@neat.is/types");
11080
+ function isNpxInvocation() {
11081
+ if (process.env.npm_command === "exec") return true;
11082
+ const execpath = process.env.npm_execpath ?? "";
11083
+ if (execpath.includes("npx")) return true;
11084
+ const entry2 = process.argv[1] ?? "";
11085
+ if (entry2.includes("/_npx/") || entry2.includes("\\_npx\\")) return true;
11086
+ return false;
11087
+ }
11088
+ function commandPrefix() {
11089
+ return isNpxInvocation() ? "npx neat.is" : "neat";
11090
+ }
10803
11091
  function usage() {
10804
- console.log("usage: neat <command> [args] [--project <name>]");
11092
+ const neat = commandPrefix();
11093
+ console.log("Installed via npx? Prefix commands with `npx neat.is`, or install once: `npm i -g neat.is`.");
11094
+ console.log("");
11095
+ console.log(`usage: ${neat} <command> [args] [--project <name>]`);
11096
+ console.log("");
11097
+ console.log(`Run \`${neat}\` with no command from inside your project to go zero-to-graph in one step.`);
10805
11098
  console.log("");
10806
11099
  console.log("lifecycle commands:");
10807
11100
  console.log(" init <path> One-time install: discover, extract, register, plan SDK install.");
@@ -10820,6 +11113,8 @@ function usage() {
10820
11113
  console.log(" uninstall <name>");
10821
11114
  console.log(" Remove a project from the registry. Does not touch");
10822
11115
  console.log(" neat-out/, policy.json, or any user file.");
11116
+ console.log(" prune Drop registry entries whose path is gone from disk.");
11117
+ console.log(" Flags: --json emit the removed list as JSON");
10823
11118
  console.log(" version Print the installed @neat.is/core version and exit.");
10824
11119
  console.log(" Aliases: --version, -v.");
10825
11120
  console.log(" skill Install or print the Claude Code MCP drop-in.");
@@ -10841,30 +11136,30 @@ function usage() {
10841
11136
  console.log("");
10842
11137
  console.log("query commands (mirror the MCP tools, ADR-050):");
10843
11138
  console.log(" root-cause <node-id> Walk inbound edges to find what broke first.");
10844
- console.log(" example: neat root-cause service:<name>");
11139
+ console.log(` example: ${neat} root-cause service:<name>`);
10845
11140
  console.log(" blast-radius <node-id> BFS outbound \u2014 what would break if this dies.");
10846
- console.log(" example: neat blast-radius database:<host>");
11141
+ console.log(` example: ${neat} blast-radius database:<host>`);
10847
11142
  console.log(" dependencies <node-id> Transitive outbound dependencies.");
10848
11143
  console.log(" Flags: --depth N (default 3, max 10)");
10849
- console.log(" example: neat dependencies service:<name> --depth 2");
11144
+ console.log(` example: ${neat} dependencies service:<name> --depth 2`);
10850
11145
  console.log(" observed-dependencies <node-id> OBSERVED-only outbound edges (runtime traffic).");
10851
- console.log(" example: neat observed-dependencies service:<name>");
11146
+ console.log(` example: ${neat} observed-dependencies service:<name>`);
10852
11147
  console.log(" incidents [<node-id>] Recent error events; per-node when an id is given.");
10853
11148
  console.log(" Flags: --limit N (default 20)");
10854
- console.log(" example: neat incidents service:<name> --limit 5");
11149
+ console.log(` example: ${neat} incidents service:<name> --limit 5`);
10855
11150
  console.log(" search <query> Semantic (or substring) match on node names/ids.");
10856
- console.log(' example: neat search "checkout"');
11151
+ console.log(` example: ${neat} search "checkout"`);
10857
11152
  console.log(" diff --against <snapshot> Compare the live graph to a saved snapshot.");
10858
- console.log(" example: neat diff --against ./snapshots/baseline.json");
11153
+ console.log(` example: ${neat} diff --against ./snapshots/baseline.json`);
10859
11154
  console.log(" stale-edges Recent OBSERVED \u2192 STALE transitions.");
10860
11155
  console.log(" Flags: --limit N, --edge-type CALLS|CONNECTS_TO|...");
10861
- console.log(" example: neat stale-edges --edge-type CALLS");
11156
+ console.log(` example: ${neat} stale-edges --edge-type CALLS`);
10862
11157
  console.log(" policies Current policy violations.");
10863
11158
  console.log(" Flags: --node <id>, --hypothetical-action <json>");
10864
- console.log(" example: neat policies --node service:<name> --json");
11159
+ console.log(` example: ${neat} policies --node service:<name> --json`);
10865
11160
  console.log(" divergences Where code (EXTRACTED) and production (OBSERVED) disagree.");
10866
11161
  console.log(" Flags: --type <list>, --min-confidence <0..1>, --node <id>");
10867
- console.log(" example: neat divergences --min-confidence 0.7");
11162
+ console.log(` example: ${neat} divergences --min-confidence 0.7`);
10868
11163
  console.log("");
10869
11164
  console.log("flags:");
10870
11165
  console.log(' --project <name> Name the project this command targets. Default: "default".');
@@ -10879,6 +11174,7 @@ function usage() {
10879
11174
  console.log("");
10880
11175
  console.log("environment:");
10881
11176
  console.log(" NEAT_API_URL base URL for the core REST API (default http://localhost:8080)");
11177
+ console.log(" alias: NEAT_CORE_URL (the name the MCP server reads)");
10882
11178
  console.log(" NEAT_PROJECT project name when --project isn't passed");
10883
11179
  }
10884
11180
  var STRING_FLAGS = [
@@ -11008,40 +11304,10 @@ function assignFlag(out, field, value) {
11008
11304
  ;
11009
11305
  out[field] = value;
11010
11306
  }
11011
- function readPackageVersion() {
11012
- const here = typeof __dirname !== "undefined" ? __dirname : import_node_path48.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
11013
- const candidates = [
11014
- import_node_path48.default.resolve(here, "../package.json"),
11015
- import_node_path48.default.resolve(here, "../../package.json")
11016
- ];
11017
- for (const candidate of candidates) {
11018
- try {
11019
- const raw = (0, import_node_fs31.readFileSync)(candidate, "utf8");
11020
- const parsed = JSON.parse(raw);
11021
- if (parsed.name === "@neat.is/core" && typeof parsed.version === "string") {
11022
- return parsed.version;
11023
- }
11024
- } catch {
11025
- }
11026
- }
11027
- return "unknown";
11028
- }
11029
11307
  function printVersion() {
11030
11308
  process.stdout.write(`${readPackageVersion()}
11031
11309
  `);
11032
11310
  }
11033
- function printBanner() {
11034
- console.log("\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557");
11035
- console.log("\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D");
11036
- console.log("\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 ");
11037
- console.log("\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 ");
11038
- console.log("\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 ");
11039
- console.log("\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D ");
11040
- console.log("");
11041
- console.log(" Network Expressive Architecting Tool");
11042
- console.log(` neat.is \xB7 v${readPackageVersion()} \xB7 Apache 2.0`);
11043
- console.log("");
11044
- }
11045
11311
  function printDiscoveryReport(opts, services) {
11046
11312
  const languages = [...new Set(services.map((s) => s.node.language))].sort();
11047
11313
  const mode = opts.dryRun ? "dry-run" : opts.apply ? "apply" : "patch-only";
@@ -11080,7 +11346,7 @@ async function buildPatchSections(services, project) {
11080
11346
  }
11081
11347
  async function runInit(opts) {
11082
11348
  const written = [];
11083
- const stat = await import_node_fs31.promises.stat(opts.scanPath).catch(() => null);
11349
+ const stat = await import_node_fs32.promises.stat(opts.scanPath).catch(() => null);
11084
11350
  if (!stat || !stat.isDirectory()) {
11085
11351
  console.error(`neat init: ${opts.scanPath} is not a directory`);
11086
11352
  return { exitCode: 2, writtenFiles: written };
@@ -11089,13 +11355,13 @@ async function runInit(opts) {
11089
11355
  printDiscoveryReport(opts, services);
11090
11356
  const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
11091
11357
  const patch = renderPatch(sections);
11092
- const patchPath = import_node_path48.default.join(opts.scanPath, "neat.patch");
11358
+ const patchPath = import_node_path50.default.join(opts.scanPath, "neat.patch");
11093
11359
  if (opts.dryRun) {
11094
- await import_node_fs31.promises.writeFile(patchPath, patch, "utf8");
11360
+ await import_node_fs32.promises.writeFile(patchPath, patch, "utf8");
11095
11361
  written.push(patchPath);
11096
11362
  console.log(`dry-run: patch written to ${patchPath}`);
11097
- const gitignorePath = import_node_path48.default.join(opts.scanPath, ".gitignore");
11098
- const gitignoreExists = await import_node_fs31.promises.stat(gitignorePath).then(() => true).catch(() => false);
11363
+ const gitignorePath = import_node_path50.default.join(opts.scanPath, ".gitignore");
11364
+ const gitignoreExists = await import_node_fs32.promises.stat(gitignorePath).then(() => true).catch(() => false);
11099
11365
  const verb = gitignoreExists ? "append" : "create";
11100
11366
  console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
11101
11367
  console.log("rerun without --dry-run to register and snapshot.");
@@ -11106,9 +11372,9 @@ async function runInit(opts) {
11106
11372
  const graph = getGraph(graphKey);
11107
11373
  const projectPaths = pathsForProject(
11108
11374
  graphKey,
11109
- import_node_path48.default.join(opts.scanPath, "neat-out")
11375
+ import_node_path50.default.join(opts.scanPath, "neat-out")
11110
11376
  );
11111
- const errorsPath = import_node_path48.default.join(import_node_path48.default.dirname(opts.outPath), import_node_path48.default.basename(projectPaths.errorsPath));
11377
+ const errorsPath = import_node_path50.default.join(import_node_path50.default.dirname(opts.outPath), import_node_path50.default.basename(projectPaths.errorsPath));
11112
11378
  const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
11113
11379
  await saveGraphToDisk(graph, opts.outPath);
11114
11380
  written.push(opts.outPath);
@@ -11187,7 +11453,7 @@ async function runInit(opts) {
11187
11453
  console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
11188
11454
  }
11189
11455
  } else {
11190
- await import_node_fs31.promises.writeFile(patchPath, patch, "utf8");
11456
+ await import_node_fs32.promises.writeFile(patchPath, patch, "utf8");
11191
11457
  written.push(patchPath);
11192
11458
  }
11193
11459
  }
@@ -11220,16 +11486,16 @@ var CLAUDE_SKILL_CONFIG = {
11220
11486
  command: "npx",
11221
11487
  args: ["-y", "@neat.is/mcp"],
11222
11488
  env: {
11223
- NEAT_API_URL: "http://localhost:8080"
11489
+ NEAT_CORE_URL: "http://localhost:8080"
11224
11490
  }
11225
11491
  }
11226
11492
  }
11227
11493
  };
11228
11494
  function claudeConfigPath() {
11229
11495
  const override = process.env.NEAT_CLAUDE_CONFIG;
11230
- if (override && override.length > 0) return import_node_path48.default.resolve(override);
11496
+ if (override && override.length > 0) return import_node_path50.default.resolve(override);
11231
11497
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
11232
- return import_node_path48.default.join(home, ".claude.json");
11498
+ return import_node_path50.default.join(home, ".claude.json");
11233
11499
  }
11234
11500
  async function runSkill(opts) {
11235
11501
  const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
@@ -11241,7 +11507,7 @@ async function runSkill(opts) {
11241
11507
  const target = claudeConfigPath();
11242
11508
  let existing = {};
11243
11509
  try {
11244
- existing = JSON.parse(await import_node_fs31.promises.readFile(target, "utf8"));
11510
+ existing = JSON.parse(await import_node_fs32.promises.readFile(target, "utf8"));
11245
11511
  } catch (err) {
11246
11512
  if (err.code !== "ENOENT") {
11247
11513
  console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
@@ -11253,8 +11519,8 @@ async function runSkill(opts) {
11253
11519
  ...existing,
11254
11520
  mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
11255
11521
  };
11256
- await import_node_fs31.promises.mkdir(import_node_path48.default.dirname(target), { recursive: true });
11257
- await import_node_fs31.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
11522
+ await import_node_fs32.promises.mkdir(import_node_path50.default.dirname(target), { recursive: true });
11523
+ await import_node_fs32.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
11258
11524
  console.log(`neat skill: wrote mcpServers.neat to ${target}`);
11259
11525
  console.log("restart Claude Code to pick up the new MCP server.");
11260
11526
  return { exitCode: 0 };
@@ -11266,19 +11532,30 @@ async function runSkill(opts) {
11266
11532
  console.log("");
11267
11533
  console.log("Manual install: copy mcpServers.neat from --print-config into ~/.claude.json,");
11268
11534
  console.log("then restart Claude Code. See packages/claude-skill/SKILL.md for the tool list.");
11535
+ console.log("");
11536
+ console.log("The MCP server reads NEAT_CORE_URL for the daemon URL \u2014 point it at a");
11537
+ console.log("non-default daemon by editing that value in the generated config.");
11269
11538
  return { exitCode: 0 };
11270
11539
  }
11271
11540
  async function main() {
11272
- const [, , cmd, ...rest] = process.argv;
11273
- if (!cmd || cmd === "-h" || cmd === "--help") {
11541
+ const argv = process.argv.slice(2);
11542
+ const cmd0 = argv[0];
11543
+ if (cmd0 === "-h" || cmd0 === "--help") {
11274
11544
  usage();
11275
11545
  process.exit(0);
11276
11546
  }
11277
- if (cmd === "--version" || cmd === "-v" || cmd === "version") {
11547
+ if (cmd0 === "--version" || cmd0 === "-v" || cmd0 === "version") {
11278
11548
  printVersion();
11279
11549
  process.exit(0);
11280
11550
  }
11281
- const parsed = parseArgs(rest);
11551
+ const argvParsed = parseArgs(argv);
11552
+ if (argvParsed.positional.length === 0) {
11553
+ const orchestratorCode2 = await tryOrchestrator(process.cwd(), argvParsed);
11554
+ if (orchestratorCode2 !== null && orchestratorCode2 !== 0) process.exit(orchestratorCode2);
11555
+ return;
11556
+ }
11557
+ const cmd = argvParsed.positional[0];
11558
+ const parsed = { ...argvParsed, positional: argvParsed.positional.slice(1) };
11282
11559
  const { positional, apply: apply3, dryRun, noInstall } = parsed;
11283
11560
  const project = parsed.project ?? DEFAULT_PROJECT;
11284
11561
  if (cmd === "init") {
@@ -11292,12 +11569,12 @@ async function main() {
11292
11569
  console.error("neat init: --apply and --dry-run are mutually exclusive");
11293
11570
  process.exit(2);
11294
11571
  }
11295
- const scanPath = import_node_path48.default.resolve(target);
11572
+ const scanPath = import_node_path50.default.resolve(target);
11296
11573
  const projectExplicit = parsed.project !== null;
11297
- const projectName = projectExplicit ? project : import_node_path48.default.basename(scanPath);
11574
+ const projectName = projectExplicit ? project : import_node_path50.default.basename(scanPath);
11298
11575
  const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
11299
- const fallback = pathsForProject(projectKey, import_node_path48.default.join(scanPath, "neat-out")).snapshotPath;
11300
- const outPath = import_node_path48.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
11576
+ const fallback = pathsForProject(projectKey, import_node_path50.default.join(scanPath, "neat-out")).snapshotPath;
11577
+ const outPath = import_node_path50.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
11301
11578
  const result = await runInit({
11302
11579
  scanPath,
11303
11580
  outPath,
@@ -11318,21 +11595,21 @@ async function main() {
11318
11595
  usage();
11319
11596
  process.exit(2);
11320
11597
  }
11321
- const scanPath = import_node_path48.default.resolve(target);
11322
- const stat = await import_node_fs31.promises.stat(scanPath).catch(() => null);
11598
+ const scanPath = import_node_path50.default.resolve(target);
11599
+ const stat = await import_node_fs32.promises.stat(scanPath).catch(() => null);
11323
11600
  if (!stat || !stat.isDirectory()) {
11324
11601
  console.error(`neat watch: ${scanPath} is not a directory`);
11325
11602
  process.exit(2);
11326
11603
  }
11327
- const projectPaths = pathsForProject(project, import_node_path48.default.join(scanPath, "neat-out"));
11328
- const outPath = import_node_path48.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
11329
- const errorsPath = import_node_path48.default.resolve(
11330
- process.env.NEAT_ERRORS_PATH ?? import_node_path48.default.join(import_node_path48.default.dirname(outPath), import_node_path48.default.basename(projectPaths.errorsPath))
11604
+ const projectPaths = pathsForProject(project, import_node_path50.default.join(scanPath, "neat-out"));
11605
+ const outPath = import_node_path50.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
11606
+ const errorsPath = import_node_path50.default.resolve(
11607
+ process.env.NEAT_ERRORS_PATH ?? import_node_path50.default.join(import_node_path50.default.dirname(outPath), import_node_path50.default.basename(projectPaths.errorsPath))
11331
11608
  );
11332
- const staleEventsPath = import_node_path48.default.resolve(
11333
- process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path48.default.join(import_node_path48.default.dirname(outPath), import_node_path48.default.basename(projectPaths.staleEventsPath))
11609
+ const staleEventsPath = import_node_path50.default.resolve(
11610
+ process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path50.default.join(import_node_path50.default.dirname(outPath), import_node_path50.default.basename(projectPaths.staleEventsPath))
11334
11611
  );
11335
- const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path48.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
11612
+ const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path50.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
11336
11613
  const handle = await startWatch(getGraph(project), {
11337
11614
  scanPath,
11338
11615
  outPath,
@@ -11425,6 +11702,19 @@ async function main() {
11425
11702
  console.log("note: neat-out/, policy.json, and other files at the project path were left in place.");
11426
11703
  return;
11427
11704
  }
11705
+ if (cmd === "prune") {
11706
+ const removed = await pruneRegistry({ ttlMs: 0 });
11707
+ if (parsed.json) {
11708
+ console.log(JSON.stringify(removed.map((p) => ({ name: p.name, path: p.path })), null, 2));
11709
+ return;
11710
+ }
11711
+ if (removed.length === 0) {
11712
+ console.log("nothing to prune \u2014 every registered project path still exists.");
11713
+ return;
11714
+ }
11715
+ console.log(`pruned ${removed.length} project${removed.length === 1 ? "" : "s"}: ${removed.map((p) => p.name).join(", ")}`);
11716
+ return;
11717
+ }
11428
11718
  if (cmd === "deploy") {
11429
11719
  const artifact = await runDeploy();
11430
11720
  const block = renderOtelEnvBlock2(artifact.token);
@@ -11478,11 +11768,11 @@ async function main() {
11478
11768
  process.exit(1);
11479
11769
  }
11480
11770
  async function tryOrchestrator(cmd, parsed) {
11481
- const scanPath = import_node_path48.default.resolve(cmd);
11482
- const stat = await import_node_fs31.promises.stat(scanPath).catch(() => null);
11771
+ const scanPath = import_node_path50.default.resolve(cmd);
11772
+ const stat = await import_node_fs32.promises.stat(scanPath).catch(() => null);
11483
11773
  if (!stat || !stat.isDirectory()) return null;
11484
11774
  const projectExplicit = parsed.project !== null;
11485
- const projectName = projectExplicit ? parsed.project : import_node_path48.default.basename(scanPath);
11775
+ const projectName = projectExplicit ? parsed.project : import_node_path50.default.basename(scanPath);
11486
11776
  const result = await runOrchestrator({
11487
11777
  scanPath,
11488
11778
  project: projectName,
@@ -11512,12 +11802,44 @@ function resolveProjectFlag(parsed) {
11512
11802
  if (env && env.length > 0 && env !== DEFAULT_PROJECT) return env;
11513
11803
  return void 0;
11514
11804
  }
11805
+ var ProjectResolutionError = class extends Error {
11806
+ constructor(message, exitCode = 2) {
11807
+ super(message);
11808
+ this.exitCode = exitCode;
11809
+ this.name = "ProjectResolutionError";
11810
+ }
11811
+ exitCode;
11812
+ };
11813
+ async function resolveProjectForVerb(client, parsed) {
11814
+ const explicit = resolveProjectFlag(parsed);
11815
+ if (explicit) return explicit;
11816
+ const projects = await client.get("/projects");
11817
+ if (projects.some((p) => p.name === DEFAULT_PROJECT)) {
11818
+ return void 0;
11819
+ }
11820
+ if (projects.length === 1) {
11821
+ return projects[0].name;
11822
+ }
11823
+ if (projects.length === 0) {
11824
+ throw new ProjectResolutionError(
11825
+ "No projects are registered with the daemon yet. Run `npx neat.is` in a repo to build a graph first, then re-run this command."
11826
+ );
11827
+ }
11828
+ const names = projects.map((p) => p.name).sort().map((n) => ` ${n}`).join("\n");
11829
+ throw new ProjectResolutionError(
11830
+ `Several projects are registered and none is named "default", so I can't pick one for you.
11831
+ Pass --project <name> to choose:
11832
+ ${names}`
11833
+ );
11834
+ }
11835
+ function resolveDaemonUrl() {
11836
+ return process.env.NEAT_API_URL ?? process.env.NEAT_CORE_URL ?? "http://localhost:8080";
11837
+ }
11515
11838
  async function runQueryVerb(cmd, parsed) {
11516
- const baseUrl = process.env.NEAT_API_URL ?? "http://localhost:8080";
11839
+ const baseUrl = resolveDaemonUrl();
11517
11840
  const client = createHttpClient(baseUrl, resolveAuthToken());
11518
- const project = resolveProjectFlag(parsed);
11519
11841
  const positional = parsed.positional;
11520
- let work;
11842
+ let makeWork;
11521
11843
  switch (cmd) {
11522
11844
  case "root-cause": {
11523
11845
  const node = positional[0];
@@ -11525,7 +11847,7 @@ async function runQueryVerb(cmd, parsed) {
11525
11847
  console.error("neat root-cause: missing <node-id>");
11526
11848
  return 2;
11527
11849
  }
11528
- work = runRootCause(client, {
11850
+ makeWork = (project) => runRootCause(client, {
11529
11851
  errorNode: node,
11530
11852
  ...parsed.errorId ? { errorId: parsed.errorId } : {},
11531
11853
  ...project ? { project } : {}
@@ -11538,7 +11860,7 @@ async function runQueryVerb(cmd, parsed) {
11538
11860
  console.error("neat blast-radius: missing <node-id>");
11539
11861
  return 2;
11540
11862
  }
11541
- work = runBlastRadius(client, {
11863
+ makeWork = (project) => runBlastRadius(client, {
11542
11864
  nodeId: node,
11543
11865
  ...parsed.depth !== null ? { depth: parsed.depth } : {},
11544
11866
  ...project ? { project } : {}
@@ -11551,7 +11873,7 @@ async function runQueryVerb(cmd, parsed) {
11551
11873
  console.error("neat dependencies: missing <node-id>");
11552
11874
  return 2;
11553
11875
  }
11554
- work = runDependencies(client, {
11876
+ makeWork = (project) => runDependencies(client, {
11555
11877
  nodeId: node,
11556
11878
  ...parsed.depth !== null ? { depth: parsed.depth } : {},
11557
11879
  ...project ? { project } : {}
@@ -11564,14 +11886,14 @@ async function runQueryVerb(cmd, parsed) {
11564
11886
  console.error("neat observed-dependencies: missing <node-id>");
11565
11887
  return 2;
11566
11888
  }
11567
- work = runObservedDependencies(client, {
11889
+ makeWork = (project) => runObservedDependencies(client, {
11568
11890
  nodeId: node,
11569
11891
  ...project ? { project } : {}
11570
11892
  });
11571
11893
  break;
11572
11894
  }
11573
11895
  case "incidents": {
11574
- work = runIncidents(client, {
11896
+ makeWork = (project) => runIncidents(client, {
11575
11897
  ...positional[0] ? { nodeId: positional[0] } : {},
11576
11898
  ...parsed.limit !== null ? { limit: parsed.limit } : {},
11577
11899
  ...project ? { project } : {}
@@ -11584,7 +11906,7 @@ async function runQueryVerb(cmd, parsed) {
11584
11906
  console.error("neat search: missing <query>");
11585
11907
  return 2;
11586
11908
  }
11587
- work = runSearch(client, { query: q, ...project ? { project } : {} });
11909
+ makeWork = (project) => runSearch(client, { query: q, ...project ? { project } : {} });
11588
11910
  break;
11589
11911
  }
11590
11912
  case "diff": {
@@ -11593,14 +11915,14 @@ async function runQueryVerb(cmd, parsed) {
11593
11915
  console.error("neat diff: --against <snapshot-path> is required");
11594
11916
  return 2;
11595
11917
  }
11596
- work = runDiff(client, {
11918
+ makeWork = (project) => runDiff(client, {
11597
11919
  againstSnapshot: against,
11598
11920
  ...project ? { project } : {}
11599
11921
  });
11600
11922
  break;
11601
11923
  }
11602
11924
  case "stale-edges": {
11603
- work = runStaleEdges(client, {
11925
+ makeWork = (project) => runStaleEdges(client, {
11604
11926
  ...parsed.limit !== null ? { limit: parsed.limit } : {},
11605
11927
  ...parsed.edgeType ? { edgeType: parsed.edgeType } : {},
11606
11928
  ...project ? { project } : {}
@@ -11619,7 +11941,7 @@ async function runQueryVerb(cmd, parsed) {
11619
11941
  return 2;
11620
11942
  }
11621
11943
  }
11622
- work = runPolicies(client, {
11944
+ makeWork = (project) => runPolicies(client, {
11623
11945
  ...parsed.node ? { nodeId: parsed.node } : {},
11624
11946
  ...hypothetical ? { hypotheticalAction: hypothetical } : {},
11625
11947
  ...project ? { project } : {}
@@ -11632,10 +11954,10 @@ async function runQueryVerb(cmd, parsed) {
11632
11954
  const parts = parsed.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
11633
11955
  const out = [];
11634
11956
  for (const p of parts) {
11635
- const r = import_types27.DivergenceTypeSchema.safeParse(p);
11957
+ const r = import_types28.DivergenceTypeSchema.safeParse(p);
11636
11958
  if (!r.success) {
11637
11959
  console.error(
11638
- `neat divergences: unknown --type "${p}". allowed: ${import_types27.DivergenceTypeSchema.options.join(", ")}`
11960
+ `neat divergences: unknown --type "${p}". allowed: ${import_types28.DivergenceTypeSchema.options.join(", ")}`
11639
11961
  );
11640
11962
  return 2;
11641
11963
  }
@@ -11643,7 +11965,7 @@ async function runQueryVerb(cmd, parsed) {
11643
11965
  }
11644
11966
  typeFilter = out;
11645
11967
  }
11646
- work = runDivergences(client, {
11968
+ makeWork = (project) => runDivergences(client, {
11647
11969
  ...typeFilter ? { type: typeFilter } : {},
11648
11970
  ...parsed.minConfidence !== null ? { minConfidence: parsed.minConfidence } : {},
11649
11971
  ...parsed.node ? { node: parsed.node } : {},
@@ -11656,16 +11978,21 @@ async function runQueryVerb(cmd, parsed) {
11656
11978
  return 2;
11657
11979
  }
11658
11980
  try {
11659
- const result = await work;
11981
+ const project = await resolveProjectForVerb(client, parsed);
11982
+ const result = await makeWork(project);
11660
11983
  if (parsed.json) process.stdout.write(formatJson(result) + "\n");
11661
11984
  else process.stdout.write(formatHuman(result) + "\n");
11662
11985
  return 0;
11663
11986
  } catch (err) {
11987
+ if (err instanceof ProjectResolutionError) {
11988
+ console.error(`neat ${cmd}: ${err.message}`);
11989
+ return err.exitCode;
11990
+ }
11664
11991
  if (err instanceof HttpError) {
11665
11992
  const detail = err.responseBody.length > 0 ? err.responseBody : err.message;
11666
11993
  console.error(`neat ${cmd}: ${detail.trim()}`);
11667
11994
  } else if (err instanceof TransportError) {
11668
- console.error(`neat ${cmd}: ${err.message}. Is the daemon running? (NEAT_API_URL=${process.env.NEAT_API_URL ?? "http://localhost:8080"})`);
11995
+ console.error(`neat ${cmd}: ${err.message}. Is the daemon running? (NEAT_API_URL=${resolveDaemonUrl()})`);
11669
11996
  } else {
11670
11997
  console.error(`neat ${cmd}: ${err.message}`);
11671
11998
  }
@@ -11682,12 +12009,18 @@ if (/[\\/]cli\.(?:cjs|js)$/.test(entry) || entry.endsWith("/cli") || entry.endsW
11682
12009
  // Annotate the CommonJS export names for ESM import in node:
11683
12010
  0 && (module.exports = {
11684
12011
  CLAUDE_SKILL_CONFIG,
12012
+ ProjectResolutionError,
11685
12013
  QUERY_VERBS,
12014
+ commandPrefix,
12015
+ isNpxInvocation,
12016
+ main,
11686
12017
  parseArgs,
11687
12018
  printBanner,
11688
12019
  readPackageVersion,
12020
+ resolveProjectForVerb,
11689
12021
  runInit,
11690
12022
  runQueryVerb,
11691
- runSkill
12023
+ runSkill,
12024
+ usage
11692
12025
  });
11693
12026
  //# sourceMappingURL=cli.cjs.map