@neat.is/core 0.4.16 → 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 path50 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
61
+ const path51 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
62
62
  for (const suffix of suffixes) {
63
- if (path50 === suffix || path50.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_path40.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
192
- return import_node_path40.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_url2, import_node_path40, 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
265
  import_node_url2 = require("url");
266
- import_node_path40 = __toESM(require("path"), 1);
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_path41.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
377
- const protoRoot = import_node_path41.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_path41.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,12 +572,12 @@ async function buildOtelReceiver(opts) {
572
572
  };
573
573
  return decorated;
574
574
  }
575
- var import_node_path41, import_node_url3, 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_path41 = __toESM(require("path"), 1);
580
+ import_node_path42 = __toESM(require("path"), 1);
581
581
  import_node_url3 = require("url");
582
582
  import_fastify2 = __toESM(require("fastify"), 1);
583
583
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -595,6 +595,7 @@ 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,
599
600
  commandPrefix: () => commandPrefix,
600
601
  isNpxInvocation: () => isNpxInvocation,
@@ -602,6 +603,7 @@ __export(cli_exports, {
602
603
  parseArgs: () => parseArgs,
603
604
  printBanner: () => printBanner,
604
605
  readPackageVersion: () => readPackageVersion,
606
+ resolveProjectForVerb: () => resolveProjectForVerb,
605
607
  runInit: () => runInit,
606
608
  runQueryVerb: () => runQueryVerb,
607
609
  runSkill: () => runSkill,
@@ -609,7 +611,7 @@ __export(cli_exports, {
609
611
  });
610
612
  module.exports = __toCommonJS(cli_exports);
611
613
  init_cjs_shims();
612
- var import_node_path49 = __toESM(require("path"), 1);
614
+ var import_node_path50 = __toESM(require("path"), 1);
613
615
  var import_node_fs32 = require("fs");
614
616
 
615
617
  // src/banner.ts
@@ -1175,19 +1177,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1175
1177
  function longestIncomingWalk(graph, start, maxDepth) {
1176
1178
  let best = { path: [start], edges: [] };
1177
1179
  const visited = /* @__PURE__ */ new Set([start]);
1178
- function step(node, path50, edges) {
1179
- if (path50.length > best.path.length) {
1180
- best = { path: [...path50], edges: [...edges] };
1180
+ function step(node, path51, edges) {
1181
+ if (path51.length > best.path.length) {
1182
+ best = { path: [...path51], edges: [...edges] };
1181
1183
  }
1182
- if (path50.length - 1 >= maxDepth) return;
1184
+ if (path51.length - 1 >= maxDepth) return;
1183
1185
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1184
1186
  for (const [srcId, edge] of incoming) {
1185
1187
  if (visited.has(srcId)) continue;
1186
1188
  visited.add(srcId);
1187
- path50.push(srcId);
1189
+ path51.push(srcId);
1188
1190
  edges.push(edge);
1189
- step(srcId, path50, edges);
1190
- path50.pop();
1191
+ step(srcId, path51, edges);
1192
+ path51.pop();
1191
1193
  edges.pop();
1192
1194
  visited.delete(srcId);
1193
1195
  }
@@ -1727,6 +1729,41 @@ function thresholdForEdgeType(edgeType, overrides) {
1727
1729
  const map = overrides ?? loadStaleThresholdsFromEnv();
1728
1730
  return map[edgeType] ?? FALLBACK_STALE_THRESHOLD_MS;
1729
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
+ }
1730
1767
  function nowIso(ctx) {
1731
1768
  return new Date(ctx.now ? ctx.now() : Date.now()).toISOString();
1732
1769
  }
@@ -2140,6 +2177,71 @@ function makeErrorSpanWriter(errorsPath) {
2140
2177
  await import_node_fs4.promises.appendFile(errorsPath, JSON.stringify(ev) + "\n", "utf8");
2141
2178
  };
2142
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
+ }
2143
2245
  async function handleSpan(ctx, span) {
2144
2246
  const ts = span.startTimeIso ?? nowIso(ctx);
2145
2247
  const nowMs = ctx.now ? ctx.now() : Date.now();
@@ -2238,6 +2340,14 @@ async function handleSpan(ctx, span) {
2238
2340
  await appendErrorEvent(ctx, ev);
2239
2341
  }
2240
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
+ }
2241
2351
  void affectedNode;
2242
2352
  if (ctx.onPolicyTrigger) await ctx.onPolicyTrigger(ctx.graph);
2243
2353
  }
@@ -4269,7 +4379,7 @@ async function addConfigNodes(graph, services, scanPath) {
4269
4379
 
4270
4380
  // src/extract/calls/index.ts
4271
4381
  init_cjs_shims();
4272
- var import_types16 = require("@neat.is/types");
4382
+ var import_types17 = require("@neat.is/types");
4273
4383
 
4274
4384
  // src/extract/calls/http.ts
4275
4385
  init_cjs_shims();
@@ -4622,15 +4732,75 @@ function grpcEndpointsFromFile(file, serviceDir) {
4622
4732
  return out;
4623
4733
  }
4624
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),
4787
+ line,
4788
+ snippet: snippet(file.content, line)
4789
+ }
4790
+ });
4791
+ }
4792
+ return out;
4793
+ }
4794
+
4625
4795
  // src/extract/calls/index.ts
4626
4796
  function edgeTypeFromEndpoint(ep) {
4627
4797
  switch (ep.edgeType) {
4628
4798
  case "PUBLISHES_TO":
4629
- return import_types16.EdgeType.PUBLISHES_TO;
4799
+ return import_types17.EdgeType.PUBLISHES_TO;
4630
4800
  case "CONSUMES_FROM":
4631
- return import_types16.EdgeType.CONSUMES_FROM;
4801
+ return import_types17.EdgeType.CONSUMES_FROM;
4632
4802
  default:
4633
- return import_types16.EdgeType.CALLS;
4803
+ return import_types17.EdgeType.CALLS;
4634
4804
  }
4635
4805
  }
