@neat.is/core 0.3.3 → 0.3.5

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
@@ -101,8 +101,8 @@ function reshapeGrpcRequest(req) {
101
101
  };
102
102
  }
103
103
  function resolveProtoRoot() {
104
- const here = import_node_path33.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
105
- return import_node_path33.default.resolve(here, "..", "proto");
104
+ const here = import_node_path34.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
105
+ return import_node_path34.default.resolve(here, "..", "proto");
106
106
  }
107
107
  function loadTraceService() {
108
108
  const protoRoot = resolveProtoRoot();
@@ -157,13 +157,13 @@ async function startOtelGrpcReceiver(opts) {
157
157
  })
158
158
  };
159
159
  }
160
- var import_node_url, import_node_path33, grpc, protoLoader;
160
+ var import_node_url, import_node_path34, grpc, protoLoader;
161
161
  var init_otel_grpc = __esm({
162
162
  "src/otel-grpc.ts"() {
163
163
  "use strict";
164
164
  init_cjs_shims();
165
165
  import_node_url = require("url");
166
- import_node_path33 = __toESM(require("path"), 1);
166
+ import_node_path34 = __toESM(require("path"), 1);
167
167
  grpc = __toESM(require("@grpc/grpc-js"), 1);
168
168
  protoLoader = __toESM(require("@grpc/proto-loader"), 1);
169
169
  init_otel();
@@ -260,10 +260,10 @@ function parseOtlpRequest(body) {
260
260
  }
261
261
  function loadProtobufDecoder() {
262
262
  if (exportTraceServiceRequestType) return exportTraceServiceRequestType;
263
- const here = import_node_path34.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
264
- const protoRoot = import_node_path34.default.resolve(here, "..", "proto");
263
+ const here = import_node_path35.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
264
+ const protoRoot = import_node_path35.default.resolve(here, "..", "proto");
265
265
  const root = new import_protobufjs.default.Root();
266
- root.resolvePath = (_origin, target) => import_node_path34.default.resolve(protoRoot, target);
266
+ root.resolvePath = (_origin, target) => import_node_path35.default.resolve(protoRoot, target);
267
267
  root.loadSync(
268
268
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
269
269
  { keepCase: true }
@@ -355,12 +355,12 @@ async function buildOtelReceiver(opts) {
355
355
  };
356
356
  return decorated;
357
357
  }
358
- var import_node_path34, import_node_url2, import_fastify2, import_protobufjs, exportTraceServiceRequestType;
358
+ var import_node_path35, import_node_url2, import_fastify2, import_protobufjs, exportTraceServiceRequestType;
359
359
  var init_otel = __esm({
360
360
  "src/otel.ts"() {
361
361
  "use strict";
362
362
  init_cjs_shims();
363
- import_node_path34 = __toESM(require("path"), 1);
363
+ import_node_path35 = __toESM(require("path"), 1);
364
364
  import_node_url2 = require("url");
365
365
  import_fastify2 = __toESM(require("fastify"), 1);
366
366
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -380,7 +380,7 @@ __export(cli_exports, {
380
380
  });
381
381
  module.exports = __toCommonJS(cli_exports);
382
382
  init_cjs_shims();
383
- var import_node_path39 = __toESM(require("path"), 1);
383
+ var import_node_path40 = __toESM(require("path"), 1);
384
384
  var import_node_fs25 = require("fs");
385
385
 
386
386
  // src/graph.ts
@@ -805,11 +805,16 @@ init_cjs_shims();
805
805
  var import_types = require("@neat.is/types");
806
806
  var ROOT_CAUSE_MAX_DEPTH = 5;
807
807
  var BLAST_RADIUS_DEFAULT_DEPTH = 10;
808
+ function isFrontierNode(graph, nodeId) {
809
+ if (!graph.hasNode(nodeId)) return false;
810
+ const attrs = graph.getNodeAttributes(nodeId);
811
+ return attrs.type === import_types.NodeType.FrontierNode;
812
+ }
808
813
  function bestEdgeBySource(graph, edgeIds) {
809
814
  const best = /* @__PURE__ */ new Map();
810
815
  for (const id of edgeIds) {
811
816
  const e = graph.getEdgeAttributes(id);
812
- if (e.provenance === import_types.Provenance.FRONTIER) continue;
817
+ if (isFrontierNode(graph, e.source)) continue;
813
818
  const cur = best.get(e.source);
814
819
  if (!cur || import_types.PROV_RANK[e.provenance] > import_types.PROV_RANK[cur.provenance]) {
815
820
  best.set(e.source, e);
@@ -821,7 +826,7 @@ function bestEdgeByTarget(graph, edgeIds) {
821
826
  const best = /* @__PURE__ */ new Map();
822
827
  for (const id of edgeIds) {
823
828
  const e = graph.getEdgeAttributes(id);
824
- if (e.provenance === import_types.Provenance.FRONTIER) continue;
829
+ if (isFrontierNode(graph, e.target)) continue;
825
830
  const cur = best.get(e.target);
826
831
  if (!cur || import_types.PROV_RANK[e.provenance] > import_types.PROV_RANK[cur.provenance]) {
827
832
  best.set(e.target, e);
@@ -833,8 +838,7 @@ var PROVENANCE_CEILING = {
833
838
  OBSERVED: 1,
834
839
  INFERRED: 0.7,
835
840
  EXTRACTED: 0.5,
836
- STALE: 0.3,
837
- FRONTIER: 0.3
841
+ STALE: 0.3
838
842
  };
839
843
  function volumeWeight(spanCount) {
840
844
  if (!spanCount || spanCount <= 0) return 0.5;
@@ -887,19 +891,19 @@ function confidenceFromMix(edges, now = Date.now()) {
887
891
  function longestIncomingWalk(graph, start, maxDepth) {
888
892
  let best = { path: [start], edges: [] };
889
893
  const visited = /* @__PURE__ */ new Set([start]);
890
- function step(node, path40, edges) {
891
- if (path40.length > best.path.length) {
892
- best = { path: [...path40], edges: [...edges] };
894
+ function step(node, path41, edges) {
895
+ if (path41.length > best.path.length) {
896
+ best = { path: [...path41], edges: [...edges] };
893
897
  }
894
- if (path40.length - 1 >= maxDepth) return;
898
+ if (path41.length - 1 >= maxDepth) return;
895
899
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
896
900
  for (const [srcId, edge] of incoming) {
897
901
  if (visited.has(srcId)) continue;
898
902
  visited.add(srcId);
899
- path40.push(srcId);
903
+ path41.push(srcId);
900
904
  edges.push(edge);
901
- step(srcId, path40, edges);
902
- path40.pop();
905
+ step(srcId, path41, edges);
906
+ path41.pop();
903
907
  edges.pop();
904
908
  visited.delete(srcId);
905
909
  }
@@ -1118,8 +1122,8 @@ var evaluateStructural = ({
1118
1122
  for (const edgeId of graph.outboundEdges(id)) {
1119
1123
  const e = graph.getEdgeAttributes(edgeId);
1120
1124
  if (e.type !== rule.edgeType) continue;
1121
- if (e.provenance === import_types2.Provenance.FRONTIER) continue;
1122
1125
  const target = graph.getNodeAttributes(e.target);
1126
+ if (target.type === import_types2.NodeType.FrontierNode) continue;
1123
1127
  if (target.type === rule.toNodeType) {
1124
1128
  satisfied = true;
1125
1129
  break;
@@ -1238,8 +1242,8 @@ var evaluateCompatibility = ({
1238
1242
  for (const edgeId of graph.outboundEdges(svcId)) {
1239
1243
  const e = graph.getEdgeAttributes(edgeId);
1240
1244
  if (e.type !== import_types2.EdgeType.CONNECTS_TO) continue;
1241
- if (e.provenance === import_types2.Provenance.FRONTIER) continue;
1242
1245
  const dbAttrs = graph.getNodeAttributes(e.target);
1246
+ if (dbAttrs.type === import_types2.NodeType.FrontierNode) continue;
1243
1247
  if (dbAttrs.type !== import_types2.NodeType.DatabaseNode) continue;
1244
1248
  const db = dbAttrs;
1245
1249
  for (const pair of compatPairs()) {
@@ -1556,31 +1560,6 @@ function ensureFrontierNode(graph, host, ts) {
1556
1560
  graph.addNode(id, node);
1557
1561
  return id;
1558
1562
  }
1559
- function upsertFrontierEdge(graph, type, source, target, ts) {
1560
- const id = (0, import_types3.frontierEdgeId)(source, target, type);
1561
- if (graph.hasEdge(id)) {
1562
- const existing = graph.getEdgeAttributes(id);
1563
- const updated = {
1564
- ...existing,
1565
- provenance: import_types3.Provenance.FRONTIER,
1566
- lastObserved: ts,
1567
- callCount: (existing.callCount ?? 0) + 1
1568
- };
1569
- graph.replaceEdgeAttributes(id, updated);
1570
- return;
1571
- }
1572
- const edge = {
1573
- id,
1574
- source,
1575
- target,
1576
- type,
1577
- provenance: import_types3.Provenance.FRONTIER,
1578
- confidence: 1,
1579
- lastObserved: ts,
1580
- callCount: 1
1581
- };
1582
- graph.addEdgeWithKey(id, source, target, edge);
1583
- }
1584
1563
  function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
1585
1564
  if (!graph.hasNode(source) || !graph.hasNode(target)) return null;
1586
1565
  const id = makeObservedEdgeId(type, source, target);
@@ -1588,35 +1567,37 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
1588
1567
  const existing = graph.getEdgeAttributes(id);
1589
1568
  const newSpanCount = (existing.signal?.spanCount ?? existing.callCount ?? 0) + 1;
1590
1569
  const newErrorCount = (existing.signal?.errorCount ?? 0) + (isError ? 1 : 0);
1570
+ const newSignal = {
1571
+ spanCount: newSpanCount,
1572
+ errorCount: newErrorCount,
1573
+ lastObservedAgeMs: 0
1574
+ };
1591
1575
  const updated = {
1592
1576
  ...existing,
1593
1577
  provenance: import_types3.Provenance.OBSERVED,
1594
1578
  lastObserved: ts,
1595
1579
  callCount: newSpanCount,
1596
- signal: {
1597
- spanCount: newSpanCount,
1598
- errorCount: newErrorCount,
1599
- lastObservedAgeMs: 0
1600
- },
1601
- confidence: 1
1580
+ signal: newSignal,
1581
+ confidence: (0, import_types3.confidenceForObservedSignal)(newSignal)
1602
1582
  };
1603
1583
  graph.replaceEdgeAttributes(id, updated);
1604
1584
  return { edge: updated, created: false };
1605
1585
  }
1586
+ const signal = {
1587
+ spanCount: 1,
1588
+ errorCount: isError ? 1 : 0,
1589
+ lastObservedAgeMs: 0
1590
+ };
1606
1591
  const edge = {
1607
1592
  id,
1608
1593
  source,
1609
1594
  target,
1610
1595
  type,
1611
1596
  provenance: import_types3.Provenance.OBSERVED,
1612
- confidence: 1,
1597
+ confidence: (0, import_types3.confidenceForObservedSignal)(signal),
1613
1598
  lastObserved: ts,
1614
1599
  callCount: 1,
1615
- signal: {
1616
- spanCount: 1,
1617
- errorCount: isError ? 1 : 0,
1618
- lastObservedAgeMs: 0
1619
- }
1600
+ signal
1620
1601
  };
1621
1602
  graph.addEdgeWithKey(id, source, target, edge);
1622
1603
  return { edge, created: true };
@@ -1664,18 +1645,28 @@ async function appendErrorEvent(ctx, ev) {
1664
1645
  await import_node_fs3.promises.mkdir(import_node_path3.default.dirname(ctx.errorsPath), { recursive: true });
1665
1646
  await import_node_fs3.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
1666
1647
  }
1648
+ function sanitizeAttributes(attrs) {
1649
+ const out = {};
1650
+ for (const [k, v] of Object.entries(attrs)) {
1651
+ if (typeof v === "bigint") out[k] = v.toString();
1652
+ else out[k] = v;
1653
+ }
1654
+ return out;
1655
+ }
1667
1656
  function buildErrorEventForReceiver(span) {
1668
1657
  if (span.statusCode !== 2) return null;
1669
1658
  const ts = span.startTimeIso ?? (/* @__PURE__ */ new Date()).toISOString();
1659
+ const attrs = sanitizeAttributes(span.attributes);
1670
1660
  return {
1671
1661
  id: `${span.traceId}:${span.spanId}`,
1672
1662
  timestamp: ts,
1673
1663
  service: span.service,
1674
1664
  traceId: span.traceId,
1675
1665
  spanId: span.spanId,
1676
- errorMessage: span.exception?.message ?? span.errorMessage ?? span.name ?? "unknown error",
1666
+ errorMessage: span.exception?.message ?? span.name ?? "unknown error",
1677
1667
  ...span.exception?.type ? { exceptionType: span.exception.type } : {},
1678
1668
  ...span.exception?.stacktrace ? { exceptionStacktrace: span.exception.stacktrace } : {},
1669
+ ...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
1679
1670
  affectedNode: (0, import_types3.serviceId)(span.service)
1680
1671
  };
1681
1672
  }
@@ -1726,11 +1717,16 @@ async function handleSpan(ctx, span) {
1726
1717
  affectedNode = targetId;
1727
1718
  resolvedViaAddress = true;
1728
1719
  } else if (!targetId) {
1729
- const frontierId2 = ensureFrontierNode(ctx.graph, host, ts);
1730
- if (ctx.graph.hasNode(sourceId)) {
1731
- upsertFrontierEdge(ctx.graph, import_types3.EdgeType.CALLS, sourceId, frontierId2, ts);
1732
- }
1733
- affectedNode = frontierId2;
1720
+ const frontierNodeId = ensureFrontierNode(ctx.graph, host, ts);
1721
+ upsertObservedEdge(
1722
+ ctx.graph,
1723
+ import_types3.EdgeType.CALLS,
1724
+ sourceId,
1725
+ frontierNodeId,
1726
+ ts,
1727
+ isError
1728
+ );
1729
+ affectedNode = frontierNodeId;
1734
1730
  resolvedViaAddress = true;
1735
1731
  }
1736
1732
  }
@@ -1752,15 +1748,17 @@ async function handleSpan(ctx, span) {
1752
1748
  if (span.statusCode === 2) {
1753
1749
  stitchTrace(ctx.graph, sourceId, ts);
1754
1750
  if (ctx.writeErrorEventInline !== false) {
1751
+ const attrs = sanitizeAttributes(span.attributes);
1755
1752
  const ev = {
1756
1753
  id: `${span.traceId}:${span.spanId}`,
1757
1754
  timestamp: ts,
1758
1755
  service: span.service,
1759
1756
  traceId: span.traceId,
1760
1757
  spanId: span.spanId,
1761
- errorMessage: span.exception?.message ?? span.errorMessage ?? span.name ?? "unknown error",
1758
+ errorMessage: span.exception?.message ?? span.name ?? "unknown error",
1762
1759
  ...span.exception?.type ? { exceptionType: span.exception.type } : {},
1763
1760
  ...span.exception?.stacktrace ? { exceptionStacktrace: span.exception.stacktrace } : {},
1761
+ ...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
1764
1762
  affectedNode
1765
1763
  };
1766
1764
  await appendErrorEvent(ctx, ev);
@@ -1816,8 +1814,7 @@ function rewireFrontierEdges(graph, frontierId2, serviceId3) {
1816
1814
  }
1817
1815
  function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
1818
1816
  graph.dropEdge(oldEdgeId);
1819
- const promotedProvenance = edge.provenance === import_types3.Provenance.FRONTIER ? import_types3.Provenance.OBSERVED : edge.provenance;
1820
- const newId = promotedProvenance === import_types3.Provenance.OBSERVED ? (0, import_types3.observedEdgeId)(newSource, newTarget, edge.type) : promotedProvenance === import_types3.Provenance.INFERRED ? (0, import_types3.inferredEdgeId)(newSource, newTarget, edge.type) : promotedProvenance === import_types3.Provenance.EXTRACTED ? (0, import_types3.extractedEdgeId)(newSource, newTarget, edge.type) : (0, import_types3.frontierEdgeId)(newSource, newTarget, edge.type);
1817
+ const newId = edge.provenance === import_types3.Provenance.OBSERVED ? (0, import_types3.observedEdgeId)(newSource, newTarget, edge.type) : edge.provenance === import_types3.Provenance.INFERRED ? (0, import_types3.inferredEdgeId)(newSource, newTarget, edge.type) : (0, import_types3.extractedEdgeId)(newSource, newTarget, edge.type);
1821
1818
  if (graph.hasEdge(newId)) {
1822
1819
  const existing = graph.getEdgeAttributes(newId);
1823
1820
  const merged = {
@@ -1832,8 +1829,7 @@ function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
1832
1829
  ...edge,
1833
1830
  id: newId,
1834
1831
  source: newSource,
1835
- target: newTarget,
1836
- provenance: promotedProvenance
1832
+ target: newTarget
1837
1833
  };
1838
1834
  graph.addEdgeWithKey(newId, newSource, newTarget, rebuilt);
1839
1835
  }
@@ -2251,6 +2247,27 @@ function formatExtractionBanner(count) {
2251
2247
  if (count === 1) return `[neat] 1 file skipped due to parse errors`;
2252
2248
  return `[neat] ${count} files skipped due to parse errors`;
2253
2249
  }
2250
+ var droppedSink = [];
2251
+ function noteExtractedDropped(edge) {
2252
+ droppedSink.push(edge);
2253
+ }
2254
+ function drainDroppedExtracted() {
2255
+ return droppedSink.splice(0, droppedSink.length);
2256
+ }
2257
+ function isRejectedLogEnabled() {
2258
+ const raw = process.env.NEAT_EXTRACTED_REJECTED_LOG;
2259
+ return raw === "1" || raw === "true";
2260
+ }
2261
+ async function writeRejectedExtracted(drops, rejectedPath) {
2262
+ if (drops.length === 0) return;
2263
+ await import_node_fs7.promises.mkdir(import_node_path7.default.dirname(rejectedPath), { recursive: true });
2264
+ const lines = drops.map((d) => JSON.stringify({ ...d, ts: (/* @__PURE__ */ new Date()).toISOString() })).join("\n") + "\n";
2265
+ await import_node_fs7.promises.appendFile(rejectedPath, lines, "utf8");
2266
+ }
2267
+ function formatPrecisionFloorBanner(count) {
2268
+ if (count === 1) return `[neat] 1 extracted edge dropped below precision floor`;
2269
+ return `[neat] ${count} extracted edges dropped below precision floor`;
2270
+ }
2254
2271
 
2255
2272
  // src/extract/services.ts
2256
2273
  var DEFAULT_SCAN_DEPTH = 5;
@@ -3189,6 +3206,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
3189
3206
  target: dbNode.id,
3190
3207
  type: import_types7.EdgeType.CONNECTS_TO,
3191
3208
  provenance: import_types7.Provenance.EXTRACTED,
3209
+ confidence: (0, import_types7.confidenceForExtracted)("structural"),
3192
3210
  // ADR-032 / #140 — every EXTRACTED edge carries evidence.file.
3193
3211
  // Ghost-edge cleanup keys retirement on this; the conditional
3194
3212
  // sourceFile spread that used to live here was a v0.1.x leftover.
@@ -3263,6 +3281,7 @@ async function addConfigNodes(graph, services, scanPath) {
3263
3281
  target: node.id,
3264
3282
  type: import_types8.EdgeType.CONFIGURED_BY,
3265
3283
  provenance: import_types8.Provenance.EXTRACTED,
3284
+ confidence: (0, import_types8.confidenceForExtracted)("structural"),
3266
3285
  evidence: { file: relPath.split(import_node_path18.default.sep).join("/") }
3267
3286
  };
3268
3287
  if (!graph.hasEdge(edge.id)) {
@@ -3417,17 +3436,32 @@ async function addHttpCallEdges(graph, services) {
3417
3436
  for (const [targetId, evidenceFile] of seenTargets) {
3418
3437
  const fileContent = files.find((f) => f.path === evidenceFile.file)?.content ?? "";
3419
3438
  const line = lineOf(fileContent, `//${evidenceFile.host}`);
3439
+ const confidence = (0, import_types9.confidenceForExtracted)("hostname-shape-match");
3440
+ const ev = {
3441
+ file: import_node_path20.default.relative(service.dir, evidenceFile.file),
3442
+ line,
3443
+ snippet: snippet(fileContent, line)
3444
+ };
3445
+ const edgeId = (0, import_types4.extractedEdgeId)(service.node.id, targetId, import_types9.EdgeType.CALLS);
3446
+ if (!(0, import_types9.passesExtractedFloor)(confidence)) {
3447
+ noteExtractedDropped({
3448
+ source: service.node.id,
3449
+ target: targetId,
3450
+ type: import_types9.EdgeType.CALLS,
3451
+ confidence,
3452
+ confidenceKind: "hostname-shape-match",
3453
+ evidence: ev
3454
+ });
3455
+ continue;
3456
+ }
3420
3457
  const edge = {
3421
- id: (0, import_types4.extractedEdgeId)(service.node.id, targetId, import_types9.EdgeType.CALLS),
3458
+ id: edgeId,
3422
3459
  source: service.node.id,
3423
3460
  target: targetId,
3424
3461
  type: import_types9.EdgeType.CALLS,
3425
3462
  provenance: import_types9.Provenance.EXTRACTED,
3426
- evidence: {
3427
- file: import_node_path20.default.relative(service.dir, evidenceFile.file),
3428
- line,
3429
- snippet: snippet(fileContent, line)
3430
- }
3463
+ confidence,
3464
+ evidence: ev
3431
3465
  };
3432
3466
  if (!graph.hasEdge(edge.id)) {
3433
3467
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -3466,6 +3500,10 @@ function kafkaEndpointsFromFile(file, serviceDir) {
3466
3500
  name: topic,
3467
3501
  kind: "kafka-topic",
3468
3502
  edgeType,
3503
+ // `producer.send({topic: 'x'})` / `consumer.subscribe({topic: 'x'})` —
3504
+ // framework-aware (kafkajs / node-rdkafka shape). Verified-call-site
3505
+ // tier (ADR-066).
3506
+ confidenceKind: "verified-call-site",
3469
3507
  evidence: {
3470
3508
  file: import_node_path21.default.relative(serviceDir, file.path),
3471
3509
  line,
@@ -3498,6 +3536,10 @@ function redisEndpointsFromFile(file, serviceDir) {
3498
3536
  name: host,
3499
3537
  kind: "redis",
3500
3538
  edgeType: "CALLS",
3539
+ // `redis://host` URL literal — the scheme is structural support, but no
3540
+ // call expression is verified to wire it through. URL-with-structural-
3541
+ // support tier (ADR-066).
3542
+ confidenceKind: "url-with-structural-support",
3501
3543
  evidence: {
3502
3544
  file: import_node_path22.default.relative(serviceDir, file.path),
3503
3545
  line,
@@ -3539,6 +3581,10 @@ function awsEndpointsFromFile(file, serviceDir) {
3539
3581
  name,
3540
3582
  kind,
3541
3583
  edgeType: "CALLS",
3584
+ // SDK marker (S3Client, GetCommand, etc.) plus a Bucket/TableName
3585
+ // literal — framework-aware recognizer, verified-call-site tier
3586
+ // (ADR-066).
3587
+ confidenceKind: "verified-call-site",
3542
3588
  evidence: {
3543
3589
  file: import_node_path23.default.relative(serviceDir, file.path),
3544
3590
  line,
@@ -3618,6 +3664,10 @@ function grpcEndpointsFromFile(file, serviceDir) {
3618
3664
  name,
3619
3665
  kind,
3620
3666
  edgeType: "CALLS",
3667
+ // `new <Name>Client(...)` with @aws-sdk/* or @grpc/grpc-js import
3668
+ // context — import-aware classification per #238. Verified-call-site
3669
+ // tier (ADR-066).
3670
+ confidenceKind: "verified-call-site",
3621
3671
  evidence: {
3622
3672
  file: import_node_path24.default.relative(serviceDir, file.path),
3623
3673
  line,
@@ -3678,6 +3728,18 @@ async function addExternalEndpointEdges(graph, services) {
3678
3728
  const edgeId = (0, import_types4.extractedEdgeId)(service.node.id, ep.infraId, edgeType);
3679
3729
  if (seenEdges.has(edgeId)) continue;
3680
3730
  seenEdges.add(edgeId);
3731
+ const confidence = (0, import_types14.confidenceForExtracted)(ep.confidenceKind);
3732
+ if (!(0, import_types14.passesExtractedFloor)(confidence)) {
3733
+ noteExtractedDropped({
3734
+ source: service.node.id,
3735
+ target: ep.infraId,
3736
+ type: edgeType,
3737
+ confidence,
3738
+ confidenceKind: ep.confidenceKind,
3739
+ evidence: ep.evidence
3740
+ });
3741
+ continue;
3742
+ }
3681
3743
  if (!graph.hasEdge(edgeId)) {
3682
3744
  const edge = {
3683
3745
  id: edgeId,
@@ -3685,6 +3747,7 @@ async function addExternalEndpointEdges(graph, services) {
3685
3747
  target: ep.infraId,
3686
3748
  type: edgeType,
3687
3749
  provenance: import_types14.Provenance.EXTRACTED,
3750
+ confidence,
3688
3751
  evidence: ep.evidence
3689
3752
  };
3690
3753
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -3804,6 +3867,7 @@ async function addComposeInfra(graph, scanPath, services) {
3804
3867
  target: targetId,
3805
3868
  type: import_types16.EdgeType.DEPENDS_ON,
3806
3869
  provenance: import_types16.Provenance.EXTRACTED,
3870
+ confidence: (0, import_types16.confidenceForExtracted)("structural"),
3807
3871
  evidence: { file: evidenceFile }
3808
3872
  };
3809
3873
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -3864,6 +3928,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
3864
3928
  target: node.id,
3865
3929
  type: import_types17.EdgeType.RUNS_ON,
3866
3930
  provenance: import_types17.Provenance.EXTRACTED,
3931
+ confidence: (0, import_types17.confidenceForExtracted)("structural"),
3867
3932
  evidence: {
3868
3933
  file: import_node_path26.default.relative(scanPath, dockerfilePath).split(import_node_path26.default.sep).join("/")
3869
3934
  }
@@ -3980,6 +4045,9 @@ async function addInfra(graph, scanPath, services) {
3980
4045
  };
3981
4046
  }
3982
4047
 
4048
+ // src/extract/index.ts
4049
+ var import_node_path30 = __toESM(require("path"), 1);
4050
+
3983
4051
  // src/extract/retire.ts
3984
4052
  init_cjs_shims();
3985
4053
  var import_node_fs17 = require("fs");
@@ -4044,13 +4112,26 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
4044
4112
  );
4045
4113
  }
4046
4114
  }
4115
+ const droppedEntries = drainDroppedExtracted();
4116
+ if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
4117
+ const rejectedPath = import_node_path30.default.join(import_node_path30.default.dirname(opts.errorsPath), "rejected.ndjson");
4118
+ try {
4119
+ await writeRejectedExtracted(droppedEntries, rejectedPath);
4120
+ } catch (err) {
4121
+ console.warn(
4122
+ `[neat] failed to write rejected extracted edges to ${rejectedPath}: ${err.message}`
4123
+ );
4124
+ }
4125
+ }
4047
4126
  const result = {
4048
4127
  nodesAdded: phase1Nodes + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
4049
4128
  edgesAdded: phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
4050
4129
  frontiersPromoted,
4051
4130
  extractionErrors: errorEntries.length,
4052
4131
  errorEntries,
4053
- ghostsRetired
4132
+ ghostsRetired,
4133
+ extractedDropped: droppedEntries.length,
4134
+ droppedEntries
4054
4135
  };
4055
4136
  emitNeatEvent({
4056
4137
  type: "extraction-complete",
@@ -4068,8 +4149,9 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
4068
4149
  // src/persist.ts
4069
4150
  init_cjs_shims();
4070
4151
  var import_node_fs18 = require("fs");
4071
- var import_node_path30 = __toESM(require("path"), 1);
4072
- var SCHEMA_VERSION = 2;
4152
+ var import_node_path31 = __toESM(require("path"), 1);
4153
+ var import_types19 = require("@neat.is/types");
4154
+ var SCHEMA_VERSION = 3;
4073
4155
  function migrateV1ToV2(payload) {
4074
4156
  const nodes = payload.graph.nodes;
4075
4157
  if (Array.isArray(nodes)) {
@@ -4081,8 +4163,27 @@ function migrateV1ToV2(payload) {
4081
4163
  }
4082
4164
  return { ...payload, schemaVersion: 2 };
4083
4165
  }
4166
+ function migrateV2ToV3(payload) {
4167
+ const edges = payload.graph.edges;
4168
+ if (Array.isArray(edges)) {
4169
+ for (const edge of edges) {
4170
+ const attrs = edge.attributes;
4171
+ if (!attrs || attrs.provenance !== "FRONTIER") continue;
4172
+ attrs.provenance = import_types19.Provenance.OBSERVED;
4173
+ const type = typeof attrs.type === "string" ? attrs.type : void 0;
4174
+ const source = typeof attrs.source === "string" ? attrs.source : void 0;
4175
+ const target = typeof attrs.target === "string" ? attrs.target : void 0;
4176
+ if (type && source && target) {
4177
+ const newId = (0, import_types19.observedEdgeId)(source, target, type);
4178
+ attrs.id = newId;
4179
+ if (edge.key) edge.key = newId;
4180
+ }
4181
+ }
4182
+ }
4183
+ return { ...payload, schemaVersion: 3 };
4184
+ }
4084
4185
  async function ensureDir(filePath) {
4085
- await import_node_fs18.promises.mkdir(import_node_path30.default.dirname(filePath), { recursive: true });
4186
+ await import_node_fs18.promises.mkdir(import_node_path31.default.dirname(filePath), { recursive: true });
4086
4187
  }
4087
4188
  async function saveGraphToDisk(graph, outPath) {
4088
4189
  await ensureDir(outPath);
@@ -4107,6 +4208,9 @@ async function loadGraphFromDisk(graph, outPath) {
4107
4208
  if (payload.schemaVersion === 1) {
4108
4209
  payload = migrateV1ToV2(payload);
4109
4210
  }
4211
+ if (payload.schemaVersion === 2) {
4212
+ payload = migrateV2ToV3(payload);
4213
+ }
4110
4214
  if (payload.schemaVersion !== SCHEMA_VERSION) {
4111
4215
  throw new Error(
4112
4216
  `persist: unsupported snapshot schemaVersion ${payload.schemaVersion} (expected ${SCHEMA_VERSION})`
@@ -4152,18 +4256,18 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
4152
4256
  // src/watch.ts
4153
4257
  init_cjs_shims();
4154
4258
  var import_node_fs22 = __toESM(require("fs"), 1);
4155
- var import_node_path36 = __toESM(require("path"), 1);
4259
+ var import_node_path37 = __toESM(require("path"), 1);
4156
4260
  var import_chokidar = __toESM(require("chokidar"), 1);
4157
4261
 
4158
4262
  // src/api.ts
4159
4263
  init_cjs_shims();
4160
4264
  var import_fastify = __toESM(require("fastify"), 1);
4161
4265
  var import_cors = __toESM(require("@fastify/cors"), 1);
4162
- var import_types21 = require("@neat.is/types");
4266
+ var import_types22 = require("@neat.is/types");
4163
4267
 
4164
4268
  // src/divergences.ts
4165
4269
  init_cjs_shims();
4166
- var import_types19 = require("@neat.is/types");
4270
+ var import_types20 = require("@neat.is/types");
4167
4271
  function bucketKey(source, target, type) {
4168
4272
  return `${type}|${source}|${target}`;
4169
4273
  }
@@ -4171,25 +4275,22 @@ function bucketEdges(graph) {
4171
4275
  const buckets = /* @__PURE__ */ new Map();
4172
4276
  graph.forEachEdge((id, attrs) => {
4173
4277
  const e = attrs;
4174
- const parsed = (0, import_types19.parseEdgeId)(id);
4278
+ const parsed = (0, import_types20.parseEdgeId)(id);
4175
4279
  const provenance = parsed?.provenance ?? e.provenance;
4176
4280
  const key = bucketKey(e.source, e.target, e.type);
4177
4281
  const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
4178
4282
  switch (provenance) {
4179
- case import_types19.Provenance.EXTRACTED:
4283
+ case import_types20.Provenance.EXTRACTED:
4180
4284
  cur.extracted = e;
4181
4285
  break;
4182
- case import_types19.Provenance.OBSERVED:
4286
+ case import_types20.Provenance.OBSERVED:
4183
4287
  cur.observed = e;
4184
4288
  break;
4185
- case import_types19.Provenance.INFERRED:
4289
+ case import_types20.Provenance.INFERRED:
4186
4290
  cur.inferred = e;
4187
4291
  break;
4188
- case import_types19.Provenance.FRONTIER:
4189
- cur.frontier = e;
4190
- break;
4191
4292
  default:
4192
- if (e.provenance === import_types19.Provenance.STALE) cur.stale = e;
4293
+ if (e.provenance === import_types20.Provenance.STALE) cur.stale = e;
4193
4294
  }
4194
4295
  buckets.set(key, cur);
4195
4296
  });
@@ -4198,15 +4299,7 @@ function bucketEdges(graph) {
4198
4299
  function nodeIsFrontier(graph, nodeId) {
4199
4300
  if (!graph.hasNode(nodeId)) return false;
4200
4301
  const attrs = graph.getNodeAttributes(nodeId);
4201
- return attrs.type === import_types19.NodeType.FrontierNode;
4202
- }
4203
- function hasAnyObservedFromSource(graph, sourceId) {
4204
- if (!graph.hasNode(sourceId)) return false;
4205
- for (const edgeId of graph.outboundEdges(sourceId)) {
4206
- const e = graph.getEdgeAttributes(edgeId);
4207
- if (e.provenance === import_types19.Provenance.OBSERVED) return true;
4208
- }
4209
- return false;
4302
+ return attrs.type === import_types20.NodeType.FrontierNode;
4210
4303
  }
4211
4304
  function clampConfidence(n) {
4212
4305
  if (!Number.isFinite(n)) return 0;
@@ -4221,32 +4314,34 @@ function reasonForMissingExtracted(source, target, type) {
4221
4314
  var RECOMMENDATION_MISSING_OBSERVED = "Verify the code path is exercised in production; check feature flags or conditional branches that might gate the call.";
4222
4315
  var RECOMMENDATION_MISSING_EXTRACTED = "Likely dynamic dispatch, reflection, or a coverage gap in tree-sitter extraction. Consider an `aliases` entry on the source service or file an extractor issue.";
4223
4316
  var RECOMMENDATION_HOST_MISMATCH = "Check environment-specific config overrides \u2014 the runtime host differs from what static configuration declares.";
4317
+ function gradedConfidence(edge) {
4318
+ if (typeof edge.confidence === "number") return clampConfidence(edge.confidence);
4319
+ return clampConfidence(confidenceForEdge(edge));
4320
+ }
4224
4321
  function detectMissingDivergences(graph, bucket) {
4225
4322
  const out = [];
4226
4323
  if (bucket.extracted && !bucket.observed) {
4227
4324
  if (!nodeIsFrontier(graph, bucket.target)) {
4228
- const sourceHasTraffic = hasAnyObservedFromSource(graph, bucket.source);
4229
4325
  out.push({
4230
4326
  type: "missing-observed",
4231
4327
  source: bucket.source,
4232
4328
  target: bucket.target,
4233
4329
  edgeType: bucket.type,
4234
4330
  extracted: bucket.extracted,
4235
- confidence: sourceHasTraffic ? 1 : 0.5,
4331
+ confidence: gradedConfidence(bucket.extracted),
4236
4332
  reason: reasonForMissingObserved(bucket.source, bucket.target, bucket.type),
4237
4333
  recommendation: RECOMMENDATION_MISSING_OBSERVED
4238
4334
  });
4239
4335
  }
4240
4336
  }
4241
4337
  if (bucket.observed && !bucket.extracted) {
4242
- const cascaded = clampConfidence(confidenceForEdge(bucket.observed));
4243
4338
  out.push({
4244
4339
  type: "missing-extracted",
4245
4340
  source: bucket.source,
4246
4341
  target: bucket.target,
4247
4342
  edgeType: bucket.type,
4248
4343
  observed: bucket.observed,
4249
- confidence: cascaded,
4344
+ confidence: gradedConfidence(bucket.observed),
4250
4345
  reason: reasonForMissingExtracted(bucket.source, bucket.target, bucket.type),
4251
4346
  recommendation: RECOMMENDATION_MISSING_EXTRACTED
4252
4347
  });
@@ -4265,7 +4360,7 @@ function declaredHostFor(svc) {
4265
4360
  function hasExtractedConfiguredBy(graph, svcId) {
4266
4361
  for (const edgeId of graph.outboundEdges(svcId)) {
4267
4362
  const e = graph.getEdgeAttributes(edgeId);
4268
- if (e.type === import_types19.EdgeType.CONFIGURED_BY && e.provenance === import_types19.Provenance.EXTRACTED) {
4363
+ if (e.type === import_types20.EdgeType.CONFIGURED_BY && e.provenance === import_types20.Provenance.EXTRACTED) {
4269
4364
  return true;
4270
4365
  }
4271
4366
  }
@@ -4278,10 +4373,10 @@ function detectHostMismatch(graph, svcId, svc) {
4278
4373
  const out = [];
4279
4374
  for (const edgeId of graph.outboundEdges(svcId)) {
4280
4375
  const edge = graph.getEdgeAttributes(edgeId);
4281
- if (edge.type !== import_types19.EdgeType.CONNECTS_TO) continue;
4282
- if (edge.provenance !== import_types19.Provenance.OBSERVED) continue;
4376
+ if (edge.type !== import_types20.EdgeType.CONNECTS_TO) continue;
4377
+ if (edge.provenance !== import_types20.Provenance.OBSERVED) continue;
4283
4378
  const target = graph.getNodeAttributes(edge.target);
4284
- if (target.type !== import_types19.NodeType.DatabaseNode) continue;
4379
+ if (target.type !== import_types20.NodeType.DatabaseNode) continue;
4285
4380
  const observedHost = target.host?.trim();
4286
4381
  if (!observedHost) continue;
4287
4382
  if (observedHost === declaredHost) continue;
@@ -4303,10 +4398,10 @@ function detectCompatDivergences(graph, svcId, svc) {
4303
4398
  const deps = svc.dependencies ?? {};
4304
4399
  for (const edgeId of graph.outboundEdges(svcId)) {
4305
4400
  const edge = graph.getEdgeAttributes(edgeId);
4306
- if (edge.type !== import_types19.EdgeType.CONNECTS_TO) continue;
4307
- if (edge.provenance !== import_types19.Provenance.OBSERVED) continue;
4401
+ if (edge.type !== import_types20.EdgeType.CONNECTS_TO) continue;
4402
+ if (edge.provenance !== import_types20.Provenance.OBSERVED) continue;
4308
4403
  const target = graph.getNodeAttributes(edge.target);
4309
- if (target.type !== import_types19.NodeType.DatabaseNode) continue;
4404
+ if (target.type !== import_types20.NodeType.DatabaseNode) continue;
4310
4405
  for (const pair of compatPairs()) {
4311
4406
  if (pair.engine !== target.engine) continue;
4312
4407
  const declared = deps[pair.driver];
@@ -4367,7 +4462,7 @@ function computeDivergences(graph, opts = {}) {
4367
4462
  }
4368
4463
  graph.forEachNode((nodeId, attrs) => {
4369
4464
  const n = attrs;
4370
- if (n.type !== import_types19.NodeType.ServiceNode) return;
4465
+ if (n.type !== import_types20.NodeType.ServiceNode) return;
4371
4466
  const svc = n;
4372
4467
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
4373
4468
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
@@ -4385,13 +4480,22 @@ function computeDivergences(graph, opts = {}) {
4385
4480
  const target = opts.node;
4386
4481
  filtered = filtered.filter((d) => involvesNode(d, target));
4387
4482
  }
4483
+ const TYPE_LEADERSHIP = {
4484
+ "missing-extracted": 0,
4485
+ "missing-observed": 1,
4486
+ "version-mismatch": 2,
4487
+ "host-mismatch": 3,
4488
+ "compat-violation": 4
4489
+ };
4388
4490
  filtered.sort((a, b) => {
4389
4491
  if (b.confidence !== a.confidence) return b.confidence - a.confidence;
4492
+ const lead = TYPE_LEADERSHIP[a.type] - TYPE_LEADERSHIP[b.type];
4493
+ if (lead !== 0) return lead;
4390
4494
  if (a.type !== b.type) return a.type.localeCompare(b.type);
4391
4495
  if (a.source !== b.source) return a.source.localeCompare(b.source);
4392
4496
  return a.target.localeCompare(b.target);
4393
4497
  });
4394
- return import_types19.DivergenceResultSchema.parse({
4498
+ return import_types20.DivergenceResultSchema.parse({
4395
4499
  divergences: filtered,
4396
4500
  totalAffected: filtered.length,
4397
4501
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -4477,23 +4581,23 @@ function canonicalJson(value) {
4477
4581
 
4478
4582
  // src/projects.ts
4479
4583
  init_cjs_shims();
4480
- var import_node_path31 = __toESM(require("path"), 1);
4584
+ var import_node_path32 = __toESM(require("path"), 1);
4481
4585
  function pathsForProject(project, baseDir) {
4482
4586
  if (project === DEFAULT_PROJECT) {
4483
4587
  return {
4484
- snapshotPath: import_node_path31.default.join(baseDir, "graph.json"),
4485
- errorsPath: import_node_path31.default.join(baseDir, "errors.ndjson"),
4486
- staleEventsPath: import_node_path31.default.join(baseDir, "stale-events.ndjson"),
4487
- embeddingsCachePath: import_node_path31.default.join(baseDir, "embeddings.json"),
4488
- policyViolationsPath: import_node_path31.default.join(baseDir, "policy-violations.ndjson")
4588
+ snapshotPath: import_node_path32.default.join(baseDir, "graph.json"),
4589
+ errorsPath: import_node_path32.default.join(baseDir, "errors.ndjson"),
4590
+ staleEventsPath: import_node_path32.default.join(baseDir, "stale-events.ndjson"),
4591
+ embeddingsCachePath: import_node_path32.default.join(baseDir, "embeddings.json"),
4592
+ policyViolationsPath: import_node_path32.default.join(baseDir, "policy-violations.ndjson")
4489
4593
  };
4490
4594
  }
4491
4595
  return {
4492
- snapshotPath: import_node_path31.default.join(baseDir, `${project}.json`),
4493
- errorsPath: import_node_path31.default.join(baseDir, `errors.${project}.ndjson`),
4494
- staleEventsPath: import_node_path31.default.join(baseDir, `stale-events.${project}.ndjson`),
4495
- embeddingsCachePath: import_node_path31.default.join(baseDir, `embeddings.${project}.json`),
4496
- policyViolationsPath: import_node_path31.default.join(baseDir, `policy-violations.${project}.ndjson`)
4596
+ snapshotPath: import_node_path32.default.join(baseDir, `${project}.json`),
4597
+ errorsPath: import_node_path32.default.join(baseDir, `errors.${project}.ndjson`),
4598
+ staleEventsPath: import_node_path32.default.join(baseDir, `stale-events.${project}.ndjson`),
4599
+ embeddingsCachePath: import_node_path32.default.join(baseDir, `embeddings.${project}.json`),
4600
+ policyViolationsPath: import_node_path32.default.join(baseDir, `policy-violations.${project}.ndjson`)
4497
4601
  };
4498
4602
  }
4499
4603
  var Projects = class {
@@ -4531,23 +4635,23 @@ var Projects = class {
4531
4635
  init_cjs_shims();
4532
4636
  var import_node_fs20 = require("fs");
4533
4637
  var import_node_os2 = __toESM(require("os"), 1);
4534
- var import_node_path32 = __toESM(require("path"), 1);
4535
- var import_types20 = require("@neat.is/types");
4638
+ var import_node_path33 = __toESM(require("path"), 1);
4639
+ var import_types21 = require("@neat.is/types");
4536
4640
  var LOCK_TIMEOUT_MS = 5e3;
4537
4641
  var LOCK_RETRY_MS = 50;
4538
4642
  function neatHome() {
4539
4643
  const override = process.env.NEAT_HOME;
4540
- if (override && override.length > 0) return import_node_path32.default.resolve(override);
4541
- return import_node_path32.default.join(import_node_os2.default.homedir(), ".neat");
4644
+ if (override && override.length > 0) return import_node_path33.default.resolve(override);
4645
+ return import_node_path33.default.join(import_node_os2.default.homedir(), ".neat");
4542
4646
  }
4543
4647
  function registryPath() {
4544
- return import_node_path32.default.join(neatHome(), "projects.json");
4648
+ return import_node_path33.default.join(neatHome(), "projects.json");
4545
4649
  }
4546
4650
  function registryLockPath() {
4547
- return import_node_path32.default.join(neatHome(), "projects.json.lock");
4651
+ return import_node_path33.default.join(neatHome(), "projects.json.lock");
4548
4652
  }
4549
4653
  async function normalizeProjectPath(input) {
4550
- const resolved = import_node_path32.default.resolve(input);
4654
+ const resolved = import_node_path33.default.resolve(input);
4551
4655
  try {
4552
4656
  return await import_node_fs20.promises.realpath(resolved);
4553
4657
  } catch {
@@ -4555,7 +4659,7 @@ async function normalizeProjectPath(input) {
4555
4659
  }
4556
4660
  }
4557
4661
  async function writeAtomically(target, contents) {
4558
- await import_node_fs20.promises.mkdir(import_node_path32.default.dirname(target), { recursive: true });
4662
+ await import_node_fs20.promises.mkdir(import_node_path33.default.dirname(target), { recursive: true });
4559
4663
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
4560
4664
  const fd = await import_node_fs20.promises.open(tmp, "w");
4561
4665
  try {
@@ -4568,7 +4672,7 @@ async function writeAtomically(target, contents) {
4568
4672
  }
4569
4673
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS) {
4570
4674
  const deadline = Date.now() + timeoutMs;
4571
- await import_node_fs20.promises.mkdir(import_node_path32.default.dirname(lockPath), { recursive: true });
4675
+ await import_node_fs20.promises.mkdir(import_node_path33.default.dirname(lockPath), { recursive: true });
4572
4676
  while (true) {
4573
4677
  try {
4574
4678
  const fd = await import_node_fs20.promises.open(lockPath, "wx");
@@ -4611,10 +4715,10 @@ async function readRegistry() {
4611
4715
  throw err;
4612
4716
  }
4613
4717
  const parsed = JSON.parse(raw);
4614
- return import_types20.RegistryFileSchema.parse(parsed);
4718
+ return import_types21.RegistryFileSchema.parse(parsed);
4615
4719
  }
4616
4720
  async function writeRegistry(reg) {
4617
- const validated = import_types20.RegistryFileSchema.parse(reg);
4721
+ const validated = import_types21.RegistryFileSchema.parse(reg);
4618
4722
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
4619
4723
  }
4620
4724
  var ProjectNameCollisionError = class extends Error {
@@ -4865,11 +4969,11 @@ function registerRoutes(scope, ctx) {
4865
4969
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
4866
4970
  const parsed = [];
4867
4971
  for (const c of candidates) {
4868
- const r = import_types21.DivergenceTypeSchema.safeParse(c);
4972
+ const r = import_types22.DivergenceTypeSchema.safeParse(c);
4869
4973
  if (!r.success) {
4870
4974
  return reply.code(400).send({
4871
4975
  error: `unknown divergence type "${c}"`,
4872
- allowed: import_types21.DivergenceTypeSchema.options
4976
+ allowed: import_types22.DivergenceTypeSchema.options
4873
4977
  });
4874
4978
  }
4875
4979
  parsed.push(r.data);
@@ -5053,7 +5157,7 @@ function registerRoutes(scope, ctx) {
5053
5157
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
5054
5158
  let violations = await log.readAll();
5055
5159
  if (req.query.severity) {
5056
- const sev = import_types21.PolicySeveritySchema.safeParse(req.query.severity);
5160
+ const sev = import_types22.PolicySeveritySchema.safeParse(req.query.severity);
5057
5161
  if (!sev.success) {
5058
5162
  return reply.code(400).send({
5059
5163
  error: "invalid severity",
@@ -5070,7 +5174,7 @@ function registerRoutes(scope, ctx) {
5070
5174
  scope.post("/policies/check", async (req, reply) => {
5071
5175
  const proj = resolveProject(registry, req, reply);
5072
5176
  if (!proj) return;
5073
- const parsed = import_types21.PoliciesCheckBodySchema.safeParse(req.body ?? {});
5177
+ const parsed = import_types22.PoliciesCheckBodySchema.safeParse(req.body ?? {});
5074
5178
  if (!parsed.success) {
5075
5179
  return reply.code(400).send({
5076
5180
  error: "invalid /policies/check body",
@@ -5175,7 +5279,7 @@ init_otel_grpc();
5175
5279
  // src/search.ts
5176
5280
  init_cjs_shims();
5177
5281
  var import_node_fs21 = require("fs");
5178
- var import_node_path35 = __toESM(require("path"), 1);
5282
+ var import_node_path36 = __toESM(require("path"), 1);
5179
5283
  var import_node_crypto = require("crypto");
5180
5284
  var DEFAULT_LIMIT = 10;
5181
5285
  var NOMIC_DIM = 768;
@@ -5314,7 +5418,7 @@ async function readCache(cachePath) {
5314
5418
  }
5315
5419
  }
5316
5420
  async function writeCache(cachePath, cache) {
5317
- await import_node_fs21.promises.mkdir(import_node_path35.default.dirname(cachePath), { recursive: true });
5421
+ await import_node_fs21.promises.mkdir(import_node_path36.default.dirname(cachePath), { recursive: true });
5318
5422
  await import_node_fs21.promises.writeFile(cachePath, JSON.stringify(cache));
5319
5423
  }
5320
5424
  var VectorIndex = class {
@@ -5470,8 +5574,8 @@ var ALL_PHASES = [
5470
5574
  ];
5471
5575
  function classifyChange(relPath) {
5472
5576
  const phases = /* @__PURE__ */ new Set();
5473
- const base = import_node_path36.default.basename(relPath).toLowerCase();
5474
- const segments = relPath.split(import_node_path36.default.sep).map((s) => s.toLowerCase());
5577
+ const base = import_node_path37.default.basename(relPath).toLowerCase();
5578
+ const segments = relPath.split(import_node_path37.default.sep).map((s) => s.toLowerCase());
5475
5579
  if (base === "package.json" || base === "requirements.txt" || base === "pyproject.toml" || base === "setup.py") {
5476
5580
  phases.add("services");
5477
5581
  phases.add("aliases");
@@ -5573,9 +5677,9 @@ function countWatchableDirs(scanPath, limit) {
5573
5677
  for (const e of entries) {
5574
5678
  if (count >= limit) return;
5575
5679
  if (!e.isDirectory()) continue;
5576
- if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path36.default.join(dir, e.name) + import_node_path36.default.sep))) continue;
5680
+ if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path37.default.join(dir, e.name) + import_node_path37.default.sep))) continue;
5577
5681
  count++;
5578
- if (depth < 2) visit(import_node_path36.default.join(dir, e.name), depth + 1);
5682
+ if (depth < 2) visit(import_node_path37.default.join(dir, e.name), depth + 1);
5579
5683
  }
5580
5684
  };
5581
5685
  visit(scanPath, 0);
@@ -5593,8 +5697,8 @@ async function startWatch(graph, opts) {
5593
5697
  const projectName = opts.project ?? DEFAULT_PROJECT;
5594
5698
  await loadGraphFromDisk(graph, opts.outPath);
5595
5699
  const detachEventBus = attachGraphToEventBus(graph, { project: projectName });
5596
- const policyFilePath = import_node_path36.default.join(opts.scanPath, "policy.json");
5597
- const policyViolationsPath = import_node_path36.default.join(import_node_path36.default.dirname(opts.outPath), "policy-violations.ndjson");
5700
+ const policyFilePath = import_node_path37.default.join(opts.scanPath, "policy.json");
5701
+ const policyViolationsPath = import_node_path37.default.join(import_node_path37.default.dirname(opts.outPath), "policy-violations.ndjson");
5598
5702
  let policies = [];
5599
5703
  try {
5600
5704
  policies = await loadPolicyFile(policyFilePath);
@@ -5643,7 +5747,7 @@ async function startWatch(graph, opts) {
5643
5747
  const host = opts.host ?? "0.0.0.0";
5644
5748
  const port = opts.port ?? 8080;
5645
5749
  const otelPort = opts.otelPort ?? 4318;
5646
- const cachePath = opts.embeddingsCachePath ?? import_node_path36.default.join(import_node_path36.default.dirname(opts.outPath), "embeddings.json");
5750
+ const cachePath = opts.embeddingsCachePath ?? import_node_path37.default.join(import_node_path37.default.dirname(opts.outPath), "embeddings.json");
5647
5751
  let searchIndex;
5648
5752
  try {
5649
5753
  searchIndex = await buildSearchIndex(graph, { cachePath });
@@ -5661,7 +5765,7 @@ async function startWatch(graph, opts) {
5661
5765
  // Paths are derived from the explicit options the watch caller passes
5662
5766
  // — pathsForProject is only used to fill in the embeddings/snapshot
5663
5767
  // fields so the registry shape is complete.
5664
- ...pathsForProject(projectName, import_node_path36.default.dirname(opts.outPath)),
5768
+ ...pathsForProject(projectName, import_node_path37.default.dirname(opts.outPath)),
5665
5769
  snapshotPath: opts.outPath,
5666
5770
  errorsPath: opts.errorsPath,
5667
5771
  staleEventsPath: opts.staleEventsPath
@@ -5746,9 +5850,9 @@ async function startWatch(graph, opts) {
5746
5850
  };
5747
5851
  const onPath = (absPath) => {
5748
5852
  if (shouldIgnore(absPath)) return;
5749
- const rel = import_node_path36.default.relative(opts.scanPath, absPath);
5853
+ const rel = import_node_path37.default.relative(opts.scanPath, absPath);
5750
5854
  if (!rel || rel.startsWith("..")) return;
5751
- pendingPaths.add(rel.split(import_node_path36.default.sep).join("/"));
5855
+ pendingPaths.add(rel.split(import_node_path37.default.sep).join("/"));
5752
5856
  const phases = classifyChange(rel);
5753
5857
  if (phases.size === 0) {
5754
5858
  for (const p of ALL_PHASES) pending.add(p);
@@ -5806,7 +5910,7 @@ init_cjs_shims();
5806
5910
  // src/installers/javascript.ts
5807
5911
  init_cjs_shims();
5808
5912
  var import_node_fs23 = require("fs");
5809
- var import_node_path37 = __toESM(require("path"), 1);
5913
+ var import_node_path38 = __toESM(require("path"), 1);
5810
5914
  var SDK_PACKAGES = [
5811
5915
  { name: "@opentelemetry/api", version: "^1.9.0" },
5812
5916
  { name: "@opentelemetry/sdk-node", version: "^0.57.0" },
@@ -5822,7 +5926,7 @@ var OTEL_ENV = {
5822
5926
  };
5823
5927
  async function readPackageJson(serviceDir) {
5824
5928
  try {
5825
- const raw = await import_node_fs23.promises.readFile(import_node_path37.default.join(serviceDir, "package.json"), "utf8");
5929
+ const raw = await import_node_fs23.promises.readFile(import_node_path38.default.join(serviceDir, "package.json"), "utf8");
5826
5930
  return JSON.parse(raw);
5827
5931
  } catch {
5828
5932
  return null;
@@ -5841,7 +5945,7 @@ function rewriteStartScript(start) {
5841
5945
  }
5842
5946
  async function plan(serviceDir) {
5843
5947
  const pkg = await readPackageJson(serviceDir);
5844
- const manifestPath = import_node_path37.default.join(serviceDir, "package.json");
5948
+ const manifestPath = import_node_path38.default.join(serviceDir, "package.json");
5845
5949
  const empty = {
5846
5950
  language: "javascript",
5847
5951
  serviceDir,
@@ -5940,7 +6044,7 @@ async function rollback(installPlan, originals) {
5940
6044
  ...restored.map((f) => `restored: ${f}`),
5941
6045
  ""
5942
6046
  ];
5943
- const rollbackPath = import_node_path37.default.join(installPlan.serviceDir, "neat-rollback.patch");
6047
+ const rollbackPath = import_node_path38.default.join(installPlan.serviceDir, "neat-rollback.patch");
5944
6048
  await import_node_fs23.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
5945
6049
  }
5946
6050
  var javascriptInstaller = {
@@ -5953,7 +6057,7 @@ var javascriptInstaller = {
5953
6057
  // src/installers/python.ts
5954
6058
  init_cjs_shims();
5955
6059
  var import_node_fs24 = require("fs");
5956
- var import_node_path38 = __toESM(require("path"), 1);
6060
+ var import_node_path39 = __toESM(require("path"), 1);
5957
6061
  var SDK_PACKAGES2 = [
5958
6062
  { name: "opentelemetry-distro", version: ">=0.49b0" },
5959
6063
  { name: "opentelemetry-exporter-otlp", version: ">=1.28.0" }
@@ -5974,7 +6078,7 @@ async function exists2(p) {
5974
6078
  async function detect2(serviceDir) {
5975
6079
  const markers = ["requirements.txt", "pyproject.toml", "setup.py"];
5976
6080
  for (const m of markers) {
5977
- if (await exists2(import_node_path38.default.join(serviceDir, m))) return true;
6081
+ if (await exists2(import_node_path39.default.join(serviceDir, m))) return true;
5978
6082
  }
5979
6083
  return false;
5980
6084
  }
@@ -5984,7 +6088,7 @@ function reqPackageName(line) {
5984
6088
  return head.replace(/[<>=!~].*$/, "").toLowerCase();
5985
6089
  }
5986
6090
  async function planRequirementsTxtEdits(serviceDir) {
5987
- const file = import_node_path38.default.join(serviceDir, "requirements.txt");
6091
+ const file = import_node_path39.default.join(serviceDir, "requirements.txt");
5988
6092
  if (!await exists2(file)) return null;
5989
6093
  const raw = await import_node_fs24.promises.readFile(file, "utf8");
5990
6094
  const presentNames = new Set(
@@ -5994,7 +6098,7 @@ async function planRequirementsTxtEdits(serviceDir) {
5994
6098
  return { manifest: file, missing: [...missing] };
5995
6099
  }
5996
6100
  async function planProcfileEdits(serviceDir) {
5997
- const procfile = import_node_path38.default.join(serviceDir, "Procfile");
6101
+ const procfile = import_node_path39.default.join(serviceDir, "Procfile");
5998
6102
  if (!await exists2(procfile)) return [];
5999
6103
  const raw = await import_node_fs24.promises.readFile(procfile, "utf8");
6000
6104
  const edits = [];
@@ -6079,7 +6183,7 @@ async function apply2(installPlan) {
6079
6183
  if (raw === void 0) {
6080
6184
  throw new Error(`python installer: cannot read ${file} during apply`);
6081
6185
  }
6082
- const base = import_node_path38.default.basename(file);
6186
+ const base = import_node_path39.default.basename(file);
6083
6187
  if (base === "requirements.txt") {
6084
6188
  const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
6085
6189
  if (edits.length > 0) await applyRequirementsTxt(file, edits, raw);
@@ -6111,7 +6215,7 @@ async function rollback2(installPlan, originals) {
6111
6215
  ...restored.map((f) => `restored: ${f}`),
6112
6216
  ""
6113
6217
  ];
6114
- const rollbackPath = import_node_path38.default.join(installPlan.serviceDir, "neat-rollback.patch");
6218
+ const rollbackPath = import_node_path39.default.join(installPlan.serviceDir, "neat-rollback.patch");
6115
6219
  await import_node_fs24.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
6116
6220
  }
6117
6221
  var pythonInstaller = {
@@ -6201,11 +6305,11 @@ function renderPatch(sections) {
6201
6305
  }
6202
6306
 
6203
6307
  // src/cli.ts
6204
- var import_types23 = require("@neat.is/types");
6308
+ var import_types24 = require("@neat.is/types");
6205
6309
 
6206
6310
  // src/cli-client.ts
6207
6311
  init_cjs_shims();
6208
- var import_types22 = require("@neat.is/types");
6312
+ var import_types23 = require("@neat.is/types");
6209
6313
  var HttpError = class extends Error {
6210
6314
  constructor(status2, message, responseBody = "") {
6211
6315
  super(message);
@@ -6225,10 +6329,10 @@ var TransportError = class extends Error {
6225
6329
  function createHttpClient(baseUrl) {
6226
6330
  const root = baseUrl.replace(/\/$/, "");
6227
6331
  return {
6228
- async get(path40) {
6332
+ async get(path41) {
6229
6333
  let res;
6230
6334
  try {
6231
- res = await fetch(`${root}${path40}`);
6335
+ res = await fetch(`${root}${path41}`);
6232
6336
  } catch (err) {
6233
6337
  throw new TransportError(
6234
6338
  `cannot reach neat-core at ${root}: ${err.message}`
@@ -6238,16 +6342,16 @@ function createHttpClient(baseUrl) {
6238
6342
  const body = await res.text().catch(() => "");
6239
6343
  throw new HttpError(
6240
6344
  res.status,
6241
- `${res.status} ${res.statusText} on GET ${path40}: ${body}`,
6345
+ `${res.status} ${res.statusText} on GET ${path41}: ${body}`,
6242
6346
  body
6243
6347
  );
6244
6348
  }
6245
6349
  return await res.json();
6246
6350
  },
6247
- async post(path40, body) {
6351
+ async post(path41, body) {
6248
6352
  let res;
6249
6353
  try {
6250
- res = await fetch(`${root}${path40}`, {
6354
+ res = await fetch(`${root}${path41}`, {
6251
6355
  method: "POST",
6252
6356
  headers: { "content-type": "application/json" },
6253
6357
  body: JSON.stringify(body)
@@ -6261,7 +6365,7 @@ function createHttpClient(baseUrl) {
6261
6365
  const text = await res.text().catch(() => "");
6262
6366
  throw new HttpError(
6263
6367
  res.status,
6264
- `${res.status} ${res.statusText} on POST ${path40}: ${text}`,
6368
+ `${res.status} ${res.statusText} on POST ${path41}: ${text}`,
6265
6369
  text
6266
6370
  );
6267
6371
  }
@@ -6275,12 +6379,12 @@ function projectPath(project, suffix) {
6275
6379
  }
6276
6380
  async function runRootCause(client, input) {
6277
6381
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
6278
- const path40 = projectPath(
6382
+ const path41 = projectPath(
6279
6383
  input.project,
6280
6384
  `/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
6281
6385
  );
6282
6386
  try {
6283
- const result = await client.get(path40);
6387
+ const result = await client.get(path41);
6284
6388
  const arrowPath = result.traversalPath.join(" \u2190 ");
6285
6389
  const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
6286
6390
  const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
@@ -6306,12 +6410,12 @@ async function runRootCause(client, input) {
6306
6410
  }
6307
6411
  async function runBlastRadius(client, input) {
6308
6412
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
6309
- const path40 = projectPath(
6413
+ const path41 = projectPath(
6310
6414
  input.project,
6311
6415
  `/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
6312
6416
  );
6313
6417
  try {
6314
- const result = await client.get(path40);
6418
+ const result = await client.get(path41);
6315
6419
  if (result.totalAffected === 0) {
6316
6420
  return {
6317
6421
  summary: `${result.origin} has no downstream dependencies. Nothing else would break if it failed.`
@@ -6340,17 +6444,17 @@ async function runBlastRadius(client, input) {
6340
6444
  }
6341
6445
  }
6342
6446
  function formatBlastEntry(n) {
6343
- const tag = n.edgeProvenance === import_types22.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
6447
+ const tag = n.edgeProvenance === import_types23.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
6344
6448
  return ` \u2022 ${n.nodeId} (distance ${n.distance}, ${n.edgeProvenance})${tag}`;
6345
6449
  }
6346
6450
  async function runDependencies(client, input) {
6347
6451
  const depth = input.depth ?? 3;
6348
- const path40 = projectPath(
6452
+ const path41 = projectPath(
6349
6453
  input.project,
6350
6454
  `/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
6351
6455
  );
6352
6456
  try {
6353
- const result = await client.get(path40);
6457
+ const result = await client.get(path41);
6354
6458
  if (result.total === 0) {
6355
6459
  return {
6356
6460
  summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
@@ -6386,9 +6490,9 @@ async function runObservedDependencies(client, input) {
6386
6490
  const edges = await client.get(
6387
6491
  projectPath(input.project, `/graph/edges/${encodeURIComponent(input.nodeId)}`)
6388
6492
  );
6389
- const observed = edges.outbound.filter((e) => e.provenance === import_types22.Provenance.OBSERVED);
6493
+ const observed = edges.outbound.filter((e) => e.provenance === import_types23.Provenance.OBSERVED);
6390
6494
  if (observed.length === 0) {
6391
- const hasExtracted = edges.outbound.some((e) => e.provenance === import_types22.Provenance.EXTRACTED);
6495
+ const hasExtracted = edges.outbound.some((e) => e.provenance === import_types23.Provenance.EXTRACTED);
6392
6496
  const note = hasExtracted ? " Static (EXTRACTED) dependencies exist but no runtime traffic has been seen \u2014 is OTel running?" : "";
6393
6497
  return { summary: `No OBSERVED dependencies for ${input.nodeId}.${note}` };
6394
6498
  }
@@ -6396,7 +6500,7 @@ async function runObservedDependencies(client, input) {
6396
6500
  return {
6397
6501
  summary: `${input.nodeId} has ${observed.length} runtime dependenc${observed.length === 1 ? "y" : "ies"} confirmed by OTel.`,
6398
6502
  block: blockLines.join("\n"),
6399
- provenance: import_types22.Provenance.OBSERVED
6503
+ provenance: import_types23.Provenance.OBSERVED
6400
6504
  };
6401
6505
  } catch (err) {
6402
6506
  if (err instanceof HttpError && err.status === 404) {
@@ -6431,9 +6535,9 @@ function formatDuration(ms) {
6431
6535
  return `${Math.round(h / 24)}d`;
6432
6536
  }
6433
6537
  async function runIncidents(client, input) {
6434
- const path40 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
6538
+ const path41 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
6435
6539
  try {
6436
- const body = await client.get(path40);
6540
+ const body = await client.get(path41);
6437
6541
  const events = body.events;
6438
6542
  if (events.length === 0) {
6439
6543
  return {
@@ -6450,7 +6554,7 @@ async function runIncidents(client, input) {
6450
6554
  return {
6451
6555
  summary: `${target} has ${body.total} recorded incident${body.total === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
6452
6556
  block: blockLines.join("\n"),
6453
- provenance: import_types22.Provenance.OBSERVED
6557
+ provenance: import_types23.Provenance.OBSERVED
6454
6558
  };
6455
6559
  } catch (err) {
6456
6560
  if (err instanceof HttpError && err.status === 404) {
@@ -6559,7 +6663,7 @@ async function runStaleEdges(client, input) {
6559
6663
  return {
6560
6664
  summary: `${events.length} stale-edge transition${events.length === 1 ? "" : "s"} recorded${input.edgeType ? ` for ${input.edgeType}` : ""}.`,
6561
6665
  block: blockLines.join("\n"),
6562
- provenance: import_types22.Provenance.STALE
6666
+ provenance: import_types23.Provenance.STALE
6563
6667
  };
6564
6668
  }
6565
6669
  async function runPolicies(client, input) {
@@ -6957,7 +7061,7 @@ async function runInit(opts) {
6957
7061
  printDiscoveryReport(opts, services);
6958
7062
  const sections = opts.noInstall ? [] : await buildPatchSections(services);
6959
7063
  const patch = renderPatch(sections);
6960
- const patchPath = import_node_path39.default.join(opts.scanPath, "neat.patch");
7064
+ const patchPath = import_node_path40.default.join(opts.scanPath, "neat.patch");
6961
7065
  if (opts.dryRun) {
6962
7066
  await import_node_fs25.promises.writeFile(patchPath, patch, "utf8");
6963
7067
  written.push(patchPath);
@@ -6970,9 +7074,9 @@ async function runInit(opts) {
6970
7074
  const graph = getGraph(graphKey);
6971
7075
  const projectPaths = pathsForProject(
6972
7076
  graphKey,
6973
- import_node_path39.default.join(opts.scanPath, "neat-out")
7077
+ import_node_path40.default.join(opts.scanPath, "neat-out")
6974
7078
  );
6975
- const errorsPath = import_node_path39.default.join(import_node_path39.default.dirname(opts.outPath), import_node_path39.default.basename(projectPaths.errorsPath));
7079
+ const errorsPath = import_node_path40.default.join(import_node_path40.default.dirname(opts.outPath), import_node_path40.default.basename(projectPaths.errorsPath));
6976
7080
  const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
6977
7081
  await saveGraphToDisk(graph, opts.outPath);
6978
7082
  written.push(opts.outPath);
@@ -7022,6 +7126,7 @@ async function runInit(opts) {
7022
7126
  if (result.extractionErrors > 0) {
7023
7127
  console.log(`errors: ${errorsPath}`);
7024
7128
  }
7129
+ console.log(formatPrecisionFloorBanner(result.extractedDropped));
7025
7130
  const incompatibilities = findIncompatibilities(nodes);
7026
7131
  if (incompatibilities.length > 0) {
7027
7132
  console.log("");
@@ -7051,9 +7156,9 @@ var CLAUDE_SKILL_CONFIG = {
7051
7156
  };
7052
7157
  function claudeConfigPath() {
7053
7158
  const override = process.env.NEAT_CLAUDE_CONFIG;
7054
- if (override && override.length > 0) return import_node_path39.default.resolve(override);
7159
+ if (override && override.length > 0) return import_node_path40.default.resolve(override);
7055
7160
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
7056
- return import_node_path39.default.join(home, ".claude.json");
7161
+ return import_node_path40.default.join(home, ".claude.json");
7057
7162
  }
7058
7163
  async function runSkill(opts) {
7059
7164
  const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
@@ -7077,7 +7182,7 @@ async function runSkill(opts) {
7077
7182
  ...existing,
7078
7183
  mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
7079
7184
  };
7080
- await import_node_fs25.promises.mkdir(import_node_path39.default.dirname(target), { recursive: true });
7185
+ await import_node_fs25.promises.mkdir(import_node_path40.default.dirname(target), { recursive: true });
7081
7186
  await import_node_fs25.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
7082
7187
  console.log(`neat skill: wrote mcpServers.neat to ${target}`);
7083
7188
  console.log("restart Claude Code to pick up the new MCP server.");
@@ -7112,12 +7217,12 @@ async function main() {
7112
7217
  console.error("neat init: --apply and --dry-run are mutually exclusive");
7113
7218
  process.exit(2);
7114
7219
  }
7115
- const scanPath = import_node_path39.default.resolve(target);
7220
+ const scanPath = import_node_path40.default.resolve(target);
7116
7221
  const projectExplicit = parsed.project !== null;
7117
- const projectName = projectExplicit ? project : import_node_path39.default.basename(scanPath);
7222
+ const projectName = projectExplicit ? project : import_node_path40.default.basename(scanPath);
7118
7223
  const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
7119
- const fallback = pathsForProject(projectKey, import_node_path39.default.join(scanPath, "neat-out")).snapshotPath;
7120
- const outPath = import_node_path39.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
7224
+ const fallback = pathsForProject(projectKey, import_node_path40.default.join(scanPath, "neat-out")).snapshotPath;
7225
+ const outPath = import_node_path40.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
7121
7226
  const result = await runInit({
7122
7227
  scanPath,
7123
7228
  outPath,
@@ -7137,21 +7242,21 @@ async function main() {
7137
7242
  usage();
7138
7243
  process.exit(2);
7139
7244
  }
7140
- const scanPath = import_node_path39.default.resolve(target);
7245
+ const scanPath = import_node_path40.default.resolve(target);
7141
7246
  const stat = await import_node_fs25.promises.stat(scanPath).catch(() => null);
7142
7247
  if (!stat || !stat.isDirectory()) {
7143
7248
  console.error(`neat watch: ${scanPath} is not a directory`);
7144
7249
  process.exit(2);
7145
7250
  }
7146
- const projectPaths = pathsForProject(project, import_node_path39.default.join(scanPath, "neat-out"));
7147
- const outPath = import_node_path39.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
7148
- const errorsPath = import_node_path39.default.resolve(
7149
- process.env.NEAT_ERRORS_PATH ?? import_node_path39.default.join(import_node_path39.default.dirname(outPath), import_node_path39.default.basename(projectPaths.errorsPath))
7251
+ const projectPaths = pathsForProject(project, import_node_path40.default.join(scanPath, "neat-out"));
7252
+ const outPath = import_node_path40.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
7253
+ const errorsPath = import_node_path40.default.resolve(
7254
+ process.env.NEAT_ERRORS_PATH ?? import_node_path40.default.join(import_node_path40.default.dirname(outPath), import_node_path40.default.basename(projectPaths.errorsPath))
7150
7255
  );
7151
- const staleEventsPath = import_node_path39.default.resolve(
7152
- process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path39.default.join(import_node_path39.default.dirname(outPath), import_node_path39.default.basename(projectPaths.staleEventsPath))
7256
+ const staleEventsPath = import_node_path40.default.resolve(
7257
+ process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path40.default.join(import_node_path40.default.dirname(outPath), import_node_path40.default.basename(projectPaths.staleEventsPath))
7153
7258
  );
7154
- const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path39.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
7259
+ const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path40.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
7155
7260
  const handle = await startWatch(getGraph(project), {
7156
7261
  scanPath,
7157
7262
  outPath,
@@ -7392,10 +7497,10 @@ async function runQueryVerb(cmd, parsed) {
7392
7497
  const parts = parsed.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
7393
7498
  const out = [];
7394
7499
  for (const p of parts) {
7395
- const r = import_types23.DivergenceTypeSchema.safeParse(p);
7500
+ const r = import_types24.DivergenceTypeSchema.safeParse(p);
7396
7501
  if (!r.success) {
7397
7502
  console.error(
7398
- `neat divergences: unknown --type "${p}". allowed: ${import_types23.DivergenceTypeSchema.options.join(", ")}`
7503
+ `neat divergences: unknown --type "${p}". allowed: ${import_types24.DivergenceTypeSchema.options.join(", ")}`
7399
7504
  );
7400
7505
  return 2;
7401
7506
  }