4636
4806
  function isAwsKind(kind) {
@@ -4650,6 +4820,7 @@ async function addExternalEndpointEdges(graph, services) {
4650
4820
  endpoints.push(...redisEndpointsFromFile(maskedFile, service.dir));
4651
4821
  endpoints.push(...awsEndpointsFromFile(maskedFile, service.dir));
4652
4822
  endpoints.push(...grpcEndpointsFromFile(maskedFile, service.dir));
4823
+ endpoints.push(...supabaseEndpointsFromFile(maskedFile, service.dir));
4653
4824
  }
4654
4825
  if (endpoints.length === 0) continue;
4655
4826
  const seenEdges = /* @__PURE__ */ new Set();
@@ -4657,7 +4828,7 @@ async function addExternalEndpointEdges(graph, services) {
4657
4828
  if (!graph.hasNode(ep.infraId)) {
4658
4829
  const node = {
4659
4830
  id: ep.infraId,
4660
- type: import_types16.NodeType.InfraNode,
4831
+ type: import_types17.NodeType.InfraNode,
4661
4832
  name: ep.name,
4662
4833
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
4663
4834
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -4669,7 +4840,7 @@ async function addExternalEndpointEdges(graph, services) {
4669
4840
  nodesAdded++;
4670
4841
  }
4671
4842
  const edgeType = edgeTypeFromEndpoint(ep);
4672
- const confidence = (0, import_types16.confidenceForExtracted)(ep.confidenceKind);
4843
+ const confidence = (0, import_types17.confidenceForExtracted)(ep.confidenceKind);
4673
4844
  const relFile = toPosix2(ep.evidence.file);
4674
4845
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
4675
4846
  graph,
@@ -4679,7 +4850,7 @@ async function addExternalEndpointEdges(graph, services) {
4679
4850
  );
4680
4851
  nodesAdded += n;
4681
4852
  edgesAdded += e;
4682
- if (!(0, import_types16.passesExtractedFloor)(confidence)) {
4853
+ if (!(0, import_types17.passesExtractedFloor)(confidence)) {
4683
4854
  noteExtractedDropped({
4684
4855
  source: fileNodeId,
4685
4856
  target: ep.infraId,
@@ -4699,7 +4870,7 @@ async function addExternalEndpointEdges(graph, services) {
4699
4870
  source: fileNodeId,
4700
4871
  target: ep.infraId,
4701
4872
  type: edgeType,
4702
- provenance: import_types16.Provenance.EXTRACTED,
4873
+ provenance: import_types17.Provenance.EXTRACTED,
4703
4874
  confidence,
4704
4875
  evidence: ep.evidence
4705
4876
  };
@@ -4724,16 +4895,16 @@ init_cjs_shims();
4724
4895
 
4725
4896
  // src/extract/infra/docker-compose.ts
4726
4897
  init_cjs_shims();
4727
- var import_node_path28 = __toESM(require("path"), 1);
4728
- var import_types18 = require("@neat.is/types");
4898
+ var import_node_path29 = __toESM(require("path"), 1);
4899
+ var import_types19 = require("@neat.is/types");
4729
4900
 
4730
4901
  // src/extract/infra/shared.ts
4731
4902
  init_cjs_shims();
4732
- var import_types17 = require("@neat.is/types");
4903
+ var import_types18 = require("@neat.is/types");
4733
4904
  function makeInfraNode(kind, name, provider = "self", extras) {
4734
4905
  return {
4735
- id: (0, import_types17.infraId)(kind, name),
4736
- type: import_types17.NodeType.InfraNode,
4906
+ id: (0, import_types18.infraId)(kind, name),
4907
+ type: import_types18.NodeType.InfraNode,
4737
4908
  name,
4738
4909
  provider,
4739
4910
  kind,
@@ -4762,7 +4933,7 @@ function dependsOnList(value) {
4762
4933
  }
4763
4934
  function serviceNameToServiceNode(name, services) {
4764
4935
  for (const s of services) {
4765
- if (s.node.name === name || import_node_path28.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;
4766
4937
  }
4767
4938
  return null;
4768
4939
  }
@@ -4771,7 +4942,7 @@ async function addComposeInfra(graph, scanPath, services) {
4771
4942
  let edgesAdded = 0;
4772
4943
  let composePath = null;
4773
4944
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
4774
- const abs = import_node_path28.default.join(scanPath, name);
4945
+ const abs = import_node_path29.default.join(scanPath, name);
4775
4946
  if (await exists(abs)) {
4776
4947
  composePath = abs;
4777
4948
  break;
@@ -4784,13 +4955,13 @@ async function addComposeInfra(graph, scanPath, services) {
4784
4955
  } catch (err) {
4785
4956
  recordExtractionError(
4786
4957
  "infra docker-compose",
4787
- import_node_path28.default.relative(scanPath, composePath),
4958
+ import_node_path29.default.relative(scanPath, composePath),
4788
4959
  err
4789
4960
  );
4790
4961
  return { nodesAdded, edgesAdded };
4791
4962
  }
4792
4963
  if (!compose?.services) return { nodesAdded, edgesAdded };
4793
- const evidenceFile = import_node_path28.default.relative(scanPath, composePath).split(import_node_path28.default.sep).join("/");
4964
+ const evidenceFile = import_node_path29.default.relative(scanPath, composePath).split(import_node_path29.default.sep).join("/");
4794
4965
  const composeNameToNodeId = /* @__PURE__ */ new Map();
4795
4966
  for (const [composeName, svc] of Object.entries(compose.services)) {
4796
4967
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -4812,15 +4983,15 @@ async function addComposeInfra(graph, scanPath, services) {
4812
4983
  for (const dep of dependsOnList(svc.depends_on)) {
4813
4984
  const targetId = composeNameToNodeId.get(dep);
4814
4985
  if (!targetId) continue;
4815
- 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);
4816
4987
  if (graph.hasEdge(edgeId)) continue;
4817
4988
  const edge = {
4818
4989
  id: edgeId,
4819
4990
  source: sourceId,
4820
4991
  target: targetId,
4821
- type: import_types18.EdgeType.DEPENDS_ON,
4822
- provenance: import_types18.Provenance.EXTRACTED,
4823
- 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"),
4824
4995
  evidence: { file: evidenceFile }
4825
4996
  };
4826
4997
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -4832,9 +5003,9 @@ async function addComposeInfra(graph, scanPath, services) {
4832
5003
 
4833
5004
  // src/extract/infra/dockerfile.ts
4834
5005
  init_cjs_shims();
4835
- var import_node_path29 = __toESM(require("path"), 1);
5006
+ var import_node_path30 = __toESM(require("path"), 1);
4836
5007
  var import_node_fs16 = require("fs");
4837
- var import_types19 = require("@neat.is/types");
5008
+ var import_types20 = require("@neat.is/types");
4838
5009
  function runtimeImage(content) {
4839
5010
  const lines = content.split("\n");
4840
5011
  let last = null;
@@ -4853,7 +5024,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4853
5024
  let nodesAdded = 0;
4854
5025
  let edgesAdded = 0;
4855
5026
  for (const service of services) {
4856
- const dockerfilePath = import_node_path29.default.join(service.dir, "Dockerfile");
5027
+ const dockerfilePath = import_node_path30.default.join(service.dir, "Dockerfile");
4857
5028
  if (!await exists(dockerfilePath)) continue;
4858
5029
  let content;
4859
5030
  try {
@@ -4861,7 +5032,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4861
5032
  } catch (err) {
4862
5033
  recordExtractionError(
4863
5034
  "infra dockerfile",
4864
- import_node_path29.default.relative(scanPath, dockerfilePath),
5035
+ import_node_path30.default.relative(scanPath, dockerfilePath),
4865
5036
  err
4866
5037
  );
4867
5038
  continue;
@@ -4873,7 +5044,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4873
5044
  graph.addNode(node.id, node);
4874
5045
  nodesAdded++;
4875
5046
  }
4876
- const relDockerfile = toPosix2(import_node_path29.default.relative(service.dir, dockerfilePath));
5047
+ const relDockerfile = toPosix2(import_node_path30.default.relative(service.dir, dockerfilePath));
4877
5048
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
4878
5049
  graph,
4879
5050
  service.pkg.name,
@@ -4882,17 +5053,17 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4882
5053
  );
4883
5054
  nodesAdded += fn;
4884
5055
  edgesAdded += fe;
4885
- 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);
4886
5057
  if (!graph.hasEdge(edgeId)) {
4887
5058
  const edge = {
4888
5059
  id: edgeId,
4889
5060
  source: fileNodeId,
4890
5061
  target: node.id,
4891
- type: import_types19.EdgeType.RUNS_ON,
4892
- provenance: import_types19.Provenance.EXTRACTED,
4893
- 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"),
4894
5065
  evidence: {
4895
- file: toPosix2(import_node_path29.default.relative(scanPath, dockerfilePath))
5066
+ file: toPosix2(import_node_path30.default.relative(scanPath, dockerfilePath))
4896
5067
  }
4897
5068
  };
4898
5069
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -4905,7 +5076,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4905
5076
  // src/extract/infra/terraform.ts
4906
5077
  init_cjs_shims();
4907
5078
  var import_node_fs17 = require("fs");
4908
- var import_node_path30 = __toESM(require("path"), 1);
5079
+ var import_node_path31 = __toESM(require("path"), 1);
4909
5080
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
4910
5081
  async function walkTfFiles(start, depth = 0, max = 5) {
4911
5082
  if (depth > max) return [];
@@ -4914,11 +5085,11 @@ async function walkTfFiles(start, depth = 0, max = 5) {
4914
5085
  for (const entry2 of entries) {
4915
5086
  if (entry2.isDirectory()) {
4916
5087
  if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
4917
- const child = import_node_path30.default.join(start, entry2.name);
5088
+ const child = import_node_path31.default.join(start, entry2.name);
4918
5089
  if (await isPythonVenvDir(child)) continue;
4919
5090
  out.push(...await walkTfFiles(child, depth + 1, max));
4920
5091
  } else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
4921
- out.push(import_node_path30.default.join(start, entry2.name));
5092
+ out.push(import_node_path31.default.join(start, entry2.name));
4922
5093
  }
4923
5094
  }
4924
5095
  return out;
@@ -4946,7 +5117,7 @@ async function addTerraformResources(graph, scanPath) {
4946
5117
  // src/extract/infra/k8s.ts
4947
5118
  init_cjs_shims();
4948
5119
  var import_node_fs18 = require("fs");
4949
- var import_node_path31 = __toESM(require("path"), 1);
5120
+ var import_node_path32 = __toESM(require("path"), 1);
4950
5121
  var import_yaml3 = require("yaml");
4951
5122
  var K8S_KIND_TO_INFRA_KIND = {
4952
5123
  Service: "k8s-service",
@@ -4964,11 +5135,11 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
4964
5135
  for (const entry2 of entries) {
4965
5136
  if (entry2.isDirectory()) {
4966
5137
  if (IGNORED_DIRS.has(entry2.name)) continue;
4967
- const child = import_node_path31.default.join(start, entry2.name);
5138
+ const child = import_node_path32.default.join(start, entry2.name);
4968
5139
  if (await isPythonVenvDir(child)) continue;
4969
5140
  out.push(...await walkYamlFiles2(child, depth + 1, max));
4970
- } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path31.default.extname(entry2.name))) {
4971
- out.push(import_node_path31.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));
4972
5143
  }
4973
5144
  }
4974
5145
  return out;
@@ -5012,17 +5183,17 @@ async function addInfra(graph, scanPath, services) {
5012
5183
  }
5013
5184
 
5014
5185
  // src/extract/index.ts
5015
- var import_node_path33 = __toESM(require("path"), 1);
5186
+ var import_node_path34 = __toESM(require("path"), 1);
5016
5187
 
5017
5188
  // src/extract/retire.ts
5018
5189
  init_cjs_shims();
5019
5190
  var import_node_fs19 = require("fs");
5020
- var import_node_path32 = __toESM(require("path"), 1);
5021
- var import_types20 = require("@neat.is/types");
5191
+ var import_node_path33 = __toESM(require("path"), 1);
5192
+ var import_types21 = require("@neat.is/types");
5022
5193
  function dropOrphanedFileNodes(graph) {
5023
5194
  const orphans = [];
5024
5195
  graph.forEachNode((id, attrs) => {
5025
- if (attrs.type !== import_types20.NodeType.FileNode) return;
5196
+ if (attrs.type !== import_types21.NodeType.FileNode) return;
5026
5197
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
5027
5198
  orphans.push(id);
5028
5199
  }
@@ -5035,7 +5206,7 @@ function retireEdgesByFile(graph, file) {
5035
5206
  const toDrop = [];
5036
5207
  graph.forEachEdge((id, attrs) => {
5037
5208
  const edge = attrs;
5038
- if (edge.provenance !== import_types20.Provenance.EXTRACTED) return;
5209
+ if (edge.provenance !== import_types21.Provenance.EXTRACTED) return;
5039
5210
  if (!edge.evidence?.file) return;
5040
5211
  if (edge.evidence.file === normalized) toDrop.push(id);
5041
5212
  });
@@ -5048,14 +5219,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
5048
5219
  const bases = [scanPath, ...serviceDirs];
5049
5220
  graph.forEachEdge((id, attrs) => {
5050
5221
  const edge = attrs;
5051
- if (edge.provenance !== import_types20.Provenance.EXTRACTED) return;
5222
+ if (edge.provenance !== import_types21.Provenance.EXTRACTED) return;
5052
5223
  const evidenceFile = edge.evidence?.file;
5053
5224
  if (!evidenceFile) return;
5054
- if (import_node_path32.default.isAbsolute(evidenceFile)) {
5225
+ if (import_node_path33.default.isAbsolute(evidenceFile)) {
5055
5226
  if (!(0, import_node_fs19.existsSync)(evidenceFile)) toDrop.push(id);
5056
5227
  return;
5057
5228
  }
5058
- const found = bases.some((base) => (0, import_node_fs19.existsSync)(import_node_path32.default.join(base, evidenceFile)));
5229
+ const found = bases.some((base) => (0, import_node_fs19.existsSync)(import_node_path33.default.join(base, evidenceFile)));
5059
5230
  if (!found) toDrop.push(id);
5060
5231
  });
5061
5232
  for (const id of toDrop) graph.dropEdge(id);
@@ -5095,7 +5266,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
5095
5266
  }
5096
5267
  const droppedEntries = drainDroppedExtracted();
5097
5268
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
5098
- const rejectedPath = import_node_path33.default.join(import_node_path33.default.dirname(opts.errorsPath), "rejected.ndjson");
5269
+ const rejectedPath = import_node_path34.default.join(import_node_path34.default.dirname(opts.errorsPath), "rejected.ndjson");
5099
5270
  try {
5100
5271
  await writeRejectedExtracted(droppedEntries, rejectedPath);
5101
5272
  } catch (err) {
@@ -5129,7 +5300,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
5129
5300
 
5130
5301
  // src/divergences.ts
5131
5302
  init_cjs_shims();
5132
- var import_types21 = require("@neat.is/types");
5303
+ var import_types22 = require("@neat.is/types");
5133
5304
  function bucketKey(source, target, type) {
5134
5305
  return `${type}|${source}|${target}`;
5135
5306
  }
@@ -5137,22 +5308,22 @@ function bucketEdges(graph) {
5137
5308
  const buckets = /* @__PURE__ */ new Map();
5138
5309
  graph.forEachEdge((id, attrs) => {
5139
5310
  const e = attrs;
5140
- const parsed = (0, import_types21.parseEdgeId)(id);
5311
+ const parsed = (0, import_types22.parseEdgeId)(id);
5141
5312
  const provenance = parsed?.provenance ?? e.provenance;
5142
5313
  const key = bucketKey(e.source, e.target, e.type);
5143
5314
  const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
5144
5315
  switch (provenance) {
5145
- case import_types21.Provenance.EXTRACTED:
5316
+ case import_types22.Provenance.EXTRACTED:
5146
5317
  cur.extracted = e;
5147
5318
  break;
5148
- case import_types21.Provenance.OBSERVED:
5319
+ case import_types22.Provenance.OBSERVED:
5149
5320
  cur.observed = e;
5150
5321
  break;
5151
- case import_types21.Provenance.INFERRED:
5322
+ case import_types22.Provenance.INFERRED:
5152
5323
  cur.inferred = e;
5153
5324
  break;
5154
5325
  default:
5155
- if (e.provenance === import_types21.Provenance.STALE) cur.stale = e;
5326
+ if (e.provenance === import_types22.Provenance.STALE) cur.stale = e;
5156
5327
  }
5157
5328
  buckets.set(key, cur);
5158
5329
  });
@@ -5161,7 +5332,7 @@ function bucketEdges(graph) {
5161
5332
  function nodeIsFrontier(graph, nodeId) {
5162
5333
  if (!graph.hasNode(nodeId)) return false;
5163
5334
  const attrs = graph.getNodeAttributes(nodeId);
5164
- return attrs.type === import_types21.NodeType.FrontierNode;
5335
+ return attrs.type === import_types22.NodeType.FrontierNode;
5165
5336
  }
5166
5337
  function clampConfidence(n) {
5167
5338
  if (!Number.isFinite(n)) return 0;
@@ -5182,7 +5353,7 @@ function gradedConfidence(edge) {
5182
5353
  }
5183
5354
  function detectMissingDivergences(graph, bucket) {
5184
5355
  const out = [];
5185
- if (bucket.type === import_types21.EdgeType.CONTAINS) return out;
5356
+ if (bucket.type === import_types22.EdgeType.CONTAINS) return out;
5186
5357
  if (bucket.extracted && !bucket.observed) {
5187
5358
  if (!nodeIsFrontier(graph, bucket.target)) {
5188
5359
  out.push({
@@ -5223,7 +5394,7 @@ function declaredHostFor(svc) {
5223
5394
  function hasExtractedConfiguredBy(graph, svcId) {
5224
5395
  for (const edgeId of graph.outboundEdges(svcId)) {
5225
5396
  const e = graph.getEdgeAttributes(edgeId);
5226
- 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) {
5227
5398
  return true;
5228
5399
  }
5229
5400
  }
@@ -5236,10 +5407,10 @@ function detectHostMismatch(graph, svcId, svc) {
5236
5407
  const out = [];
5237
5408
  for (const edgeId of graph.outboundEdges(svcId)) {
5238
5409
  const edge = graph.getEdgeAttributes(edgeId);
5239
- if (edge.type !== import_types21.EdgeType.CONNECTS_TO) continue;
5240
- 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;
5241
5412
  const target = graph.getNodeAttributes(edge.target);
5242
- if (target.type !== import_types21.NodeType.DatabaseNode) continue;
5413
+ if (target.type !== import_types22.NodeType.DatabaseNode) continue;
5243
5414
  const observedHost = target.host?.trim();
5244
5415
  if (!observedHost) continue;
5245
5416
  if (observedHost === declaredHost) continue;
@@ -5261,10 +5432,10 @@ function detectCompatDivergences(graph, svcId, svc) {
5261
5432
  const deps = svc.dependencies ?? {};
5262
5433
  for (const edgeId of graph.outboundEdges(svcId)) {
5263
5434
  const edge = graph.getEdgeAttributes(edgeId);
5264
- if (edge.type !== import_types21.EdgeType.CONNECTS_TO) continue;
5265
- 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;
5266
5437
  const target = graph.getNodeAttributes(edge.target);
5267
- if (target.type !== import_types21.NodeType.DatabaseNode) continue;
5438
+ if (target.type !== import_types22.NodeType.DatabaseNode) continue;
5268
5439
  for (const pair of compatPairs()) {
5269
5440
  if (pair.engine !== target.engine) continue;
5270
5441
  const declared = deps[pair.driver];
@@ -5325,7 +5496,7 @@ function computeDivergences(graph, opts = {}) {
5325
5496
  }
5326
5497
  graph.forEachNode((nodeId, attrs) => {
5327
5498
  const n = attrs;
5328
- if (n.type !== import_types21.NodeType.ServiceNode) return;
5499
+ if (n.type !== import_types22.NodeType.ServiceNode) return;
5329
5500
  const svc = n;
5330
5501
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
5331
5502
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
@@ -5358,7 +5529,7 @@ function computeDivergences(graph, opts = {}) {
5358
5529
  if (a.source !== b.source) return a.source.localeCompare(b.source);
5359
5530
  return a.target.localeCompare(b.target);
5360
5531
  });
5361
- return import_types21.DivergenceResultSchema.parse({
5532
+ return import_types22.DivergenceResultSchema.parse({
5362
5533
  divergences: filtered,
5363
5534
  totalAffected: filtered.length,
5364
5535
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -5368,8 +5539,8 @@ function computeDivergences(graph, opts = {}) {
5368
5539
  // src/persist.ts
5369
5540
  init_cjs_shims();
5370
5541
  var import_node_fs20 = require("fs");
5371
- var import_node_path34 = __toESM(require("path"), 1);
5372
- var import_types22 = require("@neat.is/types");
5542
+ var import_node_path35 = __toESM(require("path"), 1);
5543
+ var import_types23 = require("@neat.is/types");
5373
5544
  var SCHEMA_VERSION = 4;
5374
5545
  function migrateV1ToV2(payload) {
5375
5546
  const nodes = payload.graph.nodes;
@@ -5391,12 +5562,12 @@ function migrateV2ToV3(payload) {
5391
5562
  for (const edge of edges) {
5392
5563
  const attrs = edge.attributes;
5393
5564
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
5394
- attrs.provenance = import_types22.Provenance.OBSERVED;
5565
+ attrs.provenance = import_types23.Provenance.OBSERVED;
5395
5566
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
5396
5567
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
5397
5568
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
5398
5569
  if (type && source && target) {
5399
- const newId = (0, import_types22.observedEdgeId)(source, target, type);
5570
+ const newId = (0, import_types23.observedEdgeId)(source, target, type);
5400
5571
  attrs.id = newId;
5401
5572
  if (edge.key) edge.key = newId;
5402
5573
  }
@@ -5405,7 +5576,7 @@ function migrateV2ToV3(payload) {
5405
5576
  return { ...payload, schemaVersion: 3 };
5406
5577
  }
5407
5578
  async function ensureDir(filePath) {
5408
- await import_node_fs20.promises.mkdir(import_node_path34.default.dirname(filePath), { recursive: true });
5579
+ await import_node_fs20.promises.mkdir(import_node_path35.default.dirname(filePath), { recursive: true });
5409
5580
  }
5410
5581
  async function saveGraphToDisk(graph, outPath) {
5411
5582
  await ensureDir(outPath);
@@ -5481,7 +5652,7 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
5481
5652
  // src/gitignore.ts
5482
5653
  init_cjs_shims();
5483
5654
  var import_node_fs21 = require("fs");
5484
- var import_node_path35 = __toESM(require("path"), 1);
5655
+ var import_node_path36 = __toESM(require("path"), 1);
5485
5656
  var NEAT_OUT_LINE = "neat-out/";
5486
5657
  var NEAT_HEADER = "# NEAT \u2014 machine-local snapshots and events";
5487
5658
  function isNeatOutLine(line) {
@@ -5489,7 +5660,7 @@ function isNeatOutLine(line) {
5489
5660
  return trimmed === "neat-out/" || trimmed === "neat-out";
5490
5661
  }
5491
5662
  async function ensureNeatOutIgnored(projectDir) {
5492
- const file = import_node_path35.default.join(projectDir, ".gitignore");
5663
+ const file = import_node_path36.default.join(projectDir, ".gitignore");
5493
5664
  let existing = null;
5494
5665
  try {
5495
5666
  existing = await import_node_fs21.promises.readFile(file, "utf8");
@@ -5516,7 +5687,7 @@ ${NEAT_OUT_LINE}
5516
5687
 
5517
5688
  // src/summary.ts
5518
5689
  init_cjs_shims();
5519
- var import_types23 = require("@neat.is/types");
5690
+ var import_types24 = require("@neat.is/types");
5520
5691
  function renderOtelEnvBlock() {
5521
5692
  return [
5522
5693
  "for prod OTel routing, set these in your deploy platform's env:",
@@ -5526,19 +5697,19 @@ function renderOtelEnvBlock() {
5526
5697
  }
5527
5698
  function findIncompatServices(nodes) {
5528
5699
  return nodes.filter(
5529
- (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
5530
5701
  );
5531
5702
  }
5532
5703
  function servicesWithoutObserved(nodes, edges) {
5533
5704
  const seen = /* @__PURE__ */ new Set();
5534
5705
  for (const e of edges) {
5535
- if (e.provenance === import_types23.Provenance.OBSERVED) {
5706
+ if (e.provenance === import_types24.Provenance.OBSERVED) {
5536
5707
  seen.add(e.source);
5537
5708
  seen.add(e.target);
5538
5709
  }
5539
5710
  }
5540
5711
  return nodes.filter(
5541
- (n) => n.type === import_types23.NodeType.ServiceNode && !seen.has(n.id)
5712
+ (n) => n.type === import_types24.NodeType.ServiceNode && !seen.has(n.id)
5542
5713
  );
5543
5714
  }
5544
5715
  function formatDivergence(d) {
@@ -5613,26 +5784,26 @@ function formatIncompat(inc) {
5613
5784
  // src/watch.ts
5614
5785
  init_cjs_shims();
5615
5786
  var import_node_fs27 = __toESM(require("fs"), 1);
5616
- var import_node_path43 = __toESM(require("path"), 1);
5787
+ var import_node_path44 = __toESM(require("path"), 1);
5617
5788
  var import_chokidar = __toESM(require("chokidar"), 1);
5618
5789
 
5619
5790
  // src/api.ts
5620
5791
  init_cjs_shims();
5621
5792
  var import_fastify = __toESM(require("fastify"), 1);
5622
5793
  var import_cors = __toESM(require("@fastify/cors"), 1);
5623
- var import_types25 = require("@neat.is/types");
5794
+ var import_types26 = require("@neat.is/types");
5624
5795
 
5625
5796
  // src/extend/index.ts
5626
5797
  init_cjs_shims();
5627
5798
  var import_node_fs23 = require("fs");
5628
- var import_node_path37 = __toESM(require("path"), 1);
5799
+ var import_node_path38 = __toESM(require("path"), 1);
5629
5800
  var import_node_os2 = __toESM(require("os"), 1);
5630
5801
  var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
5631
5802
 
5632
5803
  // src/installers/package-manager.ts
5633
5804
  init_cjs_shims();
5634
5805
  var import_node_fs22 = require("fs");
5635
- var import_node_path36 = __toESM(require("path"), 1);
5806
+ var import_node_path37 = __toESM(require("path"), 1);
5636
5807
  var import_node_child_process = require("child_process");
5637
5808
  var LOCKFILE_PRIORITY = [
5638
5809
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -5654,22 +5825,22 @@ async function exists2(p) {
5654
5825
  }
5655
5826
  }
5656
5827
  async function detectPackageManager(serviceDir) {
5657
- let dir = import_node_path36.default.resolve(serviceDir);
5828
+ let dir = import_node_path37.default.resolve(serviceDir);
5658
5829
  const stops = /* @__PURE__ */ new Set();
5659
5830
  for (let i = 0; i < 64; i++) {
5660
5831
  if (stops.has(dir)) break;
5661
5832
  stops.add(dir);
5662
5833
  for (const candidate of LOCKFILE_PRIORITY) {
5663
- const lockPath = import_node_path36.default.join(dir, candidate.lockfile);
5834
+ const lockPath = import_node_path37.default.join(dir, candidate.lockfile);
5664
5835
  if (await exists2(lockPath)) {
5665
5836
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
5666
5837
  }
5667
5838
  }
5668
- const parent = import_node_path36.default.dirname(dir);
5839
+ const parent = import_node_path37.default.dirname(dir);
5669
5840
  if (parent === dir) break;
5670
5841
  dir = parent;
5671
5842
  }
5672
- return { pm: "npm", cwd: import_node_path36.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
5843
+ return { pm: "npm", cwd: import_node_path37.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
5673
5844
  }
5674
5845
  async function runPackageManagerInstall(cmd) {
5675
5846
  return new Promise((resolve) => {
@@ -5718,7 +5889,7 @@ async function fileExists2(p) {
5718
5889
  }
5719
5890
  }
5720
5891
  async function readPackageJson(scanPath) {
5721
- const pkgPath = import_node_path37.default.join(scanPath, "package.json");
5892
+ const pkgPath = import_node_path38.default.join(scanPath, "package.json");
5722
5893
  const raw = await import_node_fs23.promises.readFile(pkgPath, "utf8");
5723
5894
  return JSON.parse(raw);
5724
5895
  }
@@ -5729,11 +5900,11 @@ async function findHookFiles(scanPath) {
5729
5900
  ).sort();
5730
5901
  }
5731
5902
  function extendLogPath() {
5732
- return process.env.NEAT_EXTEND_LOG ?? import_node_path37.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");
5733
5904
  }
5734
5905
  async function appendExtendLog(entry2) {
5735
5906
  const logPath = extendLogPath();
5736
- await import_node_fs23.promises.mkdir(import_node_path37.default.dirname(logPath), { recursive: true });
5907
+ await import_node_fs23.promises.mkdir(import_node_path38.default.dirname(logPath), { recursive: true });
5737
5908
  await import_node_fs23.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
5738
5909
  }
5739
5910
  function splicedContent(fileContent, snippet2) {
@@ -5792,7 +5963,7 @@ function lookupInstrumentation(library, installedVersion) {
5792
5963
  }
5793
5964
  async function describeProjectInstrumentation(ctx) {
5794
5965
  const hookFiles = await findHookFiles(ctx.scanPath);
5795
- const envNeat = await fileExists2(import_node_path37.default.join(ctx.scanPath, ".env.neat"));
5966
+ const envNeat = await fileExists2(import_node_path38.default.join(ctx.scanPath, ".env.neat"));
5796
5967
  const registryInstrPackages = new Set(
5797
5968
  (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
5798
5969
  );
@@ -5814,16 +5985,16 @@ async function applyExtension(ctx, args, options) {
5814
5985
  );
5815
5986
  }
5816
5987
  for (const file of hookFiles) {
5817
- const content = await import_node_fs23.promises.readFile(import_node_path37.default.join(ctx.scanPath, file), "utf8");
5988
+ const content = await import_node_fs23.promises.readFile(import_node_path38.default.join(ctx.scanPath, file), "utf8");
5818
5989
  if (content.includes(args.registration_snippet)) {
5819
5990
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
5820
5991
  }
5821
5992
  }
5822
5993
  const primaryFile = hookFiles[0];
5823
- const primaryPath = import_node_path37.default.join(ctx.scanPath, primaryFile);
5994
+ const primaryPath = import_node_path38.default.join(ctx.scanPath, primaryFile);
5824
5995
  const filesTouched = [];
5825
5996
  const depsAdded = [];
5826
- const pkgPath = import_node_path37.default.join(ctx.scanPath, "package.json");
5997
+ const pkgPath = import_node_path38.default.join(ctx.scanPath, "package.json");
5827
5998
  const pkg = await readPackageJson(ctx.scanPath);
5828
5999
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
5829
6000
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
@@ -5869,7 +6040,7 @@ async function dryRunExtension(ctx, args) {
5869
6040
  };
5870
6041
  }
5871
6042
  for (const file of hookFiles) {
5872
- const content = await import_node_fs23.promises.readFile(import_node_path37.default.join(ctx.scanPath, file), "utf8");
6043
+ const content = await import_node_fs23.promises.readFile(import_node_path38.default.join(ctx.scanPath, file), "utf8");
5873
6044
  if (content.includes(args.registration_snippet)) {
5874
6045
  return {
5875
6046
  library: args.library,
@@ -5891,7 +6062,7 @@ async function dryRunExtension(ctx, args) {
5891
6062
  depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
5892
6063
  filesTouched.push("package.json");
5893
6064
  }
5894
- const hookContent = await import_node_fs23.promises.readFile(import_node_path37.default.join(ctx.scanPath, primaryFile), "utf8");
6065
+ const hookContent = await import_node_fs23.promises.readFile(import_node_path38.default.join(ctx.scanPath, primaryFile), "utf8");
5895
6066
  const patched = splicedContent(hookContent, args.registration_snippet);
5896
6067
  if (patched) {
5897
6068
  filesTouched.push(primaryFile);
@@ -5912,7 +6083,7 @@ async function rollbackExtension(ctx, args) {
5912
6083
  if (!match) {
5913
6084
  return { undone: false, message: "no apply found for library" };
5914
6085
  }
5915
- const pkgPath = import_node_path37.default.join(ctx.scanPath, "package.json");
6086
+ const pkgPath = import_node_path38.default.join(ctx.scanPath, "package.json");
5916
6087
  if (await fileExists2(pkgPath)) {
5917
6088
  const pkg = await readPackageJson(ctx.scanPath);
5918
6089
  if (pkg.dependencies?.[match.instrumentation_package]) {
@@ -5923,7 +6094,7 @@ async function rollbackExtension(ctx, args) {
5923
6094
  }
5924
6095
  const hookFiles = await findHookFiles(ctx.scanPath);
5925
6096
  for (const file of hookFiles) {
5926
- const filePath = import_node_path37.default.join(ctx.scanPath, file);
6097
+ const filePath = import_node_path38.default.join(ctx.scanPath, file);
5927
6098
  const content = await import_node_fs23.promises.readFile(filePath, "utf8");
5928
6099
  if (content.includes(match.registration_snippet)) {
5929
6100
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
@@ -6016,23 +6187,23 @@ function canonicalJson(value) {
6016
6187
 
6017
6188
  // src/projects.ts
6018
6189
  init_cjs_shims();
6019
- var import_node_path38 = __toESM(require("path"), 1);
6190
+ var import_node_path39 = __toESM(require("path"), 1);
6020
6191
  function pathsForProject(project, baseDir) {
6021
6192
  if (project === DEFAULT_PROJECT) {
6022
6193
  return {
6023
- snapshotPath: import_node_path38.default.join(baseDir, "graph.json"),
6024
- errorsPath: import_node_path38.default.join(baseDir, "errors.ndjson"),
6025
- staleEventsPath: import_node_path38.default.join(baseDir, "stale-events.ndjson"),
6026
- embeddingsCachePath: import_node_path38.default.join(baseDir, "embeddings.json"),
6027
- policyViolationsPath: import_node_path38.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")
6028
6199
  };
6029
6200
  }
6030
6201
  return {
6031
- snapshotPath: import_node_path38.default.join(baseDir, `${project}.json`),
6032
- errorsPath: import_node_path38.default.join(baseDir, `errors.${project}.ndjson`),
6033
- staleEventsPath: import_node_path38.default.join(baseDir, `stale-events.${project}.ndjson`),
6034
- embeddingsCachePath: import_node_path38.default.join(baseDir, `embeddings.${project}.json`),
6035
- policyViolationsPath: import_node_path38.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`)
6036
6207
  };
6037
6208
  }
6038
6209
  var Projects = class {
@@ -6070,23 +6241,23 @@ var Projects = class {
6070
6241
  init_cjs_shims();
6071
6242
  var import_node_fs25 = require("fs");
6072
6243
  var import_node_os3 = __toESM(require("os"), 1);
6073
- var import_node_path39 = __toESM(require("path"), 1);
6074
- var import_types24 = require("@neat.is/types");
6244
+ var import_node_path40 = __toESM(require("path"), 1);
6245
+ var import_types25 = require("@neat.is/types");
6075
6246
  var LOCK_TIMEOUT_MS = 5e3;
6076
6247
  var LOCK_RETRY_MS = 50;
6077
6248
  function neatHome() {
6078
6249
  const override = process.env.NEAT_HOME;
6079
- if (override && override.length > 0) return import_node_path39.default.resolve(override);
6080
- return import_node_path39.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");
6081
6252
  }
6082
6253
  function registryPath() {
6083
- return import_node_path39.default.join(neatHome(), "projects.json");
6254
+ return import_node_path40.default.join(neatHome(), "projects.json");
6084
6255
  }
6085
6256
  function registryLockPath() {
6086
- return import_node_path39.default.join(neatHome(), "projects.json.lock");
6257
+ return import_node_path40.default.join(neatHome(), "projects.json.lock");
6087
6258
  }
6088
6259
  function daemonPidPath() {
6089
- return import_node_path39.default.join(neatHome(), "neatd.pid");
6260
+ return import_node_path40.default.join(neatHome(), "neatd.pid");
6090
6261
  }
6091
6262
  function isPidAliveDefault(pid) {
6092
6263
  try {
@@ -6146,7 +6317,7 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
6146
6317
  }
6147
6318
  }
6148
6319
  async function normalizeProjectPath(input) {
6149
- const resolved = import_node_path39.default.resolve(input);
6320
+ const resolved = import_node_path40.default.resolve(input);
6150
6321
  try {
6151
6322
  return await import_node_fs25.promises.realpath(resolved);
6152
6323
  } catch {
@@ -6154,7 +6325,7 @@ async function normalizeProjectPath(input) {
6154
6325
  }
6155
6326
  }
6156
6327
  async function writeAtomically(target, contents) {
6157
- await import_node_fs25.promises.mkdir(import_node_path39.default.dirname(target), { recursive: true });
6328
+ await import_node_fs25.promises.mkdir(import_node_path40.default.dirname(target), { recursive: true });
6158
6329
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
6159
6330
  const fd = await import_node_fs25.promises.open(tmp, "w");
6160
6331
  try {
@@ -6167,7 +6338,7 @@ async function writeAtomically(target, contents) {
6167
6338
  }
6168
6339
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
6169
6340
  const deadline = Date.now() + timeoutMs;
6170
- await import_node_fs25.promises.mkdir(import_node_path39.default.dirname(lockPath), { recursive: true });
6341
+ await import_node_fs25.promises.mkdir(import_node_path40.default.dirname(lockPath), { recursive: true });
6171
6342
  let probedHolder = false;
6172
6343
  while (true) {
6173
6344
  try {
@@ -6220,10 +6391,10 @@ async function readRegistry() {
6220
6391
  throw err;
6221
6392
  }
6222
6393
  const parsed = JSON.parse(raw);
6223
- return import_types24.RegistryFileSchema.parse(parsed);
6394
+ return import_types25.RegistryFileSchema.parse(parsed);
6224
6395
  }
6225
6396
  async function writeRegistry(reg) {
6226
- const validated = import_types24.RegistryFileSchema.parse(reg);
6397
+ const validated = import_types25.RegistryFileSchema.parse(reg);
6227
6398
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
6228
6399
  }
6229
6400
  var ProjectNameCollisionError = class extends Error {
@@ -6294,6 +6465,58 @@ async function removeProject(name) {
6294
6465
  return removed;
6295
6466
  });
6296
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
+ }
6297
6520
 
6298
6521
  // src/streaming.ts
6299
6522
  init_cjs_shims();
@@ -6307,6 +6530,7 @@ function handleSse(req, reply, opts) {
6307
6530
  reply.raw.setHeader("Connection", "keep-alive");
6308
6531
  reply.raw.setHeader("X-Accel-Buffering", "no");
6309
6532
  reply.raw.flushHeaders?.();
6533
+ reply.raw.write(":open\n\n");
6310
6534
  let pending = 0;
6311
6535
  let dropped = false;
6312
6536
  const closeConnection = () => {
@@ -6486,11 +6710,11 @@ function registerRoutes(scope, ctx) {
6486
6710
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
6487
6711
  const parsed = [];
6488
6712
  for (const c of candidates) {
6489
- const r = import_types25.DivergenceTypeSchema.safeParse(c);
6713
+ const r = import_types26.DivergenceTypeSchema.safeParse(c);
6490
6714
  if (!r.success) {
6491
6715
  return reply.code(400).send({
6492
6716
  error: `unknown divergence type "${c}"`,
6493
- allowed: import_types25.DivergenceTypeSchema.options
6717
+ allowed: import_types26.DivergenceTypeSchema.options
6494
6718
  });
6495
6719
  }
6496
6720
  parsed.push(r.data);
@@ -6703,7 +6927,7 @@ function registerRoutes(scope, ctx) {
6703
6927
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
6704
6928
  let violations = await log.readAll();
6705
6929
  if (req.query.severity) {
6706
- const sev = import_types25.PolicySeveritySchema.safeParse(req.query.severity);
6930
+ const sev = import_types26.PolicySeveritySchema.safeParse(req.query.severity);
6707
6931
  if (!sev.success) {
6708
6932
  return reply.code(400).send({
6709
6933
  error: "invalid severity",
@@ -6720,7 +6944,7 @@ function registerRoutes(scope, ctx) {
6720
6944
  scope.post("/policies/check", async (req, reply) => {
6721
6945
  const proj = resolveProject(registry, req, reply, ctx.bootstrap);
6722
6946
  if (!proj) return;
6723
- const parsed = import_types25.PoliciesCheckBodySchema.safeParse(req.body ?? {});
6947
+ const parsed = import_types26.PoliciesCheckBodySchema.safeParse(req.body ?? {});
6724
6948
  if (!parsed.success) {
6725
6949
  return reply.code(400).send({
6726
6950
  error: "invalid /policies/check body",
@@ -6964,7 +7188,7 @@ init_otel_grpc();
6964
7188
  // src/search.ts
6965
7189
  init_cjs_shims();
6966
7190
  var import_node_fs26 = require("fs");
6967
- var import_node_path42 = __toESM(require("path"), 1);
7191
+ var import_node_path43 = __toESM(require("path"), 1);
6968
7192
  var import_node_crypto3 = require("crypto");
6969
7193
  var DEFAULT_LIMIT = 10;
6970
7194
  var NOMIC_DIM = 768;
@@ -7103,7 +7327,7 @@ async function readCache(cachePath) {
7103
7327
  }
7104
7328
  }
7105
7329
  async function writeCache(cachePath, cache) {
7106
- await import_node_fs26.promises.mkdir(import_node_path42.default.dirname(cachePath), { recursive: true });
7330
+ await import_node_fs26.promises.mkdir(import_node_path43.default.dirname(cachePath), { recursive: true });
7107
7331
  await import_node_fs26.promises.writeFile(cachePath, JSON.stringify(cache));
7108
7332
  }
7109
7333
  var VectorIndex = class {
@@ -7260,8 +7484,8 @@ var ALL_PHASES = [
7260
7484
  ];
7261
7485
  function classifyChange(relPath) {
7262
7486
  const phases = /* @__PURE__ */ new Set();
7263
- const base = import_node_path43.default.basename(relPath).toLowerCase();
7264
- const segments = relPath.split(import_node_path43.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());
7265
7489
  if (base === "package.json" || base === "requirements.txt" || base === "pyproject.toml" || base === "setup.py") {
7266
7490
  phases.add("services");
7267
7491
  phases.add("aliases");
@@ -7382,9 +7606,9 @@ function countWatchableDirs(scanPath, limit) {
7382
7606
  for (const e of entries) {
7383
7607
  if (count >= limit) return;
7384
7608
  if (!e.isDirectory()) continue;
7385
- if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path43.default.join(dir, e.name) + import_node_path43.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;
7386
7610
  count++;
7387
- if (depth < 2) visit(import_node_path43.default.join(dir, e.name), depth + 1);
7611
+ if (depth < 2) visit(import_node_path44.default.join(dir, e.name), depth + 1);
7388
7612
  }
7389
7613
  };
7390
7614
  visit(scanPath, 0);
@@ -7402,8 +7626,8 @@ async function startWatch(graph, opts) {
7402
7626
  const projectName = opts.project ?? DEFAULT_PROJECT;
7403
7627
  await loadGraphFromDisk(graph, opts.outPath);
7404
7628
  const detachEventBus = attachGraphToEventBus(graph, { project: projectName });
7405
- const policyFilePath = import_node_path43.default.join(opts.scanPath, "policy.json");
7406
- const policyViolationsPath = import_node_path43.default.join(import_node_path43.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");
7407
7631
  let policies = [];
7408
7632
  try {
7409
7633
  policies = await loadPolicyFile(policyFilePath);
@@ -7454,7 +7678,7 @@ async function startWatch(graph, opts) {
7454
7678
  assertBindAuthority(host, auth.authToken);
7455
7679
  const port = opts.port ?? 8080;
7456
7680
  const otelPort = opts.otelPort ?? 4318;
7457
- const cachePath = opts.embeddingsCachePath ?? import_node_path43.default.join(import_node_path43.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");
7458
7682
  let searchIndex;
7459
7683
  try {
7460
7684
  searchIndex = await buildSearchIndex(graph, { cachePath });
@@ -7472,7 +7696,7 @@ async function startWatch(graph, opts) {
7472
7696
  // Paths are derived from the explicit options the watch caller passes
7473
7697
  // — pathsForProject is only used to fill in the embeddings/snapshot
7474
7698
  // fields so the registry shape is complete.
7475
- ...pathsForProject(projectName, import_node_path43.default.dirname(opts.outPath)),
7699
+ ...pathsForProject(projectName, import_node_path44.default.dirname(opts.outPath)),
7476
7700
  snapshotPath: opts.outPath,
7477
7701
  errorsPath: opts.errorsPath,
7478
7702
  staleEventsPath: opts.staleEventsPath
@@ -7559,9 +7783,9 @@ async function startWatch(graph, opts) {
7559
7783
  };
7560
7784
  const onPath = (absPath) => {
7561
7785
  if (shouldIgnore(absPath)) return;
7562
- const rel = import_node_path43.default.relative(opts.scanPath, absPath);
7786
+ const rel = import_node_path44.default.relative(opts.scanPath, absPath);
7563
7787
  if (!rel || rel.startsWith("..")) return;
7564
- pendingPaths.add(rel.split(import_node_path43.default.sep).join("/"));
7788
+ pendingPaths.add(rel.split(import_node_path44.default.sep).join("/"));
7565
7789
  const phases = classifyChange(rel);
7566
7790
  if (phases.size === 0) {
7567
7791
  for (const p of ALL_PHASES) pending.add(p);
@@ -7616,7 +7840,7 @@ async function startWatch(graph, opts) {
7616
7840
  // src/deploy/detect.ts
7617
7841
  init_cjs_shims();
7618
7842
  var import_node_fs28 = require("fs");
7619
- var import_node_path44 = __toESM(require("path"), 1);
7843
+ var import_node_path45 = __toESM(require("path"), 1);
7620
7844
  var import_node_child_process2 = require("child_process");
7621
7845
  var import_node_crypto4 = require("crypto");
7622
7846
  function generateToken() {
@@ -7716,7 +7940,7 @@ async function runDeploy(opts = {}) {
7716
7940
  const token = generateToken();
7717
7941
  switch (substrate) {
7718
7942
  case "docker-compose": {
7719
- const artifactPath = import_node_path44.default.join(cwd, "docker-compose.neat.yml");
7943
+ const artifactPath = import_node_path45.default.join(cwd, "docker-compose.neat.yml");
7720
7944
  const contents = emitDockerCompose(cwd);
7721
7945
  await import_node_fs28.promises.writeFile(artifactPath, contents, "utf8");
7722
7946
  return {
@@ -7724,11 +7948,11 @@ async function runDeploy(opts = {}) {
7724
7948
  artifactPath,
7725
7949
  token,
7726
7950
  contents,
7727
- startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path44.default.basename(artifactPath)} up -d`
7951
+ startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path45.default.basename(artifactPath)} up -d`
7728
7952
  };
7729
7953
  }
7730
7954
  case "systemd": {
7731
- const artifactPath = import_node_path44.default.join(cwd, "neat.service");
7955
+ const artifactPath = import_node_path45.default.join(cwd, "neat.service");
7732
7956
  const contents = emitSystemdUnit(cwd);
7733
7957
  await import_node_fs28.promises.writeFile(artifactPath, contents, "utf8");
7734
7958
  return {
@@ -7764,7 +7988,7 @@ init_cjs_shims();
7764
7988
  // src/installers/javascript.ts
7765
7989
  init_cjs_shims();
7766
7990
  var import_node_fs29 = require("fs");
7767
- var import_node_path45 = __toESM(require("path"), 1);
7991
+ var import_node_path46 = __toESM(require("path"), 1);
7768
7992
  var import_semver2 = __toESM(require("semver"), 1);
7769
7993
 
7770
7994
  // src/installers/templates.ts
@@ -8311,11 +8535,11 @@ var OTEL_ENV = {
8311
8535
  value: "http://localhost:4318/projects/<project>/v1/traces"
8312
8536
  };
8313
8537
  function serviceNodeName(pkg, serviceDir) {
8314
- return pkg.name ?? import_node_path45.default.basename(serviceDir);
8538
+ return pkg.name ?? import_node_path46.default.basename(serviceDir);
8315
8539
  }
8316
8540
  function projectToken(pkg, serviceDir, project) {
8317
8541
  if (project && project.length > 0) return project;
8318
- return pkg.name ?? import_node_path45.default.basename(serviceDir);
8542
+ return pkg.name ?? import_node_path46.default.basename(serviceDir);
8319
8543
  }
8320
8544
  async function readJsonFile(p) {
8321
8545
  try {
@@ -8328,16 +8552,16 @@ async function readJsonFile(p) {
8328
8552
  async function detectRuntimeKind(pkgRoot, pkg) {
8329
8553
  const deps = allDeps(pkg);
8330
8554
  if ("react-native" in deps || "expo" in deps) return "react-native";
8331
- const appJson = await readJsonFile(import_node_path45.default.join(pkgRoot, "app.json"));
8555
+ const appJson = await readJsonFile(import_node_path46.default.join(pkgRoot, "app.json"));
8332
8556
  if (appJson && typeof appJson === "object" && "expo" in appJson) {
8333
8557
  return "react-native";
8334
8558
  }
8335
- if (await exists3(import_node_path45.default.join(pkgRoot, "vite.config.js")) || await exists3(import_node_path45.default.join(pkgRoot, "vite.config.ts")) || await exists3(import_node_path45.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) {
8336
8560
  return "browser-bundle";
8337
8561
  }
8338
- if (await exists3(import_node_path45.default.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
8339
- if (await exists3(import_node_path45.default.join(pkgRoot, "bun.lockb"))) return "bun";
8340
- if (await exists3(import_node_path45.default.join(pkgRoot, "deno.json")) || await exists3(import_node_path45.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"))) {
8341
8565
  return "deno";
8342
8566
  }
8343
8567
  const engines = pkg.engines ?? {};
@@ -8346,7 +8570,7 @@ async function detectRuntimeKind(pkgRoot, pkg) {
8346
8570
  }
8347
8571
  async function readPackageJson2(serviceDir) {
8348
8572
  try {
8349
- const raw = await import_node_fs29.promises.readFile(import_node_path45.default.join(serviceDir, "package.json"), "utf8");
8573
+ const raw = await import_node_fs29.promises.readFile(import_node_path46.default.join(serviceDir, "package.json"), "utf8");
8350
8574
  return JSON.parse(raw);
8351
8575
  } catch {
8352
8576
  return null;
@@ -8390,7 +8614,7 @@ function needsVersionUpgrade(installed, expected) {
8390
8614
  var NEXT_CONFIG_CANDIDATES = ["next.config.js", "next.config.ts", "next.config.mjs"];
8391
8615
  async function findNextConfig(serviceDir) {
8392
8616
  for (const name of NEXT_CONFIG_CANDIDATES) {
8393
- const candidate = import_node_path45.default.join(serviceDir, name);
8617
+ const candidate = import_node_path46.default.join(serviceDir, name);
8394
8618
  if (await exists3(candidate)) return candidate;
8395
8619
  }
8396
8620
  return null;
@@ -8417,7 +8641,7 @@ function hasRemixDependency(pkg) {
8417
8641
  }
8418
8642
  async function findRemixEntry(serviceDir) {
8419
8643
  for (const rel of REMIX_ENTRY_CANDIDATES) {
8420
- const candidate = import_node_path45.default.join(serviceDir, rel);
8644
+ const candidate = import_node_path46.default.join(serviceDir, rel);
8421
8645
  if (await exists3(candidate)) return candidate;
8422
8646
  }
8423
8647
  return null;
@@ -8429,14 +8653,14 @@ function hasSvelteKitDependency(pkg) {
8429
8653
  }
8430
8654
  async function findSvelteKitHooks(serviceDir) {
8431
8655
  for (const rel of SVELTEKIT_HOOKS_CANDIDATES) {
8432
- const candidate = import_node_path45.default.join(serviceDir, rel);
8656
+ const candidate = import_node_path46.default.join(serviceDir, rel);
8433
8657
  if (await exists3(candidate)) return candidate;
8434
8658
  }
8435
8659
  return null;
8436
8660
  }
8437
8661
  async function findSvelteKitConfig(serviceDir) {
8438
8662
  for (const rel of SVELTEKIT_CONFIG_CANDIDATES) {
8439
- const candidate = import_node_path45.default.join(serviceDir, rel);
8663
+ const candidate = import_node_path46.default.join(serviceDir, rel);
8440
8664
  if (await exists3(candidate)) return candidate;
8441
8665
  }
8442
8666
  return null;
@@ -8447,7 +8671,7 @@ function hasNuxtDependency(pkg) {
8447
8671
  }
8448
8672
  async function findNuxtConfig(serviceDir) {
8449
8673
  for (const name of NUXT_CONFIG_CANDIDATES) {
8450
- const candidate = import_node_path45.default.join(serviceDir, name);
8674
+ const candidate = import_node_path46.default.join(serviceDir, name);
8451
8675
  if (await exists3(candidate)) return candidate;
8452
8676
  }
8453
8677
  return null;
@@ -8458,7 +8682,7 @@ function hasAstroDependency(pkg) {
8458
8682
  }
8459
8683
  async function findAstroConfig(serviceDir) {
8460
8684
  for (const name of ASTRO_CONFIG_CANDIDATES) {
8461
- const candidate = import_node_path45.default.join(serviceDir, name);
8685
+ const candidate = import_node_path46.default.join(serviceDir, name);
8462
8686
  if (await exists3(candidate)) return candidate;
8463
8687
  }
8464
8688
  return null;
@@ -8472,7 +8696,7 @@ function parseNextMajor(range) {
8472
8696
  return Number.isFinite(n) ? n : null;
8473
8697
  }
8474
8698
  async function isTypeScriptProject(serviceDir) {
8475
- return exists3(import_node_path45.default.join(serviceDir, "tsconfig.json"));
8699
+ return exists3(import_node_path46.default.join(serviceDir, "tsconfig.json"));
8476
8700
  }
8477
8701
  var INDEX_EXTENSIONS = [".ts", ".tsx", ".js", ".mjs", ".cjs"];
8478
8702
  var INDEX_CANDIDATES = INDEX_EXTENSIONS.map((ext) => `index${ext}`);
@@ -8518,7 +8742,7 @@ function entryFromScript(script) {
8518
8742
  }
8519
8743
  async function resolveEntry(serviceDir, pkg) {
8520
8744
  if (typeof pkg.main === "string" && pkg.main.length > 0) {
8521
- const candidate = import_node_path45.default.resolve(serviceDir, pkg.main);
8745
+ const candidate = import_node_path46.default.resolve(serviceDir, pkg.main);
8522
8746
  if (await exists3(candidate)) return candidate;
8523
8747
  }
8524
8748
  if (pkg.bin) {
@@ -8532,36 +8756,36 @@ async function resolveEntry(serviceDir, pkg) {
8532
8756
  if (typeof first === "string") binEntry = first;
8533
8757
  }
8534
8758
  if (binEntry) {
8535
- const candidate = import_node_path45.default.resolve(serviceDir, binEntry);
8759
+ const candidate = import_node_path46.default.resolve(serviceDir, binEntry);
8536
8760
  if (await exists3(candidate)) return candidate;
8537
8761
  }
8538
8762
  }
8539
8763
  const startEntry = entryFromScript(pkg.scripts?.start);
8540
8764
  if (startEntry) {
8541
- const candidate = import_node_path45.default.resolve(serviceDir, startEntry);
8765
+ const candidate = import_node_path46.default.resolve(serviceDir, startEntry);
8542
8766
  if (await exists3(candidate)) return candidate;
8543
8767
  }
8544
8768
  const devEntry = entryFromScript(pkg.scripts?.dev);
8545
8769
  if (devEntry) {
8546
- const candidate = import_node_path45.default.resolve(serviceDir, devEntry);
8770
+ const candidate = import_node_path46.default.resolve(serviceDir, devEntry);
8547
8771
  if (await exists3(candidate)) return candidate;
8548
8772
  }
8549
8773
  for (const rel of SRC_INDEX_CANDIDATES) {
8550
- const candidate = import_node_path45.default.join(serviceDir, rel);
8774
+ const candidate = import_node_path46.default.join(serviceDir, rel);
8551
8775
  if (await exists3(candidate)) return candidate;
8552
8776
  }
8553
8777
  for (const rel of SRC_NAMED_CANDIDATES) {
8554
- const candidate = import_node_path45.default.join(serviceDir, rel);
8778
+ const candidate = import_node_path46.default.join(serviceDir, rel);
8555
8779
  if (await exists3(candidate)) return candidate;
8556
8780
  }
8557
8781
  for (const name of INDEX_CANDIDATES) {
8558
- const candidate = import_node_path45.default.join(serviceDir, name);
8782
+ const candidate = import_node_path46.default.join(serviceDir, name);
8559
8783
  if (await exists3(candidate)) return candidate;
8560
8784
  }
8561
8785
  return null;
8562
8786
  }
8563
8787
  function dispatchEntry(entryFile, pkg) {
8564
- const ext = import_node_path45.default.extname(entryFile).toLowerCase();
8788
+ const ext = import_node_path46.default.extname(entryFile).toLowerCase();
8565
8789
  if (ext === ".ts" || ext === ".tsx") return "ts";
8566
8790
  if (ext === ".mjs") return "esm";
8567
8791
  if (ext === ".cjs") return "cjs";
@@ -8578,9 +8802,9 @@ function otelInitContents(flavor) {
8578
8802
  return OTEL_INIT_CJS;
8579
8803
  }
8580
8804
  function injectionLine(flavor, entryFile, otelInitFile) {
8581
- let rel = import_node_path45.default.relative(import_node_path45.default.dirname(entryFile), otelInitFile);
8805
+ let rel = import_node_path46.default.relative(import_node_path46.default.dirname(entryFile), otelInitFile);
8582
8806
  if (!rel.startsWith(".")) rel = `./${rel}`;
8583
- rel = rel.split(import_node_path45.default.sep).join("/");
8807
+ rel = rel.split(import_node_path46.default.sep).join("/");
8584
8808
  if (flavor === "cjs") return `require('${rel}')`;
8585
8809
  if (flavor === "esm") return `import '${rel}'`;
8586
8810
  const tsRel = rel.replace(/\.ts$/, "");
@@ -8593,23 +8817,23 @@ function lineIsOtelInjection(line) {
8593
8817
  }
8594
8818
  async function detectsSrcLayout(serviceDir) {
8595
8819
  const [hasSrcApp, hasSrcPages, hasRootApp, hasRootPages] = await Promise.all([
8596
- exists3(import_node_path45.default.join(serviceDir, "src", "app")),
8597
- exists3(import_node_path45.default.join(serviceDir, "src", "pages")),
8598
- exists3(import_node_path45.default.join(serviceDir, "app")),
8599
- exists3(import_node_path45.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"))
8600
8824
  ]);
8601
8825
  return (hasSrcApp || hasSrcPages) && !hasRootApp && !hasRootPages;
8602
8826
  }
8603
8827
  async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project) {
8604
8828
  const useTs = await isTypeScriptProject(serviceDir);
8605
8829
  const srcLayout = await detectsSrcLayout(serviceDir);
8606
- const baseDir = srcLayout ? import_node_path45.default.join(serviceDir, "src") : serviceDir;
8607
- const instrumentationFile = import_node_path45.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
8608
- const instrumentationNodeFile = import_node_path45.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(
8609
8833
  baseDir,
8610
8834
  useTs ? "instrumentation.node.ts" : "instrumentation.node.js"
8611
8835
  );
8612
- const envNeatFile = import_node_path45.default.join(baseDir, ".env.neat");
8836
+ const envNeatFile = import_node_path46.default.join(baseDir, ".env.neat");
8613
8837
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
8614
8838
  const dependencyEdits = [];
8615
8839
  for (const sdk of SDK_PACKAGES) {
@@ -8714,7 +8938,7 @@ function buildDependencyEdits(pkg, manifestPath) {
8714
8938
  return edits;
8715
8939
  }
8716
8940
  async function queueEnvNeat(serviceDir, pkg, project, generatedFiles) {
8717
- const envNeatFile = import_node_path45.default.join(serviceDir, ".env.neat");
8941
+ const envNeatFile = import_node_path46.default.join(serviceDir, ".env.neat");
8718
8942
  if (!await exists3(envNeatFile)) {
8719
8943
  generatedFiles.push({
8720
8944
  file: envNeatFile,
@@ -8749,7 +8973,7 @@ function fileImportsOtelHook(raw, specifiers) {
8749
8973
  }
8750
8974
  async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
8751
8975
  const useTs = await isTypeScriptProject(serviceDir);
8752
- const otelServerFile = import_node_path45.default.join(
8976
+ const otelServerFile = import_node_path46.default.join(
8753
8977
  serviceDir,
8754
8978
  useTs ? "app/otel.server.ts" : "app/otel.server.js"
8755
8979
  );
@@ -8806,11 +9030,11 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
8806
9030
  }
8807
9031
  async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project) {
8808
9032
  const useTs = await isTypeScriptProject(serviceDir);
8809
- const otelInitFile = import_node_path45.default.join(
9033
+ const otelInitFile = import_node_path46.default.join(
8810
9034
  serviceDir,
8811
9035
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
8812
9036
  );
8813
- const resolvedHooksFile = hooksFile ?? import_node_path45.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");
8814
9038
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
8815
9039
  const generatedFiles = [];
8816
9040
  const entrypointEdits = [];
@@ -8872,11 +9096,11 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
8872
9096
  }
8873
9097
  async function planNuxt(serviceDir, pkg, manifestPath, project) {
8874
9098
  const useTs = await isTypeScriptProject(serviceDir);
8875
- const otelPluginFile = import_node_path45.default.join(
9099
+ const otelPluginFile = import_node_path46.default.join(
8876
9100
  serviceDir,
8877
9101
  useTs ? "server/plugins/otel.ts" : "server/plugins/otel.js"
8878
9102
  );
8879
- const otelInitFile = import_node_path45.default.join(
9103
+ const otelInitFile = import_node_path46.default.join(
8880
9104
  serviceDir,
8881
9105
  useTs ? "server/plugins/otel-init.ts" : "server/plugins/otel-init.js"
8882
9106
  );
@@ -8927,19 +9151,19 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
8927
9151
  var ASTRO_MIDDLEWARE_CANDIDATES = ["src/middleware.ts", "src/middleware.js"];
8928
9152
  async function findAstroMiddleware(serviceDir) {
8929
9153
  for (const rel of ASTRO_MIDDLEWARE_CANDIDATES) {
8930
- const candidate = import_node_path45.default.join(serviceDir, rel);
9154
+ const candidate = import_node_path46.default.join(serviceDir, rel);
8931
9155
  if (await exists3(candidate)) return candidate;
8932
9156
  }
8933
9157
  return null;
8934
9158
  }
8935
9159
  async function planAstro(serviceDir, pkg, manifestPath, project) {
8936
9160
  const useTs = await isTypeScriptProject(serviceDir);
8937
- const otelInitFile = import_node_path45.default.join(
9161
+ const otelInitFile = import_node_path46.default.join(
8938
9162
  serviceDir,
8939
9163
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
8940
9164
  );
8941
9165
  const existingMiddleware = await findAstroMiddleware(serviceDir);
8942
- const middlewareFile = existingMiddleware ?? import_node_path45.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");
8943
9167
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
8944
9168
  const generatedFiles = [];
8945
9169
  const entrypointEdits = [];
@@ -9035,7 +9259,7 @@ async function findFrameworkDispatch(serviceDir, pkg, manifestPath, project) {
9035
9259
  }
9036
9260
  async function plan(serviceDir, opts) {
9037
9261
  const pkg = await readPackageJson2(serviceDir);
9038
- const manifestPath = import_node_path45.default.join(serviceDir, "package.json");
9262
+ const manifestPath = import_node_path46.default.join(serviceDir, "package.json");
9039
9263
  const project = opts?.project;
9040
9264
  const empty = {
9041
9265
  language: "javascript",
@@ -9070,8 +9294,8 @@ async function plan(serviceDir, opts) {
9070
9294
  return { ...empty, libOnly: true };
9071
9295
  }
9072
9296
  const flavor = dispatchEntry(entryFile, pkg);
9073
- const otelInitFile = import_node_path45.default.join(import_node_path45.default.dirname(entryFile), otelInitFilename(flavor));
9074
- const envNeatFile = import_node_path45.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");
9075
9299
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
9076
9300
  const dependencyEdits = [];
9077
9301
  for (const sdk of SDK_PACKAGES) {
@@ -9140,13 +9364,13 @@ async function plan(serviceDir, opts) {
9140
9364
  };
9141
9365
  }
9142
9366
  function isAllowedWritePath(serviceDir, target) {
9143
- const rel = import_node_path45.default.relative(serviceDir, target);
9367
+ const rel = import_node_path46.default.relative(serviceDir, target);
9144
9368
  if (rel.startsWith("..")) return false;
9145
- const base = import_node_path45.default.basename(target);
9369
+ const base = import_node_path46.default.basename(target);
9146
9370
  if (base === "package.json") return true;
9147
9371
  if (base === ".env.neat") return true;
9148
9372
  if (/^otel-init\.(?:js|cjs|mjs|ts)$/.test(base)) return true;
9149
- const relPosix = rel.split(import_node_path45.default.sep).join("/");
9373
+ const relPosix = rel.split(import_node_path46.default.sep).join("/");
9150
9374
  if (/^instrumentation(?:\.node)?\.(?:js|cjs|mjs|ts)$/.test(base)) {
9151
9375
  if (relPosix === base) return true;
9152
9376
  if (relPosix === `src/${base}`) return true;
@@ -9163,7 +9387,7 @@ function isAllowedWritePath(serviceDir, target) {
9163
9387
  return false;
9164
9388
  }
9165
9389
  async function writeAtomic(file, contents) {
9166
- await import_node_fs29.promises.mkdir(import_node_path45.default.dirname(file), { recursive: true });
9390
+ await import_node_fs29.promises.mkdir(import_node_path46.default.dirname(file), { recursive: true });
9167
9391
  const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
9168
9392
  await import_node_fs29.promises.writeFile(tmp, contents, "utf8");
9169
9393
  await import_node_fs29.promises.rename(tmp, file);
@@ -9347,7 +9571,7 @@ async function rollback(installPlan, originals, createdFiles) {
9347
9571
  ...removed.map((f) => `removed: ${f}`),
9348
9572
  ""
9349
9573
  ];
9350
- const rollbackPath = import_node_path45.default.join(installPlan.serviceDir, "neat-rollback.patch");
9574
+ const rollbackPath = import_node_path46.default.join(installPlan.serviceDir, "neat-rollback.patch");
9351
9575
  await import_node_fs29.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
9352
9576
  }
9353
9577
  function injectInstrumentationHook(raw) {
@@ -9378,7 +9602,7 @@ var javascriptInstaller = {
9378
9602
  // src/installers/python.ts
9379
9603
  init_cjs_shims();
9380
9604
  var import_node_fs30 = require("fs");
9381
- var import_node_path46 = __toESM(require("path"), 1);
9605
+ var import_node_path47 = __toESM(require("path"), 1);
9382
9606
  var SDK_PACKAGES2 = [
9383
9607
  { name: "opentelemetry-distro", version: ">=0.49b0" },
9384
9608
  { name: "opentelemetry-exporter-otlp", version: ">=1.28.0" }
@@ -9399,7 +9623,7 @@ async function exists4(p) {
9399
9623
  async function detect2(serviceDir) {
9400
9624
  const markers = ["requirements.txt", "pyproject.toml", "setup.py"];
9401
9625
  for (const m of markers) {
9402
- if (await exists4(import_node_path46.default.join(serviceDir, m))) return true;
9626
+ if (await exists4(import_node_path47.default.join(serviceDir, m))) return true;
9403
9627
  }
9404
9628
  return false;
9405
9629
  }
@@ -9409,7 +9633,7 @@ function reqPackageName(line) {
9409
9633
  return head.replace(/[<>=!~].*$/, "").toLowerCase();
9410
9634
  }
9411
9635
  async function planRequirementsTxtEdits(serviceDir) {
9412
- const file = import_node_path46.default.join(serviceDir, "requirements.txt");
9636
+ const file = import_node_path47.default.join(serviceDir, "requirements.txt");
9413
9637
  if (!await exists4(file)) return null;
9414
9638
  const raw = await import_node_fs30.promises.readFile(file, "utf8");
9415
9639
  const presentNames = new Set(
@@ -9419,7 +9643,7 @@ async function planRequirementsTxtEdits(serviceDir) {
9419
9643
  return { manifest: file, missing: [...missing] };
9420
9644
  }
9421
9645
  async function planProcfileEdits(serviceDir) {
9422
- const procfile = import_node_path46.default.join(serviceDir, "Procfile");
9646
+ const procfile = import_node_path47.default.join(serviceDir, "Procfile");
9423
9647
  if (!await exists4(procfile)) return [];
9424
9648
  const raw = await import_node_fs30.promises.readFile(procfile, "utf8");
9425
9649
  const edits = [];
@@ -9508,7 +9732,7 @@ async function apply2(installPlan) {
9508
9732
  if (raw === void 0) {
9509
9733
  throw new Error(`python installer: cannot read ${file} during apply`);
9510
9734
  }
9511
- const base = import_node_path46.default.basename(file);
9735
+ const base = import_node_path47.default.basename(file);
9512
9736
  if (base === "requirements.txt") {
9513
9737
  const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
9514
9738
  if (edits.length > 0) {
@@ -9547,7 +9771,7 @@ async function rollback2(installPlan, originals) {
9547
9771
  ...restored.map((f) => `restored: ${f}`),
9548
9772
  ""
9549
9773
  ];
9550
- const rollbackPath = import_node_path46.default.join(installPlan.serviceDir, "neat-rollback.patch");
9774
+ const rollbackPath = import_node_path47.default.join(installPlan.serviceDir, "neat-rollback.patch");
9551
9775
  await import_node_fs30.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
9552
9776
  }
9553
9777
  var pythonInstaller = {
@@ -9674,7 +9898,7 @@ init_cjs_shims();
9674
9898
  var import_node_fs31 = require("fs");
9675
9899
  var import_node_http = __toESM(require("http"), 1);
9676
9900
  var import_node_net = __toESM(require("net"), 1);
9677
- var import_node_path47 = __toESM(require("path"), 1);
9901
+ var import_node_path48 = __toESM(require("path"), 1);
9678
9902
  var import_node_child_process3 = require("child_process");
9679
9903
  var import_node_readline = __toESM(require("readline"), 1);
9680
9904
  async function extractAndPersist(opts) {
@@ -9683,7 +9907,7 @@ async function extractAndPersist(opts) {
9683
9907
  const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
9684
9908
  resetGraph(graphKey);
9685
9909
  const graph = getGraph(graphKey);
9686
- const projectPaths = pathsForProject(graphKey, import_node_path47.default.join(opts.scanPath, "neat-out"));
9910
+ const projectPaths = pathsForProject(graphKey, import_node_path48.default.join(opts.scanPath, "neat-out"));
9687
9911
  const extraction = await extractFromDirectory(graph, opts.scanPath, {
9688
9912
  errorsPath: projectPaths.errorsPath
9689
9913
  });
@@ -9737,7 +9961,7 @@ async function applyInstallersOver(services, project, options = {}) {
9737
9961
  console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
9738
9962
  } else if (outcome.outcome === "react-native") {
9739
9963
  reactNative++;
9740
- const svcName = import_node_path47.default.basename(svc.dir);
9964
+ const svcName = import_node_path48.default.basename(svc.dir);
9741
9965
  console.log(
9742
9966
  `neat: ${svc.dir} detected as React Native / Expo
9743
9967
  The installer doesn't cover this runtime deterministically.
@@ -9748,7 +9972,7 @@ async function applyInstallersOver(services, project, options = {}) {
9748
9972
  );
9749
9973
  } else if (outcome.outcome === "bun") {
9750
9974
  bun++;
9751
- const svcName = import_node_path47.default.basename(svc.dir);
9975
+ const svcName = import_node_path48.default.basename(svc.dir);
9752
9976
  console.log(
9753
9977
  `neat: ${svc.dir} detected as Bun
9754
9978
  The installer doesn't cover this runtime deterministically.
@@ -9759,7 +9983,7 @@ async function applyInstallersOver(services, project, options = {}) {
9759
9983
  );
9760
9984
  } else if (outcome.outcome === "deno") {
9761
9985
  deno++;
9762
- const svcName = import_node_path47.default.basename(svc.dir);
9986
+ const svcName = import_node_path48.default.basename(svc.dir);
9763
9987
  console.log(
9764
9988
  `neat: ${svc.dir} detected as Deno
9765
9989
  The installer doesn't cover this runtime deterministically.
@@ -9770,7 +9994,7 @@ async function applyInstallersOver(services, project, options = {}) {
9770
9994
  );
9771
9995
  } else if (outcome.outcome === "cloudflare-workers") {
9772
9996
  cloudflareWorkers++;
9773
- const svcName = import_node_path47.default.basename(svc.dir);
9997
+ const svcName = import_node_path48.default.basename(svc.dir);
9774
9998
  console.log(
9775
9999
  `neat: ${svc.dir} detected as Cloudflare Workers
9776
10000
  The installer doesn't cover this runtime deterministically.
@@ -9781,7 +10005,7 @@ async function applyInstallersOver(services, project, options = {}) {
9781
10005
  );
9782
10006
  } else if (outcome.outcome === "electron") {
9783
10007
  electron++;
9784
- const svcName = import_node_path47.default.basename(svc.dir);
10008
+ const svcName = import_node_path48.default.basename(svc.dir);
9785
10009
  console.log(
9786
10010
  `neat: ${svc.dir} detected as Electron
9787
10011
  The installer doesn't cover this runtime deterministically.
@@ -9955,10 +10179,10 @@ function formatPortCollisionMessage(port) {
9955
10179
  ];
9956
10180
  }
9957
10181
  function spawnDaemonDetached() {
9958
- const here = import_node_path47.default.dirname(new URL(importMetaUrl).pathname);
10182
+ const here = import_node_path48.default.dirname(new URL(importMetaUrl).pathname);
9959
10183
  const candidates = [
9960
- import_node_path47.default.join(here, "neatd.cjs"),
9961
- import_node_path47.default.join(here, "neatd.js")
10184
+ import_node_path48.default.join(here, "neatd.cjs"),
10185
+ import_node_path48.default.join(here, "neatd.js")
9962
10186
  ];
9963
10187
  let entry2 = null;
9964
10188
  const fsSync = require("fs");
@@ -10173,11 +10397,11 @@ function printSummary(result, graph, dashboardUrl) {
10173
10397
 
10174
10398
  // src/cli-verbs.ts
10175
10399
  init_cjs_shims();
10176
- var import_node_path48 = __toESM(require("path"), 1);
10400
+ var import_node_path49 = __toESM(require("path"), 1);
10177
10401
 
10178
10402
  // src/cli-client.ts
10179
10403
  init_cjs_shims();
10180
- var import_types26 = require("@neat.is/types");
10404
+ var import_types27 = require("@neat.is/types");
10181
10405
  var HttpError = class extends Error {
10182
10406
  constructor(status2, message, responseBody = "") {
10183
10407
  super(message);
@@ -10202,10 +10426,10 @@ function createHttpClient(baseUrl, bearerToken) {
10202
10426
  const root = baseUrl.replace(/\/$/, "");
10203
10427
  const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
10204
10428
  return {
10205
- async get(path50) {
10429
+ async get(path51) {
10206
10430
  let res;
10207
10431
  try {
10208
- res = await fetch(`${root}${path50}`, {
10432
+ res = await fetch(`${root}${path51}`, {
10209
10433
  headers: { ...authHeader }
10210
10434
  });
10211
10435
  } catch (err) {
@@ -10217,16 +10441,16 @@ function createHttpClient(baseUrl, bearerToken) {
10217
10441
  const body = await res.text().catch(() => "");
10218
10442
  throw new HttpError(
10219
10443
  res.status,
10220
- `${res.status} ${res.statusText} on GET ${path50}: ${body}`,
10444
+ `${res.status} ${res.statusText} on GET ${path51}: ${body}`,
10221
10445
  body
10222
10446
  );
10223
10447
  }
10224
10448
  return await res.json();
10225
10449
  },
10226
- async post(path50, body) {
10450
+ async post(path51, body) {
10227
10451
  let res;
10228
10452
  try {
10229
- res = await fetch(`${root}${path50}`, {
10453
+ res = await fetch(`${root}${path51}`, {
10230
10454
  method: "POST",
10231
10455
  headers: { "content-type": "application/json", ...authHeader },
10232
10456
  body: JSON.stringify(body)
@@ -10240,7 +10464,7 @@ function createHttpClient(baseUrl, bearerToken) {
10240
10464
  const text = await res.text().catch(() => "");
10241
10465
  throw new HttpError(
10242
10466
  res.status,
10243
- `${res.status} ${res.statusText} on POST ${path50}: ${text}`,
10467
+ `${res.status} ${res.statusText} on POST ${path51}: ${text}`,
10244
10468
  text
10245
10469
  );
10246
10470
  }
@@ -10254,12 +10478,12 @@ function projectPath(project, suffix) {
10254
10478
  }
10255
10479
  async function runRootCause(client, input) {
10256
10480
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
10257
- const path50 = projectPath(
10481
+ const path51 = projectPath(
10258
10482
  input.project,
10259
10483
  `/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
10260
10484
  );
10261
10485
  try {
10262
- const result = await client.get(path50);
10486
+ const result = await client.get(path51);
10263
10487
  const arrowPath = result.traversalPath.join(" \u2190 ");
10264
10488
  const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
10265
10489
  const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
@@ -10285,12 +10509,12 @@ async function runRootCause(client, input) {
10285
10509
  }
10286
10510
  async function runBlastRadius(client, input) {
10287
10511
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
10288
- const path50 = projectPath(
10512
+ const path51 = projectPath(
10289
10513
  input.project,
10290
10514
  `/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
10291
10515
  );
10292
10516
  try {
10293
- const result = await client.get(path50);
10517
+ const result = await client.get(path51);
10294
10518
  if (result.totalAffected === 0) {
10295
10519
  return {
10296
10520
  summary: `${result.origin} has no downstream dependencies. Nothing else would break if it failed.`
@@ -10319,17 +10543,17 @@ async function runBlastRadius(client, input) {
10319
10543
  }
10320
10544
  }
10321
10545
  function formatBlastEntry(n) {
10322
- 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]" : "";
10323
10547
  return ` \u2022 ${n.nodeId} (distance ${n.distance}, ${n.edgeProvenance})${tag}`;
10324
10548
  }
10325
10549
  async function runDependencies(client, input) {
10326
10550
  const depth = input.depth ?? 3;
10327
- const path50 = projectPath(
10551
+ const path51 = projectPath(
10328
10552
  input.project,
10329
10553
  `/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
10330
10554
  );
10331
10555
  try {
10332
- const result = await client.get(path50);
10556
+ const result = await client.get(path51);
10333
10557
  if (result.total === 0) {
10334
10558
  return {
10335
10559
  summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
@@ -10365,9 +10589,9 @@ async function runObservedDependencies(client, input) {
10365
10589
  const edges = await client.get(
10366
10590
  projectPath(input.project, `/graph/edges/${encodeURIComponent(input.nodeId)}`)
10367
10591
  );
10368
- 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);
10369
10593
  if (observed.length === 0) {
10370
- 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);
10371
10595
  const note = hasExtracted ? " Static (EXTRACTED) dependencies exist but no runtime traffic has been seen \u2014 is OTel running?" : "";
10372
10596
  return { summary: `No OBSERVED dependencies for ${input.nodeId}.${note}` };
10373
10597
  }
@@ -10375,7 +10599,7 @@ async function runObservedDependencies(client, input) {
10375
10599
  return {
10376
10600
  summary: `${input.nodeId} has ${observed.length} runtime dependenc${observed.length === 1 ? "y" : "ies"} confirmed by OTel.`,
10377
10601
  block: blockLines.join("\n"),
10378
- provenance: import_types26.Provenance.OBSERVED
10602
+ provenance: import_types27.Provenance.OBSERVED
10379
10603
  };
10380
10604
  } catch (err) {
10381
10605
  if (err instanceof HttpError && err.status === 404) {
@@ -10410,9 +10634,9 @@ function formatDuration(ms) {
10410
10634
  return `${Math.round(h / 24)}d`;
10411
10635
  }
10412
10636
  async function runIncidents(client, input) {
10413
- const path50 = 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");
10414
10638
  try {
10415
- const body = await client.get(path50);
10639
+ const body = await client.get(path51);
10416
10640
  const events = body.events;
10417
10641
  if (events.length === 0) {
10418
10642
  return {
@@ -10429,7 +10653,7 @@ async function runIncidents(client, input) {
10429
10653
  return {
10430
10654
  summary: `${target} has ${body.total} recorded incident${body.total === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
10431
10655
  block: blockLines.join("\n"),
10432
- provenance: import_types26.Provenance.OBSERVED
10656
+ provenance: import_types27.Provenance.OBSERVED
10433
10657
  };
10434
10658
  } catch (err) {
10435
10659
  if (err instanceof HttpError && err.status === 404) {
@@ -10538,7 +10762,7 @@ async function runStaleEdges(client, input) {
10538
10762
  return {
10539
10763
  summary: `${events.length} stale-edge transition${events.length === 1 ? "" : "s"} recorded${input.edgeType ? ` for ${input.edgeType}` : ""}.`,
10540
10764
  block: blockLines.join("\n"),
10541
- provenance: import_types26.Provenance.STALE
10765
+ provenance: import_types27.Provenance.STALE
10542
10766
  };
10543
10767
  }
10544
10768
  async function runPolicies(client, input) {
@@ -10702,7 +10926,7 @@ async function resolveProjectEntry(opts) {
10702
10926
  const cwd = opts.cwd ?? process.cwd();
10703
10927
  const resolvedCwd = await normalizeProjectPath(cwd);
10704
10928
  for (const entry2 of entries) {
10705
- if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path48.default.sep}`)) {
10929
+ if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path49.default.sep}`)) {
10706
10930
  return entry2;
10707
10931
  }
10708
10932
  }
@@ -10852,7 +11076,7 @@ async function runSync(opts) {
10852
11076
  }
10853
11077
 
10854
11078
  // src/cli.ts
10855
- var import_types27 = require("@neat.is/types");
11079
+ var import_types28 = require("@neat.is/types");
10856
11080
  function isNpxInvocation() {
10857
11081
  if (process.env.npm_command === "exec") return true;
10858
11082
  const execpath = process.env.npm_execpath ?? "";
@@ -10889,6 +11113,8 @@ function usage() {
10889
11113
  console.log(" uninstall <name>");
10890
11114
  console.log(" Remove a project from the registry. Does not touch");
10891
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");
10892
11118
  console.log(" version Print the installed @neat.is/core version and exit.");
10893
11119
  console.log(" Aliases: --version, -v.");
10894
11120
  console.log(" skill Install or print the Claude Code MCP drop-in.");
@@ -10948,6 +11174,7 @@ function usage() {
10948
11174
  console.log("");
10949
11175
  console.log("environment:");
10950
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)");
10951
11178
  console.log(" NEAT_PROJECT project name when --project isn't passed");
10952
11179
  }
10953
11180
  var STRING_FLAGS = [
@@ -11128,12 +11355,12 @@ async function runInit(opts) {
11128
11355
  printDiscoveryReport(opts, services);
11129
11356
  const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
11130
11357
  const patch = renderPatch(sections);
11131
- const patchPath = import_node_path49.default.join(opts.scanPath, "neat.patch");
11358
+ const patchPath = import_node_path50.default.join(opts.scanPath, "neat.patch");
11132
11359
  if (opts.dryRun) {
11133
11360
  await import_node_fs32.promises.writeFile(patchPath, patch, "utf8");
11134
11361
  written.push(patchPath);
11135
11362
  console.log(`dry-run: patch written to ${patchPath}`);
11136
- const gitignorePath = import_node_path49.default.join(opts.scanPath, ".gitignore");
11363
+ const gitignorePath = import_node_path50.default.join(opts.scanPath, ".gitignore");
11137
11364
  const gitignoreExists = await import_node_fs32.promises.stat(gitignorePath).then(() => true).catch(() => false);
11138
11365
  const verb = gitignoreExists ? "append" : "create";
11139
11366
  console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
@@ -11145,9 +11372,9 @@ async function runInit(opts) {
11145
11372
  const graph = getGraph(graphKey);
11146
11373
  const projectPaths = pathsForProject(
11147
11374
  graphKey,
11148
- import_node_path49.default.join(opts.scanPath, "neat-out")
11375
+ import_node_path50.default.join(opts.scanPath, "neat-out")
11149
11376
  );
11150
- const errorsPath = import_node_path49.default.join(import_node_path49.default.dirname(opts.outPath), import_node_path49.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));
11151
11378
  const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
11152
11379
  await saveGraphToDisk(graph, opts.outPath);
11153
11380
  written.push(opts.outPath);
@@ -11259,16 +11486,16 @@ var CLAUDE_SKILL_CONFIG = {
11259
11486
  command: "npx",
11260
11487
  args: ["-y", "@neat.is/mcp"],
11261
11488
  env: {
11262
- NEAT_API_URL: "http://localhost:8080"
11489
+ NEAT_CORE_URL: "http://localhost:8080"
11263
11490
  }
11264
11491
  }
11265
11492
  }
11266
11493
  };
11267
11494
  function claudeConfigPath() {
11268
11495
  const override = process.env.NEAT_CLAUDE_CONFIG;
11269
- if (override && override.length > 0) return import_node_path49.default.resolve(override);
11496
+ if (override && override.length > 0) return import_node_path50.default.resolve(override);
11270
11497
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
11271
- return import_node_path49.default.join(home, ".claude.json");
11498
+ return import_node_path50.default.join(home, ".claude.json");
11272
11499
  }
11273
11500
  async function runSkill(opts) {
11274
11501
  const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
@@ -11292,7 +11519,7 @@ async function runSkill(opts) {
11292
11519
  ...existing,
11293
11520
  mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
11294
11521
  };
11295
- await import_node_fs32.promises.mkdir(import_node_path49.default.dirname(target), { recursive: true });
11522
+ await import_node_fs32.promises.mkdir(import_node_path50.default.dirname(target), { recursive: true });
11296
11523
  await import_node_fs32.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
11297
11524
  console.log(`neat skill: wrote mcpServers.neat to ${target}`);
11298
11525
  console.log("restart Claude Code to pick up the new MCP server.");
@@ -11305,6 +11532,9 @@ async function runSkill(opts) {
11305
11532
  console.log("");
11306
11533
  console.log("Manual install: copy mcpServers.neat from --print-config into ~/.claude.json,");
11307
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.");
11308
11538
  return { exitCode: 0 };
11309
11539
  }
11310
11540
  async function main() {
@@ -11339,12 +11569,12 @@ async function main() {
11339
11569
  console.error("neat init: --apply and --dry-run are mutually exclusive");
11340
11570
  process.exit(2);
11341
11571
  }
11342
- const scanPath = import_node_path49.default.resolve(target);
11572
+ const scanPath = import_node_path50.default.resolve(target);
11343
11573
  const projectExplicit = parsed.project !== null;
11344
- const projectName = projectExplicit ? project : import_node_path49.default.basename(scanPath);
11574
+ const projectName = projectExplicit ? project : import_node_path50.default.basename(scanPath);
11345
11575
  const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
11346
- const fallback = pathsForProject(projectKey, import_node_path49.default.join(scanPath, "neat-out")).snapshotPath;
11347
- const outPath = import_node_path49.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);
11348
11578
  const result = await runInit({
11349
11579
  scanPath,
11350
11580
  outPath,
@@ -11365,21 +11595,21 @@ async function main() {
11365
11595
  usage();
11366
11596
  process.exit(2);
11367
11597
  }
11368
- const scanPath = import_node_path49.default.resolve(target);
11598
+ const scanPath = import_node_path50.default.resolve(target);
11369
11599
  const stat = await import_node_fs32.promises.stat(scanPath).catch(() => null);
11370
11600
  if (!stat || !stat.isDirectory()) {
11371
11601
  console.error(`neat watch: ${scanPath} is not a directory`);
11372
11602
  process.exit(2);
11373
11603
  }
11374
- const projectPaths = pathsForProject(project, import_node_path49.default.join(scanPath, "neat-out"));
11375
- const outPath = import_node_path49.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
11376
- const errorsPath = import_node_path49.default.resolve(
11377
- process.env.NEAT_ERRORS_PATH ?? import_node_path49.default.join(import_node_path49.default.dirname(outPath), import_node_path49.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))
11378
11608
  );
11379
- const staleEventsPath = import_node_path49.default.resolve(
11380
- process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path49.default.join(import_node_path49.default.dirname(outPath), import_node_path49.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))
11381
11611
  );
11382
- const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path49.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;
11383
11613
  const handle = await startWatch(getGraph(project), {
11384
11614
  scanPath,
11385
11615
  outPath,
@@ -11472,6 +11702,19 @@ async function main() {
11472
11702
  console.log("note: neat-out/, policy.json, and other files at the project path were left in place.");
11473
11703
  return;
11474
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
+ }
11475
11718
  if (cmd === "deploy") {
11476
11719
  const artifact = await runDeploy();
11477
11720
  const block = renderOtelEnvBlock2(artifact.token);
@@ -11525,11 +11768,11 @@ async function main() {
11525
11768
  process.exit(1);
11526
11769
  }
11527
11770
  async function tryOrchestrator(cmd, parsed) {
11528
- const scanPath = import_node_path49.default.resolve(cmd);
11771
+ const scanPath = import_node_path50.default.resolve(cmd);
11529
11772
  const stat = await import_node_fs32.promises.stat(scanPath).catch(() => null);
11530
11773
  if (!stat || !stat.isDirectory()) return null;
11531
11774
  const projectExplicit = parsed.project !== null;
11532
- const projectName = projectExplicit ? parsed.project : import_node_path49.default.basename(scanPath);
11775
+ const projectName = projectExplicit ? parsed.project : import_node_path50.default.basename(scanPath);
11533
11776
  const result = await runOrchestrator({
11534
11777
  scanPath,
11535
11778
  project: projectName,
@@ -11559,12 +11802,44 @@ function resolveProjectFlag(parsed) {
11559
11802
  if (env && env.length > 0 && env !== DEFAULT_PROJECT) return env;
11560
11803
  return void 0;
11561
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
+ }
11562
11838
  async function runQueryVerb(cmd, parsed) {
11563
- const baseUrl = process.env.NEAT_API_URL ?? "http://localhost:8080";
11839
+ const baseUrl = resolveDaemonUrl();
11564
11840
  const client = createHttpClient(baseUrl, resolveAuthToken());
11565
- const project = resolveProjectFlag(parsed);
11566
11841
  const positional = parsed.positional;
11567
- let work;
11842
+ let makeWork;
11568
11843
  switch (cmd) {
11569
11844
  case "root-cause": {
11570
11845
  const node = positional[0];
@@ -11572,7 +11847,7 @@ async function runQueryVerb(cmd, parsed) {
11572
11847
  console.error("neat root-cause: missing <node-id>");
11573
11848
  return 2;
11574
11849
  }
11575
- work = runRootCause(client, {
11850
+ makeWork = (project) => runRootCause(client, {
11576
11851
  errorNode: node,
11577
11852
  ...parsed.errorId ? { errorId: parsed.errorId } : {},
11578
11853
  ...project ? { project } : {}
@@ -11585,7 +11860,7 @@ async function runQueryVerb(cmd, parsed) {
11585
11860
  console.error("neat blast-radius: missing <node-id>");
11586
11861
  return 2;
11587
11862
  }
11588
- work = runBlastRadius(client, {
11863
+ makeWork = (project) => runBlastRadius(client, {
11589
11864
  nodeId: node,
11590
11865
  ...parsed.depth !== null ? { depth: parsed.depth } : {},
11591
11866
  ...project ? { project } : {}
@@ -11598,7 +11873,7 @@ async function runQueryVerb(cmd, parsed) {
11598
11873
  console.error("neat dependencies: missing <node-id>");
11599
11874
  return 2;
11600
11875
  }
11601
- work = runDependencies(client, {
11876
+ makeWork = (project) => runDependencies(client, {
11602
11877
  nodeId: node,
11603
11878
  ...parsed.depth !== null ? { depth: parsed.depth } : {},
11604
11879
  ...project ? { project } : {}
@@ -11611,14 +11886,14 @@ async function runQueryVerb(cmd, parsed) {
11611
11886
  console.error("neat observed-dependencies: missing <node-id>");
11612
11887
  return 2;
11613
11888
  }
11614
- work = runObservedDependencies(client, {
11889
+ makeWork = (project) => runObservedDependencies(client, {
11615
11890
  nodeId: node,
11616
11891
  ...project ? { project } : {}
11617
11892
  });
11618
11893
  break;
11619
11894
  }
11620
11895
  case "incidents": {
11621
- work = runIncidents(client, {
11896
+ makeWork = (project) => runIncidents(client, {
11622
11897
  ...positional[0] ? { nodeId: positional[0] } : {},
11623
11898
  ...parsed.limit !== null ? { limit: parsed.limit } : {},
11624
11899
  ...project ? { project } : {}
@@ -11631,7 +11906,7 @@ async function runQueryVerb(cmd, parsed) {
11631
11906
  console.error("neat search: missing <query>");
11632
11907
  return 2;
11633
11908
  }
11634
- work = runSearch(client, { query: q, ...project ? { project } : {} });
11909
+ makeWork = (project) => runSearch(client, { query: q, ...project ? { project } : {} });
11635
11910
  break;
11636
11911
  }
11637
11912
  case "diff": {
@@ -11640,14 +11915,14 @@ async function runQueryVerb(cmd, parsed) {
11640
11915
  console.error("neat diff: --against <snapshot-path> is required");
11641
11916
  return 2;
11642
11917
  }
11643
- work = runDiff(client, {
11918
+ makeWork = (project) => runDiff(client, {
11644
11919
  againstSnapshot: against,
11645
11920
  ...project ? { project } : {}
11646
11921
  });
11647
11922
  break;
11648
11923
  }
11649
11924
  case "stale-edges": {
11650
- work = runStaleEdges(client, {
11925
+ makeWork = (project) => runStaleEdges(client, {
11651
11926
  ...parsed.limit !== null ? { limit: parsed.limit } : {},
11652
11927
  ...parsed.edgeType ? { edgeType: parsed.edgeType } : {},
11653
11928
  ...project ? { project } : {}
@@ -11666,7 +11941,7 @@ async function runQueryVerb(cmd, parsed) {
11666
11941
  return 2;
11667
11942
  }
11668
11943
  }
11669
- work = runPolicies(client, {
11944
+ makeWork = (project) => runPolicies(client, {
11670
11945
  ...parsed.node ? { nodeId: parsed.node } : {},
11671
11946
  ...hypothetical ? { hypotheticalAction: hypothetical } : {},
11672
11947
  ...project ? { project } : {}
@@ -11679,10 +11954,10 @@ async function runQueryVerb(cmd, parsed) {
11679
11954
  const parts = parsed.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
11680
11955
  const out = [];
11681
11956
  for (const p of parts) {
11682
- const r = import_types27.DivergenceTypeSchema.safeParse(p);
11957
+ const r = import_types28.DivergenceTypeSchema.safeParse(p);
11683
11958
  if (!r.success) {
11684
11959
  console.error(
11685
- `neat divergences: unknown --type "${p}". allowed: ${import_types27.DivergenceTypeSchema.options.join(", ")}`
11960
+ `neat divergences: unknown --type "${p}". allowed: ${import_types28.DivergenceTypeSchema.options.join(", ")}`
11686
11961
  );
11687
11962
  return 2;
11688
11963
  }
@@ -11690,7 +11965,7 @@ async function runQueryVerb(cmd, parsed) {
11690
11965
  }
11691
11966
  typeFilter = out;
11692
11967
  }
11693
- work = runDivergences(client, {
11968
+ makeWork = (project) => runDivergences(client, {
11694
11969
  ...typeFilter ? { type: typeFilter } : {},
11695
11970
  ...parsed.minConfidence !== null ? { minConfidence: parsed.minConfidence } : {},
11696
11971
  ...parsed.node ? { node: parsed.node } : {},
@@ -11703,16 +11978,21 @@ async function runQueryVerb(cmd, parsed) {
11703
11978
  return 2;
11704
11979
  }
11705
11980
  try {
11706
- const result = await work;
11981
+ const project = await resolveProjectForVerb(client, parsed);
11982
+ const result = await makeWork(project);
11707
11983
  if (parsed.json) process.stdout.write(formatJson(result) + "\n");
11708
11984
  else process.stdout.write(formatHuman(result) + "\n");
11709
11985
  return 0;
11710
11986
  } catch (err) {
11987
+ if (err instanceof ProjectResolutionError) {
11988
+ console.error(`neat ${cmd}: ${err.message}`);
11989
+ return err.exitCode;
11990
+ }
11711
11991
  if (err instanceof HttpError) {
11712
11992
  const detail = err.responseBody.length > 0 ? err.responseBody : err.message;
11713
11993
  console.error(`neat ${cmd}: ${detail.trim()}`);
11714
11994
  } else if (err instanceof TransportError) {
11715
- 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()})`);
11716
11996
  } else {
11717
11997
  console.error(`neat ${cmd}: ${err.message}`);
11718
11998
  }
@@ -11729,6 +12009,7 @@ if (/[\\/]cli\.(?:cjs|js)$/.test(entry) || entry.endsWith("/cli") || entry.endsW
11729
12009
  // Annotate the CommonJS export names for ESM import in node:
11730
12010
  0 && (module.exports = {
11731
12011
  CLAUDE_SKILL_CONFIG,
12012
+ ProjectResolutionError,
11732
12013
  QUERY_VERBS,
11733
12014
  commandPrefix,
11734
12015
  isNpxInvocation,
@@ -11736,6 +12017,7 @@ if (/[\\/]cli\.(?:cjs|js)$/.test(entry) || entry.endsWith("/cli") || entry.endsW
11736
12017
  parseArgs,
11737
12018
  printBanner,
11738
12019
  readPackageVersion,
12020
+ resolveProjectForVerb,
11739
12021
  runInit,
11740
12022
  runQueryVerb,
11741
12023
  runSkill,