@neat.is/core 0.9.2-dev.20260821 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -61,8 +61,8 @@ function mountBearerAuth(app, opts) {
61
61
  ]);
62
62
  const publicRead = opts.publicRead === true;
63
63
  app.addHook("preHandler", (req, reply, done) => {
64
- const path92 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
- if (exactUnauthPaths.has(path92) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path92)) {
64
+ const path93 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
+ if (exactUnauthPaths.has(path93) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path93)) {
66
66
  done();
67
67
  return;
68
68
  }
@@ -415,8 +415,8 @@ function websocketChannelPathOf(attrs) {
415
415
  const v = attrs[key];
416
416
  if (typeof v === "string" && v.length > 0) {
417
417
  const q = v.indexOf("?");
418
- const path92 = q === -1 ? v : v.slice(0, q);
419
- if (path92.length > 0) return path92;
418
+ const path93 = q === -1 ? v : v.slice(0, q);
419
+ if (path93.length > 0) return path93;
420
420
  }
421
421
  }
422
422
  return void 0;
@@ -799,13 +799,13 @@ __export(cli_exports, {
799
799
  runMonitorVerb: () => runMonitorVerb,
800
800
  runQueryVerb: () => runQueryVerb,
801
801
  runSkill: () => runSkill,
802
- usage: () => usage4
802
+ usage: () => usage5
803
803
  });
804
804
  module.exports = __toCommonJS(cli_exports);
805
805
  init_cjs_shims();
806
- var import_node_path91 = __toESM(require("path"), 1);
806
+ var import_node_path92 = __toESM(require("path"), 1);
807
807
  var import_node_os8 = __toESM(require("os"), 1);
808
- var import_node_fs55 = require("fs");
808
+ var import_node_fs56 = require("fs");
809
809
 
810
810
  // src/banner.ts
811
811
  init_cjs_shims();
@@ -1373,19 +1373,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1373
1373
  function longestIncomingWalk(graph, start, maxDepth) {
1374
1374
  let best = { path: [start], edges: [] };
1375
1375
  const visited = /* @__PURE__ */ new Set([start]);
1376
- function step(node, path92, edges) {
1377
- if (path92.length > best.path.length) {
1378
- best = { path: [...path92], edges: [...edges] };
1376
+ function step(node, path93, edges) {
1377
+ if (path93.length > best.path.length) {
1378
+ best = { path: [...path93], edges: [...edges] };
1379
1379
  }
1380
- if (path92.length - 1 >= maxDepth) return;
1380
+ if (path93.length - 1 >= maxDepth) return;
1381
1381
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1382
1382
  for (const [srcId, edge] of incoming) {
1383
1383
  if (visited.has(srcId)) continue;
1384
1384
  visited.add(srcId);
1385
- path92.push(srcId);
1385
+ path93.push(srcId);
1386
1386
  edges.push(edge);
1387
- step(srcId, path92, edges);
1388
- path92.pop();
1387
+ step(srcId, path93, edges);
1388
+ path93.pop();
1389
1389
  edges.pop();
1390
1390
  visited.delete(srcId);
1391
1391
  }
@@ -1400,7 +1400,7 @@ function databaseRootCauseShape(graph, origin, walk10) {
1400
1400
  for (const id of walk10.path) {
1401
1401
  const owner = resolveOwningService(graph, id);
1402
1402
  if (!owner) continue;
1403
- const { id: serviceId15, svc } = owner;
1403
+ const { id: serviceId16, svc } = owner;
1404
1404
  const deps = svc.dependencies ?? {};
1405
1405
  for (const pair of candidatePairs) {
1406
1406
  const declared = deps[pair.driver];
@@ -1413,7 +1413,7 @@ function databaseRootCauseShape(graph, origin, walk10) {
1413
1413
  );
1414
1414
  if (!result.compatible) {
1415
1415
  return {
1416
- rootCauseNode: serviceId15,
1416
+ rootCauseNode: serviceId16,
1417
1417
  rootCauseReason: result.reason ?? "incompatible driver",
1418
1418
  ...result.minDriverVersion ? {
1419
1419
  fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
@@ -1428,7 +1428,7 @@ function serviceRootCauseShape(graph, _origin, walk10) {
1428
1428
  for (const id of walk10.path) {
1429
1429
  const owner = resolveOwningService(graph, id);
1430
1430
  if (!owner) continue;
1431
- const { id: serviceId15, svc } = owner;
1431
+ const { id: serviceId16, svc } = owner;
1432
1432
  const deps = svc.dependencies ?? {};
1433
1433
  const serviceNodeEngine = svc.nodeEngine;
1434
1434
  for (const constraint of nodeEngineConstraints()) {
@@ -1437,7 +1437,7 @@ function serviceRootCauseShape(graph, _origin, walk10) {
1437
1437
  const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
1438
1438
  if (!result.compatible && result.reason) {
1439
1439
  return {
1440
- rootCauseNode: serviceId15,
1440
+ rootCauseNode: serviceId16,
1441
1441
  rootCauseReason: result.reason,
1442
1442
  ...result.requiredNodeVersion ? {
1443
1443
  fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
@@ -1452,7 +1452,7 @@ function serviceRootCauseShape(graph, _origin, walk10) {
1452
1452
  const result = checkPackageConflict(conflict, declared, requiredDeclared);
1453
1453
  if (!result.compatible && result.reason) {
1454
1454
  return {
1455
- rootCauseNode: serviceId15,
1455
+ rootCauseNode: serviceId16,
1456
1456
  rootCauseReason: result.reason,
1457
1457
  fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
1458
1458
  };
@@ -1557,9 +1557,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
1557
1557
  function isFailingCallEdge(e) {
1558
1558
  return e.type === import_types.EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0;
1559
1559
  }
1560
- function callSourcesForService(graph, serviceId15) {
1561
- const ids = [serviceId15];
1562
- for (const edgeId of graph.outboundEdges(serviceId15)) {
1560
+ function callSourcesForService(graph, serviceId16) {
1561
+ const ids = [serviceId16];
1562
+ for (const edgeId of graph.outboundEdges(serviceId16)) {
1563
1563
  const e = graph.getEdgeAttributes(edgeId);
1564
1564
  if (e.type !== import_types.EdgeType.CONTAINS) continue;
1565
1565
  const tgt = graph.getNodeAttributes(e.target);
@@ -1583,9 +1583,9 @@ function failingCallDominates(e, id, curEdge, curId) {
1583
1583
  }
1584
1584
  return id < curId;
1585
1585
  }
1586
- function dominantFailingCall(graph, serviceId15, visited) {
1586
+ function dominantFailingCall(graph, serviceId16, visited) {
1587
1587
  let best = null;
1588
- for (const src of callSourcesForService(graph, serviceId15)) {
1588
+ for (const src of callSourcesForService(graph, serviceId16)) {
1589
1589
  for (const edgeId of graph.outboundEdges(src)) {
1590
1590
  const e = graph.getEdgeAttributes(edgeId);
1591
1591
  if (!isFailingCallEdge(e)) continue;
@@ -1600,20 +1600,20 @@ function dominantFailingCall(graph, serviceId15, visited) {
1600
1600
  return best;
1601
1601
  }
1602
1602
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1603
- const path92 = [originServiceId];
1603
+ const path93 = [originServiceId];
1604
1604
  const edges = [];
1605
1605
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1606
1606
  let current = originServiceId;
1607
1607
  for (let depth = 0; depth < maxDepth; depth++) {
1608
1608
  const hop = dominantFailingCall(graph, current, visited);
1609
1609
  if (!hop) break;
1610
- path92.push(hop.nextService);
1610
+ path93.push(hop.nextService);
1611
1611
  edges.push(hop.edge);
1612
1612
  visited.add(hop.nextService);
1613
1613
  current = hop.nextService;
1614
1614
  }
1615
1615
  if (edges.length === 0) return null;
1616
- return { path: path92, edges, culprit: current };
1616
+ return { path: path93, edges, culprit: current };
1617
1617
  }
1618
1618
  function isStaleCallEdge(e) {
1619
1619
  return e.type === import_types.EdgeType.CALLS && e.provenance === import_types.Provenance.STALE;
@@ -1624,9 +1624,9 @@ function staleCallDominates(e, id, curEdge, curId) {
1624
1624
  if (ev !== cv) return ev > cv;
1625
1625
  return id < curId;
1626
1626
  }
1627
- function dominantStaleCall(graph, serviceId15, visited) {
1627
+ function dominantStaleCall(graph, serviceId16, visited) {
1628
1628
  const bestByCallee = /* @__PURE__ */ new Map();
1629
- for (const src of callSourcesForService(graph, serviceId15)) {
1629
+ for (const src of callSourcesForService(graph, serviceId16)) {
1630
1630
  for (const edgeId of graph.outboundEdges(src)) {
1631
1631
  const e = graph.getEdgeAttributes(edgeId);
1632
1632
  if (e.type !== import_types.EdgeType.CALLS) continue;
@@ -1649,26 +1649,26 @@ function dominantStaleCall(graph, serviceId15, visited) {
1649
1649
  return best;
1650
1650
  }
1651
1651
  function followStaleCallChain(graph, originServiceId, maxDepth) {
1652
- const path92 = [originServiceId];
1652
+ const path93 = [originServiceId];
1653
1653
  const edges = [];
1654
1654
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1655
1655
  let current = originServiceId;
1656
1656
  for (let depth = 0; depth < maxDepth; depth++) {
1657
1657
  const hop = dominantStaleCall(graph, current, visited);
1658
1658
  if (!hop) break;
1659
- path92.push(hop.nextService);
1659
+ path93.push(hop.nextService);
1660
1660
  edges.push(hop.edge);
1661
1661
  visited.add(hop.nextService);
1662
1662
  current = hop.nextService;
1663
1663
  }
1664
1664
  if (edges.length === 0) return null;
1665
- return { path: path92, edges, culprit: current };
1665
+ return { path: path93, edges, culprit: current };
1666
1666
  }
1667
1667
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1668
1668
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1669
1669
  if (!chain) return null;
1670
1670
  const culprit = chain.culprit;
1671
- const path92 = [...chain.path];
1671
+ const path93 = [...chain.path];
1672
1672
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1673
1673
  const baseConfidence = confidenceFromMix(chain.edges);
1674
1674
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1676,14 +1676,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1676
1676
  if (loc) {
1677
1677
  let rootCauseNode = culprit;
1678
1678
  if (loc.fileNode) {
1679
- path92.push(loc.fileNode);
1679
+ path93.push(loc.fileNode);
1680
1680
  edgeProvenances.push(import_types.Provenance.OBSERVED);
1681
1681
  rootCauseNode = loc.fileNode;
1682
1682
  }
1683
1683
  return import_types.RootCauseResultSchema.parse({
1684
1684
  rootCauseNode,
1685
1685
  rootCauseReason: loc.rootCauseReason,
1686
- traversalPath: path92,
1686
+ traversalPath: path93,
1687
1687
  edgeProvenances,
1688
1688
  confidence,
1689
1689
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1695,7 +1695,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1695
1695
  return import_types.RootCauseResultSchema.parse({
1696
1696
  rootCauseNode: culprit,
1697
1697
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1698
- traversalPath: path92,
1698
+ traversalPath: path93,
1699
1699
  edgeProvenances,
1700
1700
  confidence,
1701
1701
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -2158,10 +2158,10 @@ function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
2158
2158
  traversalPath = staleChain.path;
2159
2159
  edgeProvenances = staleChain.edges.map((e) => e.provenance);
2160
2160
  } else if (top.node !== seedNode) {
2161
- const path92 = findPath(graph, errorNodeId, top.node, "up", ROOT_CAUSE_MAX_DEPTH);
2162
- if (path92) {
2163
- traversalPath = path92.nodes;
2164
- edgeProvenances = path92.edges.map((e) => e.provenance);
2161
+ const path93 = findPath(graph, errorNodeId, top.node, "up", ROOT_CAUSE_MAX_DEPTH);
2162
+ if (path93) {
2163
+ traversalPath = path93.nodes;
2164
+ edgeProvenances = path93.edges.map((e) => e.provenance);
2165
2165
  } else {
2166
2166
  traversalPath = [errorNodeId, top.node];
2167
2167
  edgeProvenances = [top.provenance ?? import_types.Provenance.OBSERVED];
@@ -3609,8 +3609,8 @@ function chiRoutesFromSource(source, parser) {
3609
3609
  chiWalk(tree.rootNode, "", out);
3610
3610
  return out;
3611
3611
  }
3612
- function stripChiRegex(path92) {
3613
- return path92.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
3612
+ function stripChiRegex(path93) {
3613
+ return path93.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
3614
3614
  }
3615
3615
  function chiWalk(node, prefix, out) {
3616
3616
  for (let i = 0; i < node.namedChildCount; i++) {
@@ -4282,9 +4282,9 @@ function rubyRocketRoute(args) {
4282
4282
  if (!pair || pair.type !== "pair") continue;
4283
4283
  const k = pair.childForFieldName("key");
4284
4284
  if (k?.type !== "string") continue;
4285
- const path92 = rubyLiteral(k);
4286
- if (path92 === null) continue;
4287
- return { path: path92, target: rubyLiteral(pair.childForFieldName("value")) };
4285
+ const path93 = rubyLiteral(k);
4286
+ if (path93 === null) continue;
4287
+ return { path: path93, target: rubyLiteral(pair.childForFieldName("value")) };
4288
4288
  }
4289
4289
  return null;
4290
4290
  }
@@ -4837,11 +4837,11 @@ function actixAttributeRoute(attrItem, out) {
4837
4837
  if (!nameNode) return;
4838
4838
  const macro = nameNode.type === "identifier" ? nameNode.text : nameNode.type === "scoped_identifier" ? nameNode.childForFieldName("name")?.text ?? null : null;
4839
4839
  if (!macro) return;
4840
- const tokens = attr.childForFieldName("arguments");
4841
- if (!tokens || tokens.type !== "token_tree") return;
4840
+ const tokens2 = attr.childForFieldName("arguments");
4841
+ if (!tokens2 || tokens2.type !== "token_tree") return;
4842
4842
  const strings = [];
4843
- for (let i = 0; i < tokens.namedChildCount; i++) {
4844
- const s = rustStringContent(tokens.namedChild(i));
4843
+ for (let i = 0; i < tokens2.namedChildCount; i++) {
4844
+ const s = rustStringContent(tokens2.namedChild(i));
4845
4845
  if (s !== null) strings.push(s);
4846
4846
  }
4847
4847
  const pathStr = strings[0];
@@ -6735,29 +6735,29 @@ function promoteFrontierNodes(graph, opts = {}) {
6735
6735
  toPromote.push({ frontierId: id, serviceId: target });
6736
6736
  });
6737
6737
  let promoted = 0;
6738
- for (const { frontierId: frontierId2, serviceId: serviceId15 } of toPromote) {
6738
+ for (const { frontierId: frontierId2, serviceId: serviceId16 } of toPromote) {
6739
6739
  if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
6740
6740
  const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
6741
6741
  if (!gate.allowed) {
6742
6742
  continue;
6743
6743
  }
6744
6744
  }
6745
- rewireFrontierEdges(graph, frontierId2, serviceId15);
6745
+ rewireFrontierEdges(graph, frontierId2, serviceId16);
6746
6746
  graph.dropNode(frontierId2);
6747
6747
  promoted++;
6748
6748
  }
6749
6749
  return promoted;
6750
6750
  }
6751
- function rewireFrontierEdges(graph, frontierId2, serviceId15) {
6751
+ function rewireFrontierEdges(graph, frontierId2, serviceId16) {
6752
6752
  const inbound = [...graph.inboundEdges(frontierId2)];
6753
6753
  const outbound = [...graph.outboundEdges(frontierId2)];
6754
6754
  for (const edgeId of inbound) {
6755
6755
  const edge = graph.getEdgeAttributes(edgeId);
6756
- rebuildEdge(graph, edge, edge.source, serviceId15, edgeId);
6756
+ rebuildEdge(graph, edge, edge.source, serviceId16, edgeId);
6757
6757
  }
6758
6758
  for (const edgeId of outbound) {
6759
6759
  const edge = graph.getEdgeAttributes(edgeId);
6760
- rebuildEdge(graph, edge, serviceId15, edge.target, edgeId);
6760
+ rebuildEdge(graph, edge, serviceId16, edge.target, edgeId);
6761
6761
  }
6762
6762
  }
6763
6763
  function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
@@ -7872,9 +7872,9 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
7872
7872
  "StatefulSet",
7873
7873
  "DaemonSet"
7874
7874
  ]);
7875
- function addAliases(graph, serviceId15, candidates) {
7876
- if (!graph.hasNode(serviceId15)) return;
7877
- const node = graph.getNodeAttributes(serviceId15);
7875
+ function addAliases(graph, serviceId16, candidates) {
7876
+ if (!graph.hasNode(serviceId16)) return;
7877
+ const node = graph.getNodeAttributes(serviceId16);
7878
7878
  if (node.type !== import_types19.NodeType.ServiceNode) return;
7879
7879
  const set = new Set(node.aliases ?? []);
7880
7880
  for (const c of candidates) {
@@ -7884,7 +7884,7 @@ function addAliases(graph, serviceId15, candidates) {
7884
7884
  }
7885
7885
  if (set.size === 0) return;
7886
7886
  const updated = { ...node, aliases: [...set].sort() };
7887
- graph.replaceNodeAttributes(serviceId15, updated);
7887
+ graph.replaceNodeAttributes(serviceId16, updated);
7888
7888
  }
7889
7889
  function indexServicesByName(services) {
7890
7890
  const map = /* @__PURE__ */ new Map();
@@ -7904,9 +7904,9 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
7904
7904
  }
7905
7905
  }
7906
7906
  if (!composePath) return;
7907
- let compose;
7907
+ let compose2;
7908
7908
  try {
7909
- compose = await readYaml(composePath);
7909
+ compose2 = await readYaml(composePath);
7910
7910
  } catch (err) {
7911
7911
  recordExtractionError(
7912
7912
  "aliases compose",
@@ -7915,14 +7915,14 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
7915
7915
  );
7916
7916
  return;
7917
7917
  }
7918
- if (!compose?.services) return;
7919
- for (const [composeName, svc] of Object.entries(compose.services)) {
7920
- const serviceId15 = serviceIndex.get(composeName);
7921
- if (!serviceId15) continue;
7918
+ if (!compose2?.services) return;
7919
+ for (const [composeName, svc] of Object.entries(compose2.services)) {
7920
+ const serviceId16 = serviceIndex.get(composeName);
7921
+ if (!serviceId16) continue;
7922
7922
  const aliases = /* @__PURE__ */ new Set([composeName]);
7923
7923
  if (svc.container_name) aliases.add(svc.container_name);
7924
7924
  if (svc.hostname) aliases.add(svc.hostname);
7925
- addAliases(graph, serviceId15, aliases);
7925
+ addAliases(graph, serviceId16, aliases);
7926
7926
  }
7927
7927
  }
7928
7928
  var LABEL_KEYS = /* @__PURE__ */ new Set([
@@ -14475,9 +14475,9 @@ async function addComposeInfra(graph, scanPath, services) {
14475
14475
  }
14476
14476
  }
14477
14477
  if (!composePath) return { nodesAdded, edgesAdded };
14478
- let compose;
14478
+ let compose2;
14479
14479
  try {
14480
- compose = await readYaml(composePath);
14480
+ compose2 = await readYaml(composePath);
14481
14481
  } catch (err) {
14482
14482
  recordExtractionError(
14483
14483
  "infra docker-compose",
@@ -14486,10 +14486,10 @@ async function addComposeInfra(graph, scanPath, services) {
14486
14486
  );
14487
14487
  return { nodesAdded, edgesAdded };
14488
14488
  }
14489
- if (!compose?.services) return { nodesAdded, edgesAdded };
14489
+ if (!compose2?.services) return { nodesAdded, edgesAdded };
14490
14490
  const evidenceFile = import_node_path58.default.relative(scanPath, composePath).split(import_node_path58.default.sep).join("/");
14491
14491
  const composeNameToNodeId = /* @__PURE__ */ new Map();
14492
- for (const [composeName, svc] of Object.entries(compose.services)) {
14492
+ for (const [composeName, svc] of Object.entries(compose2.services)) {
14493
14493
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
14494
14494
  if (matchedServiceId) {
14495
14495
  composeNameToNodeId.set(composeName, matchedServiceId);
@@ -14503,7 +14503,7 @@ async function addComposeInfra(graph, scanPath, services) {
14503
14503
  }
14504
14504
  composeNameToNodeId.set(composeName, node.id);
14505
14505
  }
14506
- for (const [composeName, svc] of Object.entries(compose.services)) {
14506
+ for (const [composeName, svc] of Object.entries(compose2.services)) {
14507
14507
  const sourceId = composeNameToNodeId.get(composeName);
14508
14508
  if (!sourceId) continue;
14509
14509
  for (const dep of dependsOnList(svc.depends_on)) {
@@ -15224,7 +15224,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
15224
15224
 
15225
15225
  // src/extract/infra/index.ts
15226
15226
  async function addInfra(graph, scanPath, services) {
15227
- const compose = await addComposeInfra(graph, scanPath, services);
15227
+ const compose2 = await addComposeInfra(graph, scanPath, services);
15228
15228
  const dockerfile = await addDockerfileRuntimes(graph, services, scanPath);
15229
15229
  const terraform = await addTerraformResources(graph, scanPath);
15230
15230
  const k8s = await addK8sResources(graph, scanPath);
@@ -15233,8 +15233,8 @@ async function addInfra(graph, scanPath, services) {
15233
15233
  const railway = await addRailwayServices(graph, services, scanPath);
15234
15234
  const supabase = await addSupabaseProjects(graph, services, scanPath);
15235
15235
  return {
15236
- nodesAdded: compose.nodesAdded + dockerfile.nodesAdded + terraform.nodesAdded + k8s.nodesAdded + cloudflare.nodesAdded + vercel.nodesAdded + railway.nodesAdded + supabase.nodesAdded,
15237
- edgesAdded: compose.edgesAdded + dockerfile.edgesAdded + terraform.edgesAdded + k8s.edgesAdded + cloudflare.edgesAdded + vercel.edgesAdded + railway.edgesAdded + supabase.edgesAdded
15236
+ nodesAdded: compose2.nodesAdded + dockerfile.nodesAdded + terraform.nodesAdded + k8s.nodesAdded + cloudflare.nodesAdded + vercel.nodesAdded + railway.nodesAdded + supabase.nodesAdded,
15237
+ edgesAdded: compose2.edgesAdded + dockerfile.edgesAdded + terraform.edgesAdded + k8s.edgesAdded + cloudflare.edgesAdded + vercel.edgesAdded + railway.edgesAdded + supabase.edgesAdded
15238
15238
  };
15239
15239
  }
15240
15240
 
@@ -16343,7 +16343,7 @@ var import_chokidar = __toESM(require("chokidar"), 1);
16343
16343
  init_cjs_shims();
16344
16344
  var import_fastify2 = __toESM(require("fastify"), 1);
16345
16345
  var import_cors = __toESM(require("@fastify/cors"), 1);
16346
- var import_types93 = require("@neat.is/types");
16346
+ var import_types94 = require("@neat.is/types");
16347
16347
 
16348
16348
  // src/extend/index.ts
16349
16349
  init_cjs_shims();
@@ -16733,6 +16733,639 @@ function queryLogEntries(opts) {
16733
16733
  return merged;
16734
16734
  }
16735
16735
 
16736
+ // src/ask.ts
16737
+ init_cjs_shims();
16738
+ var import_types60 = require("@neat.is/types");
16739
+ var DEFAULT_MAX_NODES = 3;
16740
+ var MAX_FACTS_PER_SECTION = 6;
16741
+ var INTENT_RULES = [
16742
+ {
16743
+ intent: "root-cause",
16744
+ test: /\b(why|root[\s-]?cause|failing|fail(?:s|ed)?|breaking|broke(?:n)?|crash(?:ing|ed)?|throw(?:ing|s)?|culprit|5\d\d)\b/
16745
+ },
16746
+ {
16747
+ intent: "blast-radius",
16748
+ test: /\b(blast|break[\s-]?if|breaks[\s-]?if|impact|downstream|dependents?|redeploy|who\s+(?:uses|calls|depends)|what\s+depends\s+on|affect(?:s|ed)?)\b/
16749
+ },
16750
+ {
16751
+ intent: "divergence",
16752
+ test: /\b(diverg\w*|weird|mismatch|drift|out\s+of\s+sync|inconsistent|disagree\w*|declared\s+vs|anything\s+wrong)\b/
16753
+ },
16754
+ {
16755
+ intent: "incidents",
16756
+ test: /\b(incidents?|recent\s+(?:errors?|failures?)|error\s+history|failure\s+history)\b/
16757
+ },
16758
+ {
16759
+ intent: "observed",
16760
+ test: /\b(at\s+runtime|in\s+prod(?:uction)?|actually\s+call\w*|really\s+call\w*|observed|runtime\s+traffic)\b/
16761
+ },
16762
+ {
16763
+ intent: "dependencies",
16764
+ test: /\b(depend\w*|calls?|uses?|imports?|relies\s+on|needs?)\b/
16765
+ }
16766
+ ];
16767
+ function classifyIntent(question) {
16768
+ const q = question.toLowerCase();
16769
+ for (const rule of INTENT_RULES) {
16770
+ if (rule.test.test(q)) return rule.intent;
16771
+ }
16772
+ return "overview";
16773
+ }
16774
+ var STOPWORDS = /* @__PURE__ */ new Set([
16775
+ "the",
16776
+ "a",
16777
+ "an",
16778
+ "is",
16779
+ "are",
16780
+ "was",
16781
+ "were",
16782
+ "be",
16783
+ "been",
16784
+ "being",
16785
+ "of",
16786
+ "to",
16787
+ "in",
16788
+ "on",
16789
+ "at",
16790
+ "by",
16791
+ "for",
16792
+ "with",
16793
+ "and",
16794
+ "or",
16795
+ "but",
16796
+ "if",
16797
+ "as",
16798
+ "this",
16799
+ "that",
16800
+ "these",
16801
+ "those",
16802
+ "it",
16803
+ "its",
16804
+ "do",
16805
+ "does",
16806
+ "did",
16807
+ "has",
16808
+ "have",
16809
+ "had",
16810
+ "what",
16811
+ "which",
16812
+ "who",
16813
+ "whom",
16814
+ "how",
16815
+ "when",
16816
+ "where",
16817
+ "my",
16818
+ "me",
16819
+ "i",
16820
+ "we",
16821
+ "you",
16822
+ "they",
16823
+ "them",
16824
+ "from",
16825
+ "into",
16826
+ "about",
16827
+ "would",
16828
+ "will",
16829
+ "can",
16830
+ "could",
16831
+ "should",
16832
+ "anything",
16833
+ "everything",
16834
+ "something",
16835
+ "all",
16836
+ "any",
16837
+ "some",
16838
+ "no",
16839
+ "not",
16840
+ "so",
16841
+ "up",
16842
+ "out",
16843
+ "over",
16844
+ "get",
16845
+ "got",
16846
+ "show",
16847
+ "tell",
16848
+ "explain",
16849
+ "find",
16850
+ "give",
16851
+ "happen",
16852
+ "happens",
16853
+ "happening",
16854
+ "here",
16855
+ "there",
16856
+ "system",
16857
+ "code",
16858
+ "service",
16859
+ "node",
16860
+ "graph"
16861
+ ]);
16862
+ var INTENT_WORDS = /* @__PURE__ */ new Set([
16863
+ "why",
16864
+ "root",
16865
+ "cause",
16866
+ "failing",
16867
+ "fail",
16868
+ "fails",
16869
+ "failed",
16870
+ "breaking",
16871
+ "broke",
16872
+ "broken",
16873
+ "crash",
16874
+ "crashing",
16875
+ "crashed",
16876
+ "throw",
16877
+ "throwing",
16878
+ "throws",
16879
+ "culprit",
16880
+ "blast",
16881
+ "radius",
16882
+ "break",
16883
+ "breaks",
16884
+ "impact",
16885
+ "downstream",
16886
+ "dependent",
16887
+ "dependents",
16888
+ "redeploy",
16889
+ "affect",
16890
+ "affects",
16891
+ "affected",
16892
+ "diverge",
16893
+ "divergence",
16894
+ "divergences",
16895
+ "weird",
16896
+ "mismatch",
16897
+ "drift",
16898
+ "inconsistent",
16899
+ "disagree",
16900
+ "declared",
16901
+ "observed",
16902
+ "incident",
16903
+ "incidents",
16904
+ "recent",
16905
+ "errors",
16906
+ "error",
16907
+ "failures",
16908
+ "failure",
16909
+ "history",
16910
+ "runtime",
16911
+ "production",
16912
+ "prod",
16913
+ "traffic",
16914
+ "actually",
16915
+ "really",
16916
+ "depend",
16917
+ "depends",
16918
+ "dependency",
16919
+ "dependencies",
16920
+ "call",
16921
+ "calls",
16922
+ "use",
16923
+ "uses",
16924
+ "using",
16925
+ "import",
16926
+ "imports",
16927
+ "relies",
16928
+ "rely",
16929
+ "needs",
16930
+ "need",
16931
+ "sync"
16932
+ ]);
16933
+ function tokens(input) {
16934
+ const spaced = input.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
16935
+ return (spaced.toLowerCase().match(/[a-z0-9]+/g) ?? []).filter((t) => t.length >= 2);
16936
+ }
16937
+ function queryTokens(question) {
16938
+ const seen = /* @__PURE__ */ new Set();
16939
+ const out = [];
16940
+ for (const t of tokens(question)) {
16941
+ if (STOPWORDS.has(t) || INTENT_WORDS.has(t)) continue;
16942
+ if (seen.has(t)) continue;
16943
+ seen.add(t);
16944
+ out.push(t);
16945
+ }
16946
+ return out;
16947
+ }
16948
+ function nodeName(node) {
16949
+ return node.name ?? node.id;
16950
+ }
16951
+ function idBody(id) {
16952
+ const colon = id.indexOf(":");
16953
+ return colon >= 0 ? id.slice(colon + 1) : id;
16954
+ }
16955
+ var EMBED_MIN_SCORE = 0.35;
16956
+ async function resolveEntities(graph, question, searchIndex, maxNodes) {
16957
+ const qTokens = queryTokens(question);
16958
+ const normalized = question.toLowerCase();
16959
+ const best = /* @__PURE__ */ new Map();
16960
+ const consider = (cand) => {
16961
+ const cur = best.get(cand.nodeId);
16962
+ if (!cur || cand.score > cur.score) best.set(cand.nodeId, cand);
16963
+ };
16964
+ graph.forEachNode((id, attrs) => {
16965
+ const node = attrs;
16966
+ if (node.type === import_types60.NodeType.FrontierNode) return;
16967
+ const name = nodeName(node);
16968
+ const body = idBody(id);
16969
+ const labelTokens = /* @__PURE__ */ new Set([...tokens(body), ...tokens(name)]);
16970
+ if (labelTokens.size === 0) return;
16971
+ let matched = 0;
16972
+ for (const t of qTokens) if (labelTokens.has(t)) matched += 1;
16973
+ const named = body.length >= 2 && normalized.includes(body.toLowerCase()) || name.length >= 2 && normalized.includes(name.toLowerCase());
16974
+ if (matched === 0 && !named) return;
16975
+ const coverage = qTokens.length > 0 ? matched / qTokens.length : 0;
16976
+ const via = named ? "id" : matched > 0 && tokens(name).some((t) => qTokens.includes(t)) ? "label" : "token";
16977
+ const score = named ? Math.max(0.9, coverage) : Math.min(0.85, 0.3 + 0.7 * coverage);
16978
+ consider({ nodeId: id, label: name, via, score: Math.min(1, score) });
16979
+ });
16980
+ if (searchIndex) {
16981
+ try {
16982
+ const res = await searchIndex.search(question, 10);
16983
+ if (res.provider !== "substring") {
16984
+ for (const m of res.matches) {
16985
+ if (m.node.type === import_types60.NodeType.FrontierNode) continue;
16986
+ const already = best.get(m.node.id);
16987
+ if (!already && m.score < EMBED_MIN_SCORE) continue;
16988
+ consider({
16989
+ nodeId: m.node.id,
16990
+ label: nodeName(m.node),
16991
+ via: "embedding",
16992
+ score: Math.max(0, Math.min(1, m.score))
16993
+ });
16994
+ }
16995
+ }
16996
+ } catch {
16997
+ }
16998
+ }
16999
+ const viaRank = { id: 3, label: 2, token: 1, embedding: 0 };
17000
+ return [...best.values()].sort(
17001
+ (a, b) => b.score - a.score || viaRank[b.via] - viaRank[a.via] || a.nodeId.localeCompare(b.nodeId)
17002
+ ).slice(0, maxNodes).map((s) => ({ nodeId: s.nodeId, label: s.label, via: s.via, score: Number(s.score.toFixed(3)) }));
17003
+ }
17004
+ function incidentsForNode(nodeId, incidents) {
17005
+ if (!incidents || incidents.length === 0) return [];
17006
+ const svc = nodeId.replace(/^service:/, "");
17007
+ return incidents.filter((e) => e.affectedNode === nodeId || e.service === svc);
17008
+ }
17009
+ function edgeSignalNote(e) {
17010
+ const bits = [];
17011
+ if (e.signal) {
17012
+ bits.push(`${e.signal.spanCount} spans`);
17013
+ if (e.signal.errorCount > 0) bits.push(`${e.signal.errorCount} errors`);
17014
+ if (e.signal.latencyMs?.p95 !== void 0) bits.push(`p95 ${Math.round(e.signal.latencyMs.p95)}ms`);
17015
+ } else if (e.callCount !== void 0) {
17016
+ bits.push(`${e.callCount} calls`);
17017
+ }
17018
+ return bits.length ? ` (${bits.join(", ")})` : "";
17019
+ }
17020
+ function buildRootCauseSection(graph, node, incidents, now) {
17021
+ const result = getRootCause(graph, node, void 0, incidents, { now });
17022
+ if (!result) return null;
17023
+ const lastProv = result.edgeProvenances[result.edgeProvenances.length - 1] ?? import_types60.Provenance.OBSERVED;
17024
+ const facts = [
17025
+ {
17026
+ text: `Root cause: ${result.rootCauseNode} \u2014 ${result.rootCauseReason}`,
17027
+ provenance: lastProv,
17028
+ confidence: result.confidence
17029
+ }
17030
+ ];
17031
+ const candidates = result.candidates ?? [];
17032
+ for (const c of candidates.slice(0, 3)) {
17033
+ facts.push({
17034
+ text: `Candidate ${c.node} \u2014 ${c.classification}: ${c.reason}`,
17035
+ ...c.provenance ? { provenance: c.provenance } : {},
17036
+ confidence: c.confidence
17037
+ });
17038
+ }
17039
+ if (result.traversalPath.length > 1) {
17040
+ facts.push({ text: `Traversal: ${result.traversalPath.join(" \u2190 ")}` });
17041
+ }
17042
+ if (result.fixRecommendation) {
17043
+ facts.push({ text: `Recommended fix: ${result.fixRecommendation}` });
17044
+ }
17045
+ return { heading: "Root cause (navigation)", facts: facts.slice(0, MAX_FACTS_PER_SECTION) };
17046
+ }
17047
+ function buildDependenciesSection(graph, node) {
17048
+ const result = getTransitiveDependencies(graph, node, 2);
17049
+ if (result.total === 0) return null;
17050
+ const facts = result.dependencies.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
17051
+ text: `${d.nodeId} \u2014 ${d.edgeType} (distance ${d.distance})`,
17052
+ // Per-fact provenance: the transitive walk mixes EXTRACTED and OBSERVED
17053
+ // edges, so the tag is on the fact, not asserted for the whole section.
17054
+ provenance: d.provenance
17055
+ }));
17056
+ return { heading: "Dependencies", facts };
17057
+ }
17058
+ function buildObservedSection(graph, node) {
17059
+ const result = getObservedDependencies(graph, node);
17060
+ const facts = [];
17061
+ for (const e of result.dependencies.slice(0, MAX_FACTS_PER_SECTION)) {
17062
+ const via = e.source !== node ? ` via ${e.source}` : "";
17063
+ facts.push({
17064
+ text: `calls ${e.target}${via}${edgeSignalNote(e)}`,
17065
+ provenance: e.provenance,
17066
+ confidence: confidenceForEdge(e)
17067
+ });
17068
+ }
17069
+ if (facts.length === 0) {
17070
+ if (result.observed && result.inboundObservedCount > 0) {
17071
+ facts.push({
17072
+ text: `no outbound runtime calls, but OTel observed ${result.inboundObservedCount} inbound call path${result.inboundObservedCount === 1 ? "" : "s"} \u2014 a pure receiver`,
17073
+ provenance: import_types60.Provenance.OBSERVED
17074
+ });
17075
+ } else {
17076
+ return null;
17077
+ }
17078
+ }
17079
+ return { heading: "Runtime dependencies (OBSERVED)", facts };
17080
+ }
17081
+ function buildBlastSection(graph, node) {
17082
+ const result = getBlastRadius(graph, node);
17083
+ if (result.totalAffected === 0) return null;
17084
+ const facts = result.affectedNodes.slice(0, MAX_FACTS_PER_SECTION).map((n) => ({
17085
+ text: `${n.nodeId} (distance ${n.distance})`,
17086
+ provenance: n.edgeProvenance,
17087
+ confidence: n.confidence
17088
+ }));
17089
+ return {
17090
+ heading: `Blast radius \u2014 ${result.totalAffected} dependent${result.totalAffected === 1 ? "" : "s"}`,
17091
+ facts
17092
+ };
17093
+ }
17094
+ function buildIncidentsSection(node, incidents) {
17095
+ const relevant = incidentsForNode(node, incidents);
17096
+ if (relevant.length === 0) return null;
17097
+ const ordered = [...relevant].sort((a, b) => b.timestamp.localeCompare(a.timestamp)).slice(0, 4);
17098
+ const facts = ordered.map((ev) => ({
17099
+ text: `${ev.timestamp} \u2014 ${ev.service}: ${ev.errorMessage}`,
17100
+ // ErrorEvents are observation records — OBSERVED by definition.
17101
+ provenance: import_types60.Provenance.OBSERVED
17102
+ }));
17103
+ return { heading: `Recent incidents (OBSERVED) \u2014 ${relevant.length} recorded`, facts };
17104
+ }
17105
+ function divergenceLine(d) {
17106
+ if ((d.type === "missing-observed" || d.type === "missing-extracted") && d.column) {
17107
+ return `[${d.type}] ${d.table ?? d.source} column ${d.column} \u2014 ${d.reason}`;
17108
+ }
17109
+ return `[${d.type}] ${d.source} \u2192 ${d.target} \u2014 ${d.reason}`;
17110
+ }
17111
+ function buildDivergenceSection(graph, node) {
17112
+ const result = computeDivergences(graph, { node });
17113
+ if (result.totalAffected === 0) return null;
17114
+ const facts = result.divergences.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
17115
+ text: divergenceLine(d),
17116
+ confidence: d.confidence
17117
+ // A divergence is composite by construction (EXTRACTED vs OBSERVED), so it
17118
+ // carries no single provenance — the fact leaves it unset.
17119
+ }));
17120
+ return {
17121
+ heading: `Divergences (EXTRACTED vs OBSERVED) \u2014 ${result.totalAffected}`,
17122
+ facts
17123
+ };
17124
+ }
17125
+ function buildGlobalDivergenceSection(graph) {
17126
+ const result = computeDivergences(graph);
17127
+ if (result.totalAffected === 0) {
17128
+ return {
17129
+ heading: "Divergences (EXTRACTED vs OBSERVED)",
17130
+ facts: [{ text: "None \u2014 declared code and observed runtime agree across the graph." }]
17131
+ };
17132
+ }
17133
+ const facts = result.divergences.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
17134
+ text: divergenceLine(d),
17135
+ confidence: d.confidence
17136
+ // Composite by construction (EXTRACTED vs OBSERVED), so no single provenance.
17137
+ }));
17138
+ return {
17139
+ heading: `Divergences (EXTRACTED vs OBSERVED) \u2014 ${result.totalAffected}`,
17140
+ facts
17141
+ };
17142
+ }
17143
+ function buildGlobalIncidentsSection(incidents) {
17144
+ if (!incidents || incidents.length === 0) {
17145
+ return {
17146
+ heading: "Incidents across the system",
17147
+ facts: [{ text: "None recorded \u2014 the OBSERVED incident store is empty." }]
17148
+ };
17149
+ }
17150
+ const byKey = /* @__PURE__ */ new Map();
17151
+ for (const ev of incidents) {
17152
+ const key = ev.affectedNode || (0, import_types60.serviceId)(ev.service);
17153
+ const cur = byKey.get(key);
17154
+ if (!cur) {
17155
+ byKey.set(key, { key, count: 1, latest: ev.timestamp, sampleMsg: ev.errorMessage });
17156
+ } else {
17157
+ cur.count += 1;
17158
+ if (ev.timestamp > cur.latest) {
17159
+ cur.latest = ev.timestamp;
17160
+ cur.sampleMsg = ev.errorMessage;
17161
+ }
17162
+ }
17163
+ }
17164
+ const rows = [...byKey.values()].sort((a, b) => b.count - a.count || b.latest.localeCompare(a.latest) || a.key.localeCompare(b.key)).slice(0, MAX_FACTS_PER_SECTION);
17165
+ const facts = rows.map((r) => ({
17166
+ text: `${r.key} \u2014 ${r.count} incident${r.count === 1 ? "" : "s"}, latest ${r.latest}: ${r.sampleMsg}`,
17167
+ provenance: import_types60.Provenance.OBSERVED
17168
+ }));
17169
+ return {
17170
+ heading: `Incidents across the system \u2014 ${incidents.length} recorded`,
17171
+ facts
17172
+ };
17173
+ }
17174
+ function buildOverviewSections(graph, incidents) {
17175
+ const nodeByType = /* @__PURE__ */ new Map();
17176
+ const services = [];
17177
+ graph.forEachNode((_id, attrs) => {
17178
+ const node = attrs;
17179
+ nodeByType.set(node.type, (nodeByType.get(node.type) ?? 0) + 1);
17180
+ if (node.type === import_types60.NodeType.ServiceNode) services.push(node.id);
17181
+ });
17182
+ const edgeByProv = /* @__PURE__ */ new Map();
17183
+ graph.forEachEdge((_id, attrs) => {
17184
+ const p = attrs.provenance;
17185
+ edgeByProv.set(p, (edgeByProv.get(p) ?? 0) + 1);
17186
+ });
17187
+ const sections = [];
17188
+ const count = (t) => nodeByType.get(t) ?? 0;
17189
+ const shapeFacts = [
17190
+ { text: `${graph.order} nodes, ${graph.size} edges` },
17191
+ {
17192
+ text: `${count(import_types60.NodeType.ServiceNode)} services, ${count(import_types60.NodeType.FileNode)} files, ${count(import_types60.NodeType.SymbolNode)} symbols, ${count(import_types60.NodeType.DatabaseNode)} databases`
17193
+ }
17194
+ ];
17195
+ for (const p of [import_types60.Provenance.EXTRACTED, import_types60.Provenance.OBSERVED, import_types60.Provenance.INFERRED, import_types60.Provenance.STALE]) {
17196
+ const n = edgeByProv.get(p) ?? 0;
17197
+ if (n > 0) shapeFacts.push({ text: `${n} ${p} edge${n === 1 ? "" : "s"}`, provenance: p });
17198
+ }
17199
+ sections.push({ heading: "System shape", facts: shapeFacts });
17200
+ const depCounts = services.map((s) => ({ s, n: getTransitiveDependencies(graph, s, 1).total })).filter((r) => r.n > 0).sort((a, b) => b.n - a.n || a.s.localeCompare(b.s)).slice(0, MAX_FACTS_PER_SECTION);
17201
+ if (depCounts.length > 0) {
17202
+ sections.push({
17203
+ heading: "Busiest services (by direct dependencies)",
17204
+ facts: depCounts.map((r) => ({
17205
+ text: `${r.s} \u2014 ${r.n} direct dependenc${r.n === 1 ? "y" : "ies"}`
17206
+ }))
17207
+ });
17208
+ }
17209
+ if (incidents && incidents.length > 0) {
17210
+ const incCount = /* @__PURE__ */ new Map();
17211
+ for (const ev of incidents) {
17212
+ const key = ev.affectedNode || (0, import_types60.serviceId)(ev.service);
17213
+ incCount.set(key, (incCount.get(key) ?? 0) + 1);
17214
+ }
17215
+ const top = [...incCount.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, MAX_FACTS_PER_SECTION);
17216
+ sections.push({
17217
+ heading: `Nodes with incidents \u2014 ${incidents.length} recorded`,
17218
+ facts: top.map(([k, n]) => ({
17219
+ text: `${k} \u2014 ${n} incident${n === 1 ? "" : "s"}`,
17220
+ provenance: import_types60.Provenance.OBSERVED
17221
+ }))
17222
+ });
17223
+ }
17224
+ const div = computeDivergences(graph);
17225
+ sections.push({
17226
+ heading: "Divergences",
17227
+ facts: [
17228
+ {
17229
+ text: div.totalAffected === 0 ? "None \u2014 declared code and observed runtime agree across the graph." : `${div.totalAffected} divergence${div.totalAffected === 1 ? "" : "s"} between declared code and observed runtime \u2014 ask "are there any divergences?" for the list.`
17230
+ }
17231
+ ]
17232
+ });
17233
+ return sections;
17234
+ }
17235
+ function buildGlobalSections(intent, graph, incidents) {
17236
+ switch (intent) {
17237
+ case "divergence":
17238
+ return [buildGlobalDivergenceSection(graph)];
17239
+ case "incidents":
17240
+ return [buildGlobalIncidentsSection(incidents)];
17241
+ case "overview":
17242
+ return buildOverviewSections(graph, incidents);
17243
+ default:
17244
+ return null;
17245
+ }
17246
+ }
17247
+ var SECTION_ORDER = {
17248
+ "root-cause": ["root-cause", "incidents", "blast", "observed", "divergence"],
17249
+ "blast-radius": ["blast", "dependencies", "observed", "incidents"],
17250
+ dependencies: ["dependencies", "observed", "blast"],
17251
+ observed: ["observed", "dependencies", "incidents"],
17252
+ incidents: ["incidents", "root-cause", "observed"],
17253
+ divergence: ["divergence", "observed", "dependencies", "incidents"],
17254
+ overview: ["dependencies", "observed", "blast", "incidents", "divergence"]
17255
+ };
17256
+ function buildSection(kind, graph, node, incidents, now) {
17257
+ switch (kind) {
17258
+ case "root-cause":
17259
+ return buildRootCauseSection(graph, node, incidents, now);
17260
+ case "dependencies":
17261
+ return buildDependenciesSection(graph, node);
17262
+ case "observed":
17263
+ return buildObservedSection(graph, node);
17264
+ case "blast":
17265
+ return buildBlastSection(graph, node);
17266
+ case "incidents":
17267
+ return buildIncidentsSection(node, incidents);
17268
+ case "divergence":
17269
+ return buildDivergenceSection(graph, node);
17270
+ }
17271
+ }
17272
+ function summarizeGlobal(intent, sections) {
17273
+ const lead = sections[0];
17274
+ switch (intent) {
17275
+ case "divergence": {
17276
+ const none = lead?.facts[0]?.text.startsWith("None") ?? true;
17277
+ return none ? "No divergences across the graph \u2014 declared code and observed runtime agree." : `${lead.heading} across the graph, highest-confidence first below.`;
17278
+ }
17279
+ case "incidents": {
17280
+ const none = lead?.facts[0]?.text.startsWith("None") ?? true;
17281
+ return none ? "No incidents recorded across the system \u2014 the OBSERVED incident store is empty." : `${lead.heading}, aggregated by node below.`;
17282
+ }
17283
+ case "overview":
17284
+ default:
17285
+ return `System overview across the whole graph: ${sections.length} view${sections.length === 1 ? "" : "s"} below \u2014 shape (nodes/edges by provenance), busiest services, incidents, and divergences.`;
17286
+ }
17287
+ }
17288
+ function summarize(question, intent, matched, primary, sections, scope) {
17289
+ if (scope === "global") {
17290
+ return summarizeGlobal(intent, sections);
17291
+ }
17292
+ if (!primary) {
17293
+ return `Nothing in "${question}" resolved to a node in the graph. Name a service, file, route, or table \u2014 e.g. \`ask "what does <service> depend on?"\`. For a graph-wide look, ask for an overview, divergences, or incidents.`;
17294
+ }
17295
+ const lead = sections[0];
17296
+ const others = matched.slice(1);
17297
+ const alsoNote = others.length ? ` Also matched: ${others.map((m) => m.nodeId).join(", ")}.` : "";
17298
+ let core;
17299
+ switch (intent) {
17300
+ case "root-cause": {
17301
+ const rc = sections.find((s) => s.heading === "Root cause (navigation)");
17302
+ if (rc) {
17303
+ core = rc.facts[0]?.text ?? "";
17304
+ } else if (lead) {
17305
+ core = `No root cause surfaced for ${primary} \u2014 it may be healthy, or the failure isn't recorded. Nearest context: ${lead.heading.toLowerCase()}.`;
17306
+ } else {
17307
+ core = `No root cause surfaced for ${primary} \u2014 it may be healthy.`;
17308
+ }
17309
+ break;
17310
+ }
17311
+ case "blast-radius":
17312
+ core = lead ? `${lead.heading} of ${primary}.` : `${primary} has no dependents \u2014 nothing else would break if it failed.`;
17313
+ break;
17314
+ case "dependencies":
17315
+ core = lead ? `${primary}: ${lead.heading.toLowerCase()} listed below.` : `${primary} has no declared dependencies in the graph.`;
17316
+ break;
17317
+ case "observed":
17318
+ core = lead ? `${primary} at runtime: ${lead.facts.length} OBSERVED fact${lead.facts.length === 1 ? "" : "s"}.` : `No runtime traffic OBSERVED for ${primary}.`;
17319
+ break;
17320
+ case "incidents":
17321
+ core = lead ? `${primary}: ${lead.heading.toLowerCase()}.` : `No incidents recorded against ${primary}.`;
17322
+ break;
17323
+ case "divergence":
17324
+ core = lead ? `${lead.heading} involving ${primary}.` : `No divergences involve ${primary} \u2014 declared and observed agree here.`;
17325
+ break;
17326
+ default:
17327
+ core = `${primary}: ${sections.length} view${sections.length === 1 ? "" : "s"} of its fused local context below.`;
17328
+ }
17329
+ return `${core}${alsoNote}`;
17330
+ }
17331
+ async function askGraph(graph, question, opts = {}) {
17332
+ const now = opts.now ?? Date.now();
17333
+ const maxNodes = opts.maxNodes ?? DEFAULT_MAX_NODES;
17334
+ const intent = classifyIntent(question);
17335
+ const matched = await resolveEntities(graph, question, opts.searchIndex, maxNodes);
17336
+ const primary = matched[0]?.nodeId;
17337
+ const sections = [];
17338
+ let scope;
17339
+ if (primary) {
17340
+ scope = "node";
17341
+ for (const kind of SECTION_ORDER[intent]) {
17342
+ const s = buildSection(kind, graph, primary, opts.incidents, now);
17343
+ if (s && s.facts.length > 0) sections.push(s);
17344
+ }
17345
+ } else {
17346
+ const global = buildGlobalSections(intent, graph, opts.incidents);
17347
+ if (global) {
17348
+ scope = "global";
17349
+ for (const s of global) if (s.facts.length > 0) sections.push(s);
17350
+ }
17351
+ }
17352
+ const answer = summarize(question, intent, matched, primary, sections, scope);
17353
+ const provSet = /* @__PURE__ */ new Set();
17354
+ for (const s of sections) for (const f of s.facts) if (f.provenance) provSet.add(f.provenance);
17355
+ const confidence = sections[0]?.facts[0]?.confidence;
17356
+ return import_types60.AskResultSchema.parse({
17357
+ question,
17358
+ intent,
17359
+ matched,
17360
+ ...primary ? { primaryNode: primary } : {},
17361
+ ...scope ? { scope } : {},
17362
+ sections,
17363
+ answer,
17364
+ ...confidence !== void 0 ? { confidence } : {},
17365
+ provenance: [...provSet]
17366
+ });
17367
+ }
17368
+
16736
17369
  // src/diff.ts
16737
17370
  init_cjs_shims();
16738
17371
  var import_node_fs38 = require("fs");
@@ -16867,7 +17500,7 @@ init_cjs_shims();
16867
17500
  var import_node_fs39 = require("fs");
16868
17501
  var import_node_os3 = __toESM(require("os"), 1);
16869
17502
  var import_node_path74 = __toESM(require("path"), 1);
16870
- var import_types60 = require("@neat.is/types");
17503
+ var import_types61 = require("@neat.is/types");
16871
17504
  var LOCK_TIMEOUT_MS = 5e3;
16872
17505
  var LOCK_RETRY_MS = 50;
16873
17506
  function neatHome() {
@@ -17121,10 +17754,10 @@ async function readRegistry() {
17121
17754
  throw err;
17122
17755
  }
17123
17756
  const parsed = JSON.parse(raw);
17124
- return import_types60.RegistryFileSchema.parse(parsed);
17757
+ return import_types61.RegistryFileSchema.parse(parsed);
17125
17758
  }
17126
17759
  async function writeRegistry(reg) {
17127
- const validated = import_types60.RegistryFileSchema.parse(reg);
17760
+ const validated = import_types61.RegistryFileSchema.parse(reg);
17128
17761
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
17129
17762
  }
17130
17763
  var ProjectNameCollisionError = class extends Error {
@@ -17637,15 +18270,15 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
17637
18270
 
17638
18271
  // src/connectors/index.ts
17639
18272
  init_cjs_shims();
17640
- var import_types61 = require("@neat.is/types");
18273
+ var import_types62 = require("@neat.is/types");
17641
18274
  var NO_ENV = "unknown";
17642
18275
  function staticCallSiteFor(graph, serviceName, targetNodeId) {
17643
18276
  if (!graph.hasNode(targetNodeId)) return void 0;
17644
18277
  const sites = [];
17645
18278
  for (const edgeId of graph.inboundEdges(targetNodeId)) {
17646
18279
  const edge = graph.getEdgeAttributes(edgeId);
17647
- if (edge.provenance !== import_types61.Provenance.EXTRACTED) continue;
17648
- const parsed = (0, import_types61.parseFileId)(edge.source);
18280
+ if (edge.provenance !== import_types62.Provenance.EXTRACTED) continue;
18281
+ const parsed = (0, import_types62.parseFileId)(edge.source);
17649
18282
  if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
17650
18283
  const site = { relPath: edge.evidence.file };
17651
18284
  if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
@@ -17656,7 +18289,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
17656
18289
  function routeCallSiteFor(graph, targetNodeId) {
17657
18290
  if (!graph.hasNode(targetNodeId)) return void 0;
17658
18291
  const attrs = graph.getNodeAttributes(targetNodeId);
17659
- if (attrs.type !== import_types61.NodeType.RouteNode || !attrs.path) return void 0;
18292
+ if (attrs.type !== import_types62.NodeType.RouteNode || !attrs.path) return void 0;
17660
18293
  const site = { relPath: attrs.path };
17661
18294
  if (attrs.line !== void 0) site.line = attrs.line;
17662
18295
  return site;
@@ -18154,10 +18787,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
18154
18787
  // src/connectors/supabase/map.ts
18155
18788
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
18156
18789
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
18157
- function targetFromRestPath(path92) {
18158
- const rpcMatch = REST_RPC_PATH_RE.exec(path92);
18790
+ function targetFromRestPath(path93) {
18791
+ const rpcMatch = REST_RPC_PATH_RE.exec(path93);
18159
18792
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
18160
- const tableMatch = REST_TABLE_PATH_RE.exec(path92);
18793
+ const tableMatch = REST_TABLE_PATH_RE.exec(path93);
18161
18794
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
18162
18795
  return null;
18163
18796
  }
@@ -18268,23 +18901,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
18268
18901
 
18269
18902
  // src/connectors/supabase/resolve.ts
18270
18903
  init_cjs_shims();
18271
- var import_types63 = require("@neat.is/types");
18904
+ var import_types64 = require("@neat.is/types");
18272
18905
  function createSupabaseResolveTarget(graph, config) {
18273
18906
  return (signal, _ctx) => {
18274
18907
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
18275
18908
  return null;
18276
18909
  }
18277
- const subResourceId = (0, import_types63.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
18910
+ const subResourceId = (0, import_types64.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
18278
18911
  if (graph.hasNode(subResourceId)) {
18279
- return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types63.EdgeType.CALLS };
18912
+ return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types64.EdgeType.CALLS };
18280
18913
  }
18281
- const bareResourceId = (0, import_types63.infraId)(signal.targetKind, signal.targetName);
18914
+ const bareResourceId = (0, import_types64.infraId)(signal.targetKind, signal.targetName);
18282
18915
  if (graph.hasNode(bareResourceId)) {
18283
- return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types63.EdgeType.CALLS };
18916
+ return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types64.EdgeType.CALLS };
18284
18917
  }
18285
- const projectLevelId = (0, import_types63.infraId)("supabase", config.nodeRef);
18918
+ const projectLevelId = (0, import_types64.infraId)("supabase", config.nodeRef);
18286
18919
  if (graph.hasNode(projectLevelId)) {
18287
- return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types63.EdgeType.CALLS };
18920
+ return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types64.EdgeType.CALLS };
18288
18921
  }
18289
18922
  return null;
18290
18923
  };
@@ -18377,7 +19010,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
18377
19010
 
18378
19011
  // src/connectors/railway/index.ts
18379
19012
  init_cjs_shims();
18380
- var import_types67 = require("@neat.is/types");
19013
+ var import_types68 = require("@neat.is/types");
18381
19014
 
18382
19015
  // src/connectors/railway/client.ts
18383
19016
  init_cjs_shims();
@@ -18528,7 +19161,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
18528
19161
  const out = [];
18529
19162
  graph.forEachNode((_id, attrs) => {
18530
19163
  const node = attrs;
18531
- if (node.type !== import_types67.NodeType.RouteNode) return;
19164
+ if (node.type !== import_types68.NodeType.RouteNode) return;
18532
19165
  const route = attrs;
18533
19166
  if (route.service !== serviceName) return;
18534
19167
  out.push({
@@ -18632,12 +19265,12 @@ function createRailwayResolveTarget(config) {
18632
19265
  const serviceName = config.serviceNameById[config.serviceId];
18633
19266
  if (!serviceName) return null;
18634
19267
  if (signal.targetKind === ROUTE_TARGET_KIND) {
18635
- return { targetNodeId: signal.targetName, serviceName, edgeType: import_types67.EdgeType.CALLS };
19268
+ return { targetNodeId: signal.targetName, serviceName, edgeType: import_types68.EdgeType.CALLS };
18636
19269
  }
18637
19270
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
18638
19271
  const peerName = config.serviceNameById[signal.targetName];
18639
19272
  if (!peerName) return null;
18640
- return { targetNodeId: (0, import_types67.serviceId)(peerName), serviceName, edgeType: import_types67.EdgeType.CONNECTS_TO };
19273
+ return { targetNodeId: (0, import_types68.serviceId)(peerName), serviceName, edgeType: import_types68.EdgeType.CONNECTS_TO };
18641
19274
  }
18642
19275
  return null;
18643
19276
  };
@@ -18761,9 +19394,9 @@ function parseFirebaseTargetName(targetName) {
18761
19394
  const secondSep = rest.indexOf(FIELD_SEP);
18762
19395
  if (secondSep === -1) return null;
18763
19396
  const method = rest.slice(0, secondSep);
18764
- const path92 = rest.slice(secondSep + 1);
18765
- if (!resourceName || !method || !path92) return null;
18766
- return { resourceName, method, path: path92 };
19397
+ const path93 = rest.slice(secondSep + 1);
19398
+ if (!resourceName || !method || !path93) return null;
19399
+ return { resourceName, method, path: path93 };
18767
19400
  }
18768
19401
  function resourceNameFor(type, labels) {
18769
19402
  if (!labels) return null;
@@ -18801,14 +19434,14 @@ function mapLogEntryToSignal(entry2) {
18801
19434
  if (!req) return null;
18802
19435
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
18803
19436
  const method = req.requestMethod.toUpperCase();
18804
- const path92 = pathFromRequestUrl(req.requestUrl);
18805
- if (path92 === null) return null;
19437
+ const path93 = pathFromRequestUrl(req.requestUrl);
19438
+ if (path93 === null) return null;
18806
19439
  const timestamp = entry2.timestamp;
18807
19440
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
18808
19441
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
18809
19442
  return {
18810
19443
  targetKind: resourceType,
18811
- targetName: packFirebaseTargetName({ resourceName, method, path: path92 }),
19444
+ targetName: packFirebaseTargetName({ resourceName, method, path: path93 }),
18812
19445
  callCount: 1,
18813
19446
  errorCount: isError ? 1 : 0,
18814
19447
  lastObservedIso: timestamp
@@ -18825,7 +19458,7 @@ function mapLogEntriesToSignals(entries) {
18825
19458
 
18826
19459
  // src/connectors/firebase/resolve.ts
18827
19460
  init_cjs_shims();
18828
- var import_types68 = require("@neat.is/types");
19461
+ var import_types69 = require("@neat.is/types");
18829
19462
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
18830
19463
  switch (resourceType) {
18831
19464
  case "cloud_function":
@@ -18840,7 +19473,7 @@ function routeEntriesFor(graph, serviceName) {
18840
19473
  const entries = [];
18841
19474
  graph.forEachNode((_id, attrs) => {
18842
19475
  const node = attrs;
18843
- if (node.type !== import_types68.NodeType.RouteNode) return;
19476
+ if (node.type !== import_types69.NodeType.RouteNode) return;
18844
19477
  const route = attrs;
18845
19478
  if (route.service !== serviceName) return;
18846
19479
  entries.push({
@@ -18872,7 +19505,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
18872
19505
  return {
18873
19506
  targetNodeId: match.routeNodeId,
18874
19507
  serviceName,
18875
- edgeType: import_types68.EdgeType.CALLS
19508
+ edgeType: import_types69.EdgeType.CALLS
18876
19509
  };
18877
19510
  };
18878
19511
  }
@@ -18899,7 +19532,7 @@ init_cjs_shims();
18899
19532
 
18900
19533
  // src/connectors/cloudflare/connector.ts
18901
19534
  init_cjs_shims();
18902
- var import_types70 = require("@neat.is/types");
19535
+ var import_types71 = require("@neat.is/types");
18903
19536
 
18904
19537
  // src/connectors/cloudflare/client.ts
18905
19538
  init_cjs_shims();
@@ -19015,7 +19648,7 @@ function mapEventToSignal(event) {
19015
19648
  if (Number.isNaN(observedAt.getTime())) return null;
19016
19649
  const statusCode = metadata?.statusCode;
19017
19650
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
19018
- const path92 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
19651
+ const path93 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
19019
19652
  return {
19020
19653
  targetKind: CLOUDFLARE_TARGET_KIND,
19021
19654
  targetName: scriptName,
@@ -19023,7 +19656,7 @@ function mapEventToSignal(event) {
19023
19656
  errorCount: isError ? 1 : 0,
19024
19657
  lastObservedIso: observedAt.toISOString(),
19025
19658
  method,
19026
- ...path92 ? { path: path92 } : {},
19659
+ ...path93 ? { path: path93 } : {},
19027
19660
  ...typeof statusCode === "number" ? { statusCode } : {},
19028
19661
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
19029
19662
  };
@@ -19063,19 +19696,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
19063
19696
  graph.forEachNode((id, attrs) => {
19064
19697
  if (found) return;
19065
19698
  const a = attrs;
19066
- if (a.type === import_types70.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
19699
+ if (a.type === import_types71.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
19067
19700
  found = id;
19068
19701
  }
19069
19702
  });
19070
19703
  return found;
19071
19704
  }
19072
- function findMatchingRouteNode(graph, serviceName, method, path92) {
19073
- const normalizedPath = normalizePathTemplate(path92);
19705
+ function findMatchingRouteNode(graph, serviceName, method, path93) {
19706
+ const normalizedPath = normalizePathTemplate(path93);
19074
19707
  let found = null;
19075
19708
  graph.forEachNode((id, attrs) => {
19076
19709
  if (found) return;
19077
19710
  const a = attrs;
19078
- if (a.type !== import_types70.NodeType.RouteNode || a.service !== serviceName) return;
19711
+ if (a.type !== import_types71.NodeType.RouteNode || a.service !== serviceName) return;
19079
19712
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
19080
19713
  const routeMethod = (a.method ?? "").toUpperCase();
19081
19714
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -19087,18 +19720,18 @@ function createCloudflareResolveTarget(config, graph) {
19087
19720
  return (signal) => {
19088
19721
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
19089
19722
  const scriptName = signal.targetName;
19090
- const { method, path: path92 } = signal;
19723
+ const { method, path: path93 } = signal;
19091
19724
  const resolveRouteGrain = (serviceName, wholeFileId) => {
19092
- if (!method || !path92) return wholeFileId;
19093
- return findMatchingRouteNode(graph, serviceName, method, path92) ?? wholeFileId;
19725
+ if (!method || !path93) return wholeFileId;
19726
+ return findMatchingRouteNode(graph, serviceName, method, path93) ?? wholeFileId;
19094
19727
  };
19095
19728
  const mapping = config.workers?.[scriptName];
19096
19729
  if (mapping) {
19097
- const wholeFileId = (0, import_types70.fileId)(mapping.service, mapping.entryFile);
19730
+ const wholeFileId = (0, import_types71.fileId)(mapping.service, mapping.entryFile);
19098
19731
  return {
19099
19732
  targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
19100
19733
  serviceName: mapping.service,
19101
- edgeType: import_types70.EdgeType.CALLS
19734
+ edgeType: import_types71.EdgeType.CALLS
19102
19735
  };
19103
19736
  }
19104
19737
  const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
@@ -19107,13 +19740,13 @@ function createCloudflareResolveTarget(config, graph) {
19107
19740
  return {
19108
19741
  targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
19109
19742
  serviceName: fileNode.service,
19110
- edgeType: import_types70.EdgeType.CALLS
19743
+ edgeType: import_types71.EdgeType.CALLS
19111
19744
  };
19112
19745
  }
19113
19746
  return {
19114
- targetNodeId: (0, import_types70.infraId)("cloudflare-worker", scriptName),
19747
+ targetNodeId: (0, import_types71.infraId)("cloudflare-worker", scriptName),
19115
19748
  serviceName: scriptName,
19116
- edgeType: import_types70.EdgeType.CALLS,
19749
+ edgeType: import_types71.EdgeType.CALLS,
19117
19750
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
19118
19751
  };
19119
19752
  };
@@ -19309,14 +19942,14 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
19309
19942
 
19310
19943
  // src/connectors/neon/resolve.ts
19311
19944
  init_cjs_shims();
19312
- var import_types74 = require("@neat.is/types");
19945
+ var import_types75 = require("@neat.is/types");
19313
19946
  function createNeonResolveTarget(config) {
19314
19947
  return (signal) => {
19315
19948
  if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
19316
19949
  return {
19317
- targetNodeId: (0, import_types74.infraId)("sql-table", signal.targetName),
19950
+ targetNodeId: (0, import_types75.infraId)("sql-table", signal.targetName),
19318
19951
  serviceName: config.serviceName,
19319
- edgeType: import_types74.EdgeType.CALLS,
19952
+ edgeType: import_types75.EdgeType.CALLS,
19320
19953
  ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
19321
19954
  };
19322
19955
  };
@@ -19442,9 +20075,9 @@ function parseCloudRunTargetName(targetName) {
19442
20075
  const secondSep = rest.indexOf(FIELD_SEP2);
19443
20076
  if (secondSep === -1) return null;
19444
20077
  const method = rest.slice(0, secondSep);
19445
- const path92 = rest.slice(secondSep + 1);
19446
- if (!serviceName || !method || !path92) return null;
19447
- return { serviceName, method, path: path92 };
20078
+ const path93 = rest.slice(secondSep + 1);
20079
+ if (!serviceName || !method || !path93) return null;
20080
+ return { serviceName, method, path: path93 };
19448
20081
  }
19449
20082
 
19450
20083
  // src/connectors/cloud-run/map.ts
@@ -19473,14 +20106,14 @@ function mapLogEntryToSignal2(entry2) {
19473
20106
  if (!req) return null;
19474
20107
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
19475
20108
  const method = req.requestMethod.toUpperCase();
19476
- const path92 = pathFromRequestUrl2(req.requestUrl);
19477
- if (path92 === null) return null;
20109
+ const path93 = pathFromRequestUrl2(req.requestUrl);
20110
+ if (path93 === null) return null;
19478
20111
  const timestamp = entry2.timestamp;
19479
20112
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
19480
20113
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
19481
20114
  return {
19482
20115
  targetKind: CLOUD_RUN_TARGET_KIND,
19483
- targetName: packCloudRunTargetName({ serviceName, method, path: path92 }),
20116
+ targetName: packCloudRunTargetName({ serviceName, method, path: path93 }),
19484
20117
  callCount: 1,
19485
20118
  errorCount: isError ? 1 : 0,
19486
20119
  lastObservedIso: timestamp
@@ -19497,14 +20130,14 @@ function mapLogEntriesToSignals2(entries) {
19497
20130
 
19498
20131
  // src/connectors/cloud-run/resolve.ts
19499
20132
  init_cjs_shims();
19500
- var import_types78 = require("@neat.is/types");
20133
+ var import_types79 = require("@neat.is/types");
19501
20134
  var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
19502
20135
  function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
19503
20136
  let found = null;
19504
20137
  graph.forEachNode((_id, attrs) => {
19505
20138
  if (found) return;
19506
20139
  const node = attrs;
19507
- if (node.type !== import_types78.NodeType.RouteNode) return;
20140
+ if (node.type !== import_types79.NodeType.RouteNode) return;
19508
20141
  const route = attrs;
19509
20142
  if (route.service !== serviceName || !route.pathTemplate) return;
19510
20143
  if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
@@ -19519,23 +20152,23 @@ function createCloudRunResolveTarget(graph, config) {
19519
20152
  if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
19520
20153
  const identity = parseCloudRunTargetName(signal.targetName);
19521
20154
  if (!identity) return null;
19522
- const { serviceName: gcpServiceName, method, path: path92 } = identity;
20155
+ const { serviceName: gcpServiceName, method, path: path93 } = identity;
19523
20156
  const mappedService = config.serviceMap?.[gcpServiceName];
19524
20157
  if (mappedService) {
19525
20158
  const routeNodeId = findMatchingRouteNode2(
19526
20159
  graph,
19527
20160
  mappedService,
19528
20161
  method,
19529
- normalizePathTemplate(path92)
20162
+ normalizePathTemplate(path93)
19530
20163
  );
19531
20164
  if (routeNodeId) {
19532
- return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types78.EdgeType.CALLS };
20165
+ return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types79.EdgeType.CALLS };
19533
20166
  }
19534
20167
  }
19535
20168
  return {
19536
- targetNodeId: (0, import_types78.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
20169
+ targetNodeId: (0, import_types79.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
19537
20170
  serviceName: mappedService ?? gcpServiceName,
19538
- edgeType: import_types78.EdgeType.CALLS,
20171
+ edgeType: import_types79.EdgeType.CALLS,
19539
20172
  ensureInfraNode: {
19540
20173
  kind: CLOUD_RUN_SERVICE_INFRA_KIND,
19541
20174
  name: gcpServiceName,
@@ -19576,7 +20209,7 @@ function createCloudRunConnector(graph, config = {}) {
19576
20209
 
19577
20210
  // src/connectors/render/index.ts
19578
20211
  init_cjs_shims();
19579
- var import_types81 = require("@neat.is/types");
20212
+ var import_types82 = require("@neat.is/types");
19580
20213
 
19581
20214
  // src/connectors/render/types.ts
19582
20215
  init_cjs_shims();
@@ -19654,7 +20287,7 @@ function buildRenderRouteIndex(graph, serviceName) {
19654
20287
  const out = [];
19655
20288
  graph.forEachNode((_id, attrs) => {
19656
20289
  const node = attrs;
19657
- if (node.type !== import_types81.NodeType.RouteNode) return;
20290
+ if (node.type !== import_types82.NodeType.RouteNode) return;
19658
20291
  const route = attrs;
19659
20292
  if (route.service !== serviceName) return;
19660
20293
  out.push({
@@ -19739,7 +20372,7 @@ function mapRenderRequestLogsToSignals(entries, routeIndex) {
19739
20372
  function createRenderResolveTarget(config) {
19740
20373
  return (signal) => {
19741
20374
  if (signal.targetKind === ROUTE_TARGET_KIND2) {
19742
- return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types81.EdgeType.CALLS };
20375
+ return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types82.EdgeType.CALLS };
19743
20376
  }
19744
20377
  return null;
19745
20378
  };
@@ -19877,21 +20510,21 @@ function mapInsightsToSignals(rows, observedAtIso) {
19877
20510
 
19878
20511
  // src/connectors/planetscale/resolve.ts
19879
20512
  init_cjs_shims();
19880
- var import_types85 = require("@neat.is/types");
20513
+ var import_types86 = require("@neat.is/types");
19881
20514
  var PLANETSCALE_DATABASE_KIND = "planetscale-database";
19882
20515
  function createPlanetscaleResolveTarget(graph, config) {
19883
20516
  const databaseName = `${config.organization}/${config.database}`;
19884
20517
  return (signal, _ctx) => {
19885
20518
  if (signal.targetKind !== PLANETSCALE_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
19886
- const tableId = (0, import_types85.infraId)("sql-table", signal.targetName);
20519
+ const tableId = (0, import_types86.infraId)("sql-table", signal.targetName);
19887
20520
  if (graph.hasNode(tableId)) {
19888
- return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: import_types85.EdgeType.CALLS };
20521
+ return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: import_types86.EdgeType.CALLS };
19889
20522
  }
19890
- const providerId = (0, import_types85.infraId)(PLANETSCALE_DATABASE_KIND, databaseName);
20523
+ const providerId = (0, import_types86.infraId)(PLANETSCALE_DATABASE_KIND, databaseName);
19891
20524
  return {
19892
20525
  targetNodeId: providerId,
19893
20526
  serviceName: config.serviceName,
19894
- edgeType: import_types85.EdgeType.CALLS,
20527
+ edgeType: import_types86.EdgeType.CALLS,
19895
20528
  ensureInfraNode: { kind: PLANETSCALE_DATABASE_KIND, name: databaseName, provider: "planetscale" }
19896
20529
  };
19897
20530
  };
@@ -20156,7 +20789,7 @@ function mapBuildsToSignals(builds, serviceName) {
20156
20789
 
20157
20790
  // src/connectors/eas/resolve.ts
20158
20791
  init_cjs_shims();
20159
- var import_types90 = require("@neat.is/types");
20792
+ var import_types91 = require("@neat.is/types");
20160
20793
  var NO_ENV2 = "unknown";
20161
20794
  var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
20162
20795
  var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
@@ -20172,8 +20805,8 @@ function configBasenamesForPhase(phase) {
20172
20805
  function configNodeService(graph, configNodeId) {
20173
20806
  for (const edgeId of graph.inboundEdges(configNodeId)) {
20174
20807
  const edge = graph.getEdgeAttributes(edgeId);
20175
- if (edge.type !== import_types90.EdgeType.CONFIGURED_BY) continue;
20176
- const parsed = (0, import_types90.parseFileId)(edge.source);
20808
+ if (edge.type !== import_types91.EdgeType.CONFIGURED_BY) continue;
20809
+ const parsed = (0, import_types91.parseFileId)(edge.source);
20177
20810
  if (parsed) return parsed.service;
20178
20811
  }
20179
20812
  return null;
@@ -20184,7 +20817,7 @@ function findConfigNode(graph, basenames, serviceName) {
20184
20817
  graph.forEachNode((id, attrs) => {
20185
20818
  if (scoped) return;
20186
20819
  const node = attrs;
20187
- if (node.type !== import_types90.NodeType.ConfigNode) return;
20820
+ if (node.type !== import_types91.NodeType.ConfigNode) return;
20188
20821
  if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
20189
20822
  if (anyMatch === null) anyMatch = id;
20190
20823
  if (configNodeService(graph, id) === serviceName) scoped = id;
@@ -20201,13 +20834,13 @@ function createEasResolveTarget(graph) {
20201
20834
  if (basenames.length > 0) {
20202
20835
  const configNodeId = findConfigNode(graph, basenames, serviceName);
20203
20836
  if (configNodeId) {
20204
- return { targetNodeId: configNodeId, serviceName, edgeType: import_types90.EdgeType.CALLS };
20837
+ return { targetNodeId: configNodeId, serviceName, edgeType: import_types91.EdgeType.CALLS };
20205
20838
  }
20206
20839
  }
20207
20840
  return {
20208
20841
  targetNodeId: resolveFusedServiceId(graph, serviceName, NO_ENV2),
20209
20842
  serviceName,
20210
- edgeType: import_types90.EdgeType.CALLS
20843
+ edgeType: import_types91.EdgeType.CALLS
20211
20844
  };
20212
20845
  };
20213
20846
  }
@@ -21011,11 +21644,11 @@ function registerRoutes(scope, ctx) {
21011
21644
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
21012
21645
  const parsed = [];
21013
21646
  for (const c of candidates) {
21014
- const r = import_types93.DivergenceTypeSchema.safeParse(c);
21647
+ const r = import_types94.DivergenceTypeSchema.safeParse(c);
21015
21648
  if (!r.success) {
21016
21649
  return reply.code(400).send({
21017
21650
  error: `unknown divergence type "${c}"`,
21018
- allowed: import_types93.DivergenceTypeSchema.options
21651
+ allowed: import_types94.DivergenceTypeSchema.options
21019
21652
  });
21020
21653
  }
21021
21654
  parsed.push(r.data);
@@ -21257,6 +21890,18 @@ function registerRoutes(scope, ctx) {
21257
21890
  matches: matches.slice(0, safeLimit)
21258
21891
  };
21259
21892
  });
21893
+ scope.get("/graph/ask", async (req, reply) => {
21894
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
21895
+ if (!proj) return;
21896
+ const question = (req.query.q ?? "").trim();
21897
+ if (!question) return reply.code(400).send({ error: "query parameter `q` is required" });
21898
+ const epath = errorsPathFor(proj);
21899
+ const incidents = epath ? await readErrorEvents(epath) : [];
21900
+ return askGraph(proj.graph, question, {
21901
+ ...proj.searchIndex ? { searchIndex: proj.searchIndex } : {},
21902
+ incidents
21903
+ });
21904
+ });
21260
21905
  scope.get(
21261
21906
  "/graph/diff",
21262
21907
  async (req, reply) => {
@@ -21357,7 +22002,7 @@ function registerRoutes(scope, ctx) {
21357
22002
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
21358
22003
  let violations = await log.readAll();
21359
22004
  if (req.query.severity) {
21360
- const sev = import_types93.PolicySeveritySchema.safeParse(req.query.severity);
22005
+ const sev = import_types94.PolicySeveritySchema.safeParse(req.query.severity);
21361
22006
  if (!sev.success) {
21362
22007
  return reply.code(400).send({
21363
22008
  error: "invalid severity",
@@ -21396,7 +22041,7 @@ function registerRoutes(scope, ctx) {
21396
22041
  scope.post("/policies/check", async (req, reply) => {
21397
22042
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
21398
22043
  if (!proj) return;
21399
- const parsed = import_types93.PoliciesCheckBodySchema.safeParse(req.body ?? {});
22044
+ const parsed = import_types94.PoliciesCheckBodySchema.safeParse(req.body ?? {});
21400
22045
  if (!parsed.success) {
21401
22046
  return reply.code(400).send({
21402
22047
  error: "invalid /policies/check body",
@@ -21729,7 +22374,7 @@ var import_node_fs41 = require("fs");
21729
22374
  var import_node_path76 = __toESM(require("path"), 1);
21730
22375
 
21731
22376
  // src/daemon.ts
21732
- var import_types94 = require("@neat.is/types");
22377
+ var import_types95 = require("@neat.is/types");
21733
22378
  function daemonJsonPath(scanPath) {
21734
22379
  return import_node_path77.default.join(scanPath, "neat-out", "daemon.json");
21735
22380
  }
@@ -23655,8 +24300,8 @@ function scriptHasShellChain(script) {
23655
24300
  function entryFromScript(script) {
23656
24301
  if (!script) return void 0;
23657
24302
  if (scriptHasShellChain(script)) return void 0;
23658
- const tokens = script.split(/\s+/).filter((t) => t.length > 0);
23659
- for (const token of tokens) {
24303
+ const tokens2 = script.split(/\s+/).filter((t) => t.length > 0);
24304
+ for (const token of tokens2) {
23660
24305
  const lower = token.toLowerCase();
23661
24306
  if (SCRIPT_LAUNCHERS.has(lower)) continue;
23662
24307
  const cleaned = token.startsWith("./") ? token.slice(2) : token;
@@ -25681,7 +26326,7 @@ async function extractAndPersist(opts) {
25681
26326
  }
25682
26327
  async function applyInstallersOver(services, project, options = {}) {
25683
26328
  const resolveManager = options.resolveManager ?? detectPackageManager;
25684
- const runInstall = options.runInstall ?? runPackageManagerInstall;
26329
+ const runInstall2 = options.runInstall ?? runPackageManagerInstall;
25685
26330
  let instrumented = 0;
25686
26331
  let already = 0;
25687
26332
  let libOnly = 0;
@@ -25804,7 +26449,7 @@ async function applyInstallersOver(services, project, options = {}) {
25804
26449
  const packageManagerInstalls = [];
25805
26450
  for (const cmd of installPlans.values()) {
25806
26451
  console.log(`running \`${cmd.pm} ${cmd.args.join(" ")}\` in ${cmd.cwd}`);
25807
- const result = await runInstall(cmd);
26452
+ const result = await runInstall2(cmd);
25808
26453
  packageManagerInstalls.push(result);
25809
26454
  if (result.exitCode !== 0) {
25810
26455
  console.error(
@@ -26783,6 +27428,9 @@ function claudeSettingsPath() {
26783
27428
  function installedHookPath() {
26784
27429
  return import_node_path87.default.join(neatHome3(), "hooks", HOOK_FILENAME);
26785
27430
  }
27431
+ function gateFlagPath() {
27432
+ return import_node_path87.default.join(neatHome3(), "hooks", "gate-enabled");
27433
+ }
26786
27434
  function isNeatSearchEntry(entry2) {
26787
27435
  return (entry2.hooks ?? []).some(
26788
27436
  (h) => typeof h.command === "string" && h.command.includes(HOOK_FILENAME)
@@ -26845,13 +27493,30 @@ async function runHooks(opts) {
26845
27493
  };
26846
27494
  await import_node_fs52.promises.mkdir(import_node_path87.default.dirname(settingsFile), { recursive: true });
26847
27495
  await import_node_fs52.promises.writeFile(settingsFile, JSON.stringify(merged, null, 2) + "\n", "utf8");
26848
- console.log(`neat hooks: installed the search-nudge hook`);
27496
+ const flag = gateFlagPath();
27497
+ if (opts.gate) {
27498
+ await import_node_fs52.promises.mkdir(import_node_path87.default.dirname(flag), { recursive: true });
27499
+ await import_node_fs52.promises.writeFile(flag, "1\n", "utf8");
27500
+ } else {
27501
+ await import_node_fs52.promises.rm(flag, { force: true });
27502
+ }
27503
+ const mode = opts.gate ? "GATE (deny search until you ask the graph)" : "nudge (search still runs)";
27504
+ console.log(`neat hooks: installed the search hook in ${opts.gate ? "gate" : "nudge"} mode`);
26849
27505
  console.log(` script: ${scriptPath}`);
26850
27506
  console.log(` settings: ${settingsFile} (PreToolUse \u2192 ${HOOK_MATCHER})`);
26851
27507
  console.log(` guidance: ${guidePath}`);
27508
+ console.log(` mode: ${mode}`);
26852
27509
  console.log("");
26853
- console.log("restart Claude Code to load the hook. On a Grep/Glob or a Bash grep,");
26854
- console.log("your agent will now be nudged to query NEAT first.");
27510
+ if (opts.gate) {
27511
+ console.log("restart Claude Code to load the hook. A Grep/Glob or Bash grep is now DENIED");
27512
+ console.log('until you run `neat ask "<question>"` (or the ask MCP tool) once this session;');
27513
+ console.log("after that, search is allowed as a fallback. Set NEAT_SEARCH_GATE=0 to fall");
27514
+ console.log("back to nudge-only without re-running.");
27515
+ } else {
27516
+ console.log("restart Claude Code to load the hook. On a Grep/Glob or a Bash grep,");
27517
+ console.log("your agent will now be nudged to query NEAT first (the search still runs).");
27518
+ console.log("Re-run with --gate to hard-force the graph-first orientation.");
27519
+ }
26855
27520
  console.log("");
26856
27521
  console.log("The hook is Claude-Code-specific. For agents on other harnesses, paste");
26857
27522
  console.log(`the guidance above into your project instructions (CLAUDE.md / AGENTS.md).`);
@@ -26863,28 +27528,36 @@ async function runHooks(opts) {
26863
27528
  function usage() {
26864
27529
  console.log("neat hooks \u2014 wire NEAT into your agent so it queries the graph before grepping");
26865
27530
  console.log("");
26866
- console.log(" --apply install the Claude Code search-nudge hook and write the");
27531
+ console.log(" --apply install the Claude Code search hook and write the");
26867
27532
  console.log(" graph-first guidance to ~/.neat/, merging into");
26868
27533
  console.log(" ~/.claude/settings.json without touching your other hooks");
27534
+ console.log(" --gate with --apply, enable hard-gate mode: DENY Grep/Glob/grep-Bash");
27535
+ console.log(" until `neat ask` has run this session (default is nudge-only).");
27536
+ console.log(" Toggle off at run time with NEAT_SEARCH_GATE=0.");
26869
27537
  console.log(" --print-hook print the hook script to stdout");
26870
27538
  console.log(" --print-guide print the agent-agnostic graph-first guidance to stdout");
26871
27539
  console.log(" --print-settings print the settings.json PreToolUse block --apply would add");
26872
27540
  console.log("");
26873
- console.log("The hook is a gentle, non-blocking nudge \u2014 searches still run. It is");
26874
- console.log("Claude-Code-specific; other harnesses get the same steer from the guidance.");
27541
+ console.log("By default the hook is a gentle, non-blocking nudge \u2014 searches still run.");
27542
+ console.log("--gate turns it into a hard forcing mechanism. It is Claude-Code-specific;");
27543
+ console.log("other harnesses get the same steer from the graph-first guidance.");
26875
27544
  }
26876
27545
  async function runHooksCommand(args) {
26877
27546
  const opts = {
26878
27547
  apply: false,
26879
27548
  printHook: false,
26880
27549
  printGuide: false,
26881
- printSettings: false
27550
+ printSettings: false,
27551
+ gate: false
26882
27552
  };
26883
27553
  for (const arg of args) {
26884
27554
  switch (arg) {
26885
27555
  case "--apply":
26886
27556
  opts.apply = true;
26887
27557
  break;
27558
+ case "--gate":
27559
+ opts.gate = true;
27560
+ break;
26888
27561
  case "--print-hook":
26889
27562
  opts.printHook = true;
26890
27563
  break;
@@ -26913,11 +27586,151 @@ async function runHooksCommand(args) {
26913
27586
  }
26914
27587
  }
26915
27588
 
26916
- // src/codex-cli.ts
27589
+ // src/claude-cli.ts
26917
27590
  init_cjs_shims();
26918
27591
  var import_node_path88 = __toESM(require("path"), 1);
26919
- var import_node_os6 = __toESM(require("os"), 1);
26920
27592
  var import_node_fs53 = require("fs");
27593
+ var NEAT_SECTION_HEADING = "## neat";
27594
+ var NEAT_DIRECTIVE_BODY = `This project has NEAT wired in: a live, fused semantic graph of the system \u2014
27595
+ code and runtime behaviour (OpenTelemetry) in one model, every fact tagged with
27596
+ its provenance (EXTRACTED / OBSERVED / INFERRED / STALE) and a confidence.
27597
+
27598
+ For ANY question about this system's behaviour, dependencies, failures, root
27599
+ cause, or blast radius, call \`neat ask\` FIRST \u2014 before Read, Grep, Glob, or Bash:
27600
+
27601
+ neat ask "why is checkout failing?"
27602
+ neat ask "what breaks if I change the orders table?"
27603
+ neat ask "what does the payments service depend on at runtime?"
27604
+
27605
+ \`ask\` resolves your question to graph nodes and routes it to the right traversal
27606
+ \u2014 root cause, dependencies, observed runtime calls, incidents, divergences,
27607
+ blast radius \u2014 and returns one compact, provenance-tagged answer. The same door
27608
+ is the \`ask\` MCP tool (\`mcp__neat__ask\`) when NEAT's MCP server is wired in.
27609
+
27610
+ The graph is live and fused: it is faster and more accurate than scanning files,
27611
+ and it can tell you what the system actually does at runtime, not only what the
27612
+ source declares. Fall back to Read/Grep only when the graph does not have what
27613
+ you need \u2014 comments, string literals, config minutiae. Ask the graph first,
27614
+ then scan.
27615
+
27616
+ If \`neat ask\` errors, the daemon may not be running (\`neat list\`) \u2014 start it
27617
+ with \`neat <path>\`, then re-ask.`;
27618
+ function neatSection() {
27619
+ return `${NEAT_SECTION_HEADING}
27620
+
27621
+ ${NEAT_DIRECTIVE_BODY}
27622
+ `;
27623
+ }
27624
+ function claudeMdPath() {
27625
+ const override = process.env.NEAT_CLAUDE_MD;
27626
+ if (override && override.length > 0) return import_node_path88.default.resolve(override);
27627
+ return import_node_path88.default.join(process.cwd(), "CLAUDE.md");
27628
+ }
27629
+ function splitAroundSection(raw) {
27630
+ const lines = raw.split("\n");
27631
+ const startIdx = lines.findIndex((l) => l.replace(/\s+$/, "") === NEAT_SECTION_HEADING);
27632
+ if (startIdx === -1) {
27633
+ return { before: raw.replace(/\n*$/, ""), after: "", found: false };
27634
+ }
27635
+ let endIdx = lines.length;
27636
+ for (let i = startIdx + 1; i < lines.length; i++) {
27637
+ if (/^#{1,2}\s+/.test(lines[i] ?? "")) {
27638
+ endIdx = i;
27639
+ break;
27640
+ }
27641
+ }
27642
+ const before = lines.slice(0, startIdx).join("\n").replace(/\n*$/, "");
27643
+ const after = lines.slice(endIdx).join("\n").replace(/^\n*/, "");
27644
+ return { before, after, found: true };
27645
+ }
27646
+ function compose(before, after) {
27647
+ const parts = [];
27648
+ if (before.length > 0) parts.push(before);
27649
+ parts.push(neatSection().replace(/\n+$/, ""));
27650
+ if (after.length > 0) parts.push(after);
27651
+ return parts.join("\n\n").replace(/\n*$/, "") + "\n";
27652
+ }
27653
+ async function readIfExists2(file) {
27654
+ try {
27655
+ return await import_node_fs53.promises.readFile(file, "utf8");
27656
+ } catch (err) {
27657
+ if (err.code === "ENOENT") return null;
27658
+ throw err;
27659
+ }
27660
+ }
27661
+ async function runInstall() {
27662
+ const file = claudeMdPath();
27663
+ const raw = await readIfExists2(file) ?? "";
27664
+ const { before, after, found } = splitAroundSection(raw);
27665
+ const next = compose(before, after);
27666
+ await import_node_fs53.promises.mkdir(import_node_path88.default.dirname(file), { recursive: true });
27667
+ await import_node_fs53.promises.writeFile(file, next, "utf8");
27668
+ const verb = raw.length === 0 ? "created" : found ? "refreshed" : "added";
27669
+ console.log(`neat claude: ${verb} the \`${NEAT_SECTION_HEADING}\` section in ${file}`);
27670
+ console.log("Your agent will now reach for `neat ask` before Read/Grep/Bash. Restart the");
27671
+ console.log("session (or reload CLAUDE.md) to pick it up.");
27672
+ return { exitCode: 0 };
27673
+ }
27674
+ async function runUninstall() {
27675
+ const file = claudeMdPath();
27676
+ const raw = await readIfExists2(file);
27677
+ if (raw === null) {
27678
+ console.log(`neat claude: no CLAUDE.md at ${file} \u2014 nothing to remove.`);
27679
+ return { exitCode: 0 };
27680
+ }
27681
+ const { before, after, found } = splitAroundSection(raw);
27682
+ if (!found) {
27683
+ console.log(`neat claude: no \`${NEAT_SECTION_HEADING}\` section in ${file} \u2014 nothing to remove.`);
27684
+ return { exitCode: 0 };
27685
+ }
27686
+ const remaining = [before, after].filter((s) => s.length > 0).join("\n\n");
27687
+ const next = remaining.length > 0 ? remaining.replace(/\n*$/, "") + "\n" : "";
27688
+ await import_node_fs53.promises.writeFile(file, next, "utf8");
27689
+ console.log(`neat claude: removed the \`${NEAT_SECTION_HEADING}\` section from ${file}.`);
27690
+ return { exitCode: 0 };
27691
+ }
27692
+ function usage2() {
27693
+ console.log("neat claude \u2014 make the query-first directive always-on in Claude Code");
27694
+ console.log("");
27695
+ console.log(" install write (or refresh) a `## neat` section in ./CLAUDE.md so your");
27696
+ console.log(" agent reaches for `neat ask` before Read/Grep/Bash");
27697
+ console.log(" uninstall remove the `## neat` section from ./CLAUDE.md");
27698
+ console.log(" print print the directive block to stdout (for a manual paste)");
27699
+ console.log("");
27700
+ console.log("Idempotent: re-running install replaces its own section, never duplicates it.");
27701
+ console.log("Target file overridable via NEAT_CLAUDE_MD.");
27702
+ }
27703
+ async function runClaudeCommand(args) {
27704
+ const sub = args[0];
27705
+ if (sub === "-h" || sub === "--help" || sub === void 0) {
27706
+ usage2();
27707
+ return sub === void 0 ? 2 : 0;
27708
+ }
27709
+ try {
27710
+ switch (sub) {
27711
+ case "install":
27712
+ return (await runInstall()).exitCode;
27713
+ case "uninstall":
27714
+ return (await runUninstall()).exitCode;
27715
+ case "print":
27716
+ process.stdout.write(neatSection());
27717
+ return 0;
27718
+ default:
27719
+ console.error(`neat claude: unknown subcommand "${sub}"`);
27720
+ usage2();
27721
+ return 2;
27722
+ }
27723
+ } catch (err) {
27724
+ console.error(`neat claude: ${err.message}`);
27725
+ return 1;
27726
+ }
27727
+ }
27728
+
27729
+ // src/codex-cli.ts
27730
+ init_cjs_shims();
27731
+ var import_node_path89 = __toESM(require("path"), 1);
27732
+ var import_node_os6 = __toESM(require("os"), 1);
27733
+ var import_node_fs54 = require("fs");
26921
27734
  var import_node_util = require("util");
26922
27735
  var import_smol_toml6 = require("smol-toml");
26923
27736
  var CODEX_MCP_SERVER = {
@@ -26935,14 +27748,14 @@ var NEAT_GRAPH_FIRST_START = "<!-- neat:graph-first -->";
26935
27748
  var NEAT_GRAPH_FIRST_END = "<!-- /neat:graph-first -->";
26936
27749
  function codexConfigPath() {
26937
27750
  const override = process.env.NEAT_CODEX_CONFIG;
26938
- if (override && override.length > 0) return import_node_path88.default.resolve(override);
27751
+ if (override && override.length > 0) return import_node_path89.default.resolve(override);
26939
27752
  const home = process.env.HOME ?? process.env.USERPROFILE ?? import_node_os6.default.homedir();
26940
- return import_node_path88.default.join(home, ".codex", "config.toml");
27753
+ return import_node_path89.default.join(home, ".codex", "config.toml");
26941
27754
  }
26942
27755
  function agentsFilePath() {
26943
27756
  const override = process.env.NEAT_CODEX_AGENTS;
26944
- if (override && override.length > 0) return import_node_path88.default.resolve(override);
26945
- return import_node_path88.default.join(process.cwd(), "AGENTS.md");
27757
+ if (override && override.length > 0) return import_node_path89.default.resolve(override);
27758
+ return import_node_path89.default.join(process.cwd(), "AGENTS.md");
26946
27759
  }
26947
27760
  function isTableHeader(line) {
26948
27761
  return /^\s*\[\[?[^\]]+\]\]?\s*$/.test(line);
@@ -27076,7 +27889,7 @@ async function runCodex(opts) {
27076
27889
  const agentsPath = agentsFilePath();
27077
27890
  let configRaw = "";
27078
27891
  try {
27079
- configRaw = await import_node_fs53.promises.readFile(configPath, "utf8");
27892
+ configRaw = await import_node_fs54.promises.readFile(configPath, "utf8");
27080
27893
  } catch (err) {
27081
27894
  if (err.code !== "ENOENT") {
27082
27895
  console.error(`neat codex: failed to read ${configPath} \u2014 ${err.message}`);
@@ -27085,7 +27898,7 @@ async function runCodex(opts) {
27085
27898
  }
27086
27899
  let agentsRaw = "";
27087
27900
  try {
27088
- agentsRaw = await import_node_fs53.promises.readFile(agentsPath, "utf8");
27901
+ agentsRaw = await import_node_fs54.promises.readFile(agentsPath, "utf8");
27089
27902
  } catch (err) {
27090
27903
  if (err.code !== "ENOENT") {
27091
27904
  console.error(`neat codex: failed to read ${agentsPath} \u2014 ${err.message}`);
@@ -27125,15 +27938,15 @@ async function runCodex(opts) {
27125
27938
  return { exitCode: 0 };
27126
27939
  }
27127
27940
  if (config.changed) {
27128
- await import_node_fs53.promises.mkdir(import_node_path88.default.dirname(configPath), { recursive: true });
27129
- await import_node_fs53.promises.writeFile(configPath, config.text, "utf8");
27941
+ await import_node_fs54.promises.mkdir(import_node_path89.default.dirname(configPath), { recursive: true });
27942
+ await import_node_fs54.promises.writeFile(configPath, config.text, "utf8");
27130
27943
  console.log(`neat codex: wrote [mcp_servers.neat] to ${configPath}`);
27131
27944
  } else {
27132
27945
  console.log(`neat codex: ${configPath} already has NEAT's MCP server`);
27133
27946
  }
27134
27947
  if (agents.changed) {
27135
- await import_node_fs53.promises.mkdir(import_node_path88.default.dirname(agentsPath), { recursive: true });
27136
- await import_node_fs53.promises.writeFile(agentsPath, agents.text, "utf8");
27948
+ await import_node_fs54.promises.mkdir(import_node_path89.default.dirname(agentsPath), { recursive: true });
27949
+ await import_node_fs54.promises.writeFile(agentsPath, agents.text, "utf8");
27137
27950
  console.log(`neat codex: wrote the graph-first block to ${agentsPath}`);
27138
27951
  } else {
27139
27952
  console.log(`neat codex: ${agentsPath} already has the graph-first block`);
@@ -27143,7 +27956,7 @@ async function runCodex(opts) {
27143
27956
  console.log("points the server at the local daemon \u2014 edit it for a non-default one.");
27144
27957
  return { exitCode: 0 };
27145
27958
  }
27146
- function usage2() {
27959
+ function usage3() {
27147
27960
  console.log("neat codex \u2014 install NEAT into the OpenAI Codex CLI (MCP server + AGENTS.md)");
27148
27961
  console.log("");
27149
27962
  console.log(" (no flag) plan: print what would change, write nothing");
@@ -27171,11 +27984,11 @@ async function runCodexCommand(args) {
27171
27984
  break;
27172
27985
  case "-h":
27173
27986
  case "--help":
27174
- usage2();
27987
+ usage3();
27175
27988
  return 0;
27176
27989
  default:
27177
27990
  console.error(`neat codex: unknown flag "${arg}"`);
27178
- usage2();
27991
+ usage3();
27179
27992
  return 2;
27180
27993
  }
27181
27994
  }
@@ -27190,9 +28003,9 @@ async function runCodexCommand(args) {
27190
28003
 
27191
28004
  // src/editors-cli.ts
27192
28005
  init_cjs_shims();
27193
- var import_node_path89 = __toESM(require("path"), 1);
28006
+ var import_node_path90 = __toESM(require("path"), 1);
27194
28007
  var import_node_os7 = __toESM(require("os"), 1);
27195
- var import_node_fs54 = require("fs");
28008
+ var import_node_fs55 = require("fs");
27196
28009
  var import_node_util2 = require("util");
27197
28010
  var jsonc = __toESM(require("jsonc-parser"), 1);
27198
28011
  var NEAT_MCP_SERVER = {
@@ -27216,17 +28029,17 @@ function homeDir() {
27216
28029
  }
27217
28030
  function xdgConfigDir() {
27218
28031
  const xdg = process.env.XDG_CONFIG_HOME;
27219
- return xdg && xdg.length > 0 ? import_node_path89.default.resolve(xdg) : import_node_path89.default.join(homeDir(), ".config");
28032
+ return xdg && xdg.length > 0 ? import_node_path90.default.resolve(xdg) : import_node_path90.default.join(homeDir(), ".config");
27220
28033
  }
27221
28034
  function envOverride(name) {
27222
28035
  const v = process.env[name];
27223
- return v && v.length > 0 ? import_node_path89.default.resolve(v) : void 0;
28036
+ return v && v.length > 0 ? import_node_path90.default.resolve(v) : void 0;
27224
28037
  }
27225
28038
  var CURSOR_CLIENT = {
27226
28039
  id: "cursor",
27227
28040
  label: "Cursor",
27228
28041
  docsUrl: "https://docs.cursor.com/context/mcp",
27229
- mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ?? import_node_path89.default.join(homeDir(), ".cursor", "mcp.json"),
28042
+ mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ?? import_node_path90.default.join(homeDir(), ".cursor", "mcp.json"),
27230
28043
  mcpContainerKey: "mcpServers",
27231
28044
  format: "json",
27232
28045
  // Cursor still reads a single `.cursorrules` at the project root (the modern
@@ -27238,7 +28051,7 @@ var DEVIN_CLIENT = {
27238
28051
  id: "devin",
27239
28052
  label: "Devin Desktop (Cascade)",
27240
28053
  docsUrl: "https://docs.devin.ai/desktop/cascade/mcp",
27241
- mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ?? import_node_path89.default.join(homeDir(), ".codeium", "windsurf", "mcp_config.json"),
28054
+ mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ?? import_node_path90.default.join(homeDir(), ".codeium", "windsurf", "mcp_config.json"),
27242
28055
  mcpContainerKey: "mcpServers",
27243
28056
  format: "json",
27244
28057
  rulesFileName: ".windsurfrules"
@@ -27247,7 +28060,7 @@ var GEMINI_CLIENT = {
27247
28060
  id: "gemini",
27248
28061
  label: "Gemini CLI",
27249
28062
  docsUrl: "https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md",
27250
- mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ?? import_node_path89.default.join(homeDir(), ".gemini", "settings.json"),
28063
+ mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ?? import_node_path90.default.join(homeDir(), ".gemini", "settings.json"),
27251
28064
  mcpContainerKey: "mcpServers",
27252
28065
  format: "json",
27253
28066
  rulesFileName: "GEMINI.md"
@@ -27256,7 +28069,7 @@ var QWEN_CLIENT = {
27256
28069
  id: "qwen",
27257
28070
  label: "Qwen Code",
27258
28071
  docsUrl: "https://qwenlm.github.io/qwen-code-docs/en/users/features/mcp/",
27259
- mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ?? import_node_path89.default.join(homeDir(), ".qwen", "settings.json"),
28072
+ mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ?? import_node_path90.default.join(homeDir(), ".qwen", "settings.json"),
27260
28073
  mcpContainerKey: "mcpServers",
27261
28074
  format: "json",
27262
28075
  rulesFileName: "QWEN.md"
@@ -27265,7 +28078,7 @@ var AMAZONQ_CLIENT = {
27265
28078
  id: "amazonq",
27266
28079
  label: "Amazon Q Developer CLI",
27267
28080
  docsUrl: "https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-mcp-configuration.html",
27268
- mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ?? import_node_path89.default.join(homeDir(), ".aws", "amazonq", "mcp.json"),
28081
+ mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ?? import_node_path90.default.join(homeDir(), ".aws", "amazonq", "mcp.json"),
27269
28082
  mcpContainerKey: "mcpServers",
27270
28083
  format: "json"
27271
28084
  };
@@ -27273,7 +28086,7 @@ var ROOCODE_CLIENT = {
27273
28086
  id: "roocode",
27274
28087
  label: "Roo Code",
27275
28088
  docsUrl: "https://roocodeinc.github.io/Roo-Code/features/mcp/using-mcp-in-roo",
27276
- mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ?? import_node_path89.default.join(process.cwd(), ".roo", "mcp.json"),
28089
+ mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ?? import_node_path90.default.join(process.cwd(), ".roo", "mcp.json"),
27277
28090
  mcpContainerKey: "mcpServers",
27278
28091
  format: "json"
27279
28092
  };
@@ -27286,9 +28099,9 @@ var ZED_CLIENT = {
27286
28099
  if (override) return override;
27287
28100
  if (process.platform === "win32") {
27288
28101
  const appData = process.env.APPDATA;
27289
- if (appData && appData.length > 0) return import_node_path89.default.join(appData, "Zed", "settings.json");
28102
+ if (appData && appData.length > 0) return import_node_path90.default.join(appData, "Zed", "settings.json");
27290
28103
  }
27291
- return import_node_path89.default.join(homeDir(), ".config", "zed", "settings.json");
28104
+ return import_node_path90.default.join(homeDir(), ".config", "zed", "settings.json");
27292
28105
  },
27293
28106
  mcpContainerKey: "context_servers",
27294
28107
  format: "jsonc",
@@ -27298,7 +28111,7 @@ var OPENCODE_CLIENT = {
27298
28111
  id: "opencode",
27299
28112
  label: "OpenCode",
27300
28113
  docsUrl: "https://opencode.ai/docs/mcp-servers/",
27301
- mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ?? import_node_path89.default.join(xdgConfigDir(), "opencode", "opencode.json"),
28114
+ mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ?? import_node_path90.default.join(xdgConfigDir(), "opencode", "opencode.json"),
27302
28115
  mcpContainerKey: "mcp",
27303
28116
  format: "json",
27304
28117
  serverEntry: NEAT_OPENCODE_SERVER,
@@ -27308,7 +28121,7 @@ var CRUSH_CLIENT = {
27308
28121
  id: "crush",
27309
28122
  label: "Crush",
27310
28123
  docsUrl: "https://charmbracelet-crush.mintlify.app/configuration/mcp",
27311
- mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ?? import_node_path89.default.join(xdgConfigDir(), "crush", "crush.json"),
28124
+ mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ?? import_node_path90.default.join(xdgConfigDir(), "crush", "crush.json"),
27312
28125
  mcpContainerKey: "mcp",
27313
28126
  format: "json",
27314
28127
  serverEntry: NEAT_CRUSH_SERVER,
@@ -27371,7 +28184,7 @@ async function planMcp(client, mcpPath) {
27371
28184
  const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
27372
28185
  let raw = "";
27373
28186
  try {
27374
- raw = await import_node_fs54.promises.readFile(mcpPath, "utf8");
28187
+ raw = await import_node_fs55.promises.readFile(mcpPath, "utf8");
27375
28188
  } catch (err) {
27376
28189
  const e = err;
27377
28190
  if (e.code === "ENOENT") {
@@ -27413,7 +28226,7 @@ async function runEditorInstall(client, opts) {
27413
28226
  const mcpPath = client.mcpConfigPath();
27414
28227
  const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
27415
28228
  const hasRules = typeof client.rulesFileName === "string";
27416
- const rulesPath = hasRules ? import_node_path89.default.join(opts.projectDir, client.rulesFileName) : "";
28229
+ const rulesPath = hasRules ? import_node_path90.default.join(opts.projectDir, client.rulesFileName) : "";
27417
28230
  const mcp = await planMcp(client, mcpPath);
27418
28231
  if (mcp === null) return { exitCode: 1 };
27419
28232
  let existingRules = "";
@@ -27422,7 +28235,7 @@ async function runEditorInstall(client, opts) {
27422
28235
  let block = "";
27423
28236
  if (hasRules) {
27424
28237
  try {
27425
- existingRules = await import_node_fs54.promises.readFile(rulesPath, "utf8");
28238
+ existingRules = await import_node_fs55.promises.readFile(rulesPath, "utf8");
27426
28239
  } catch (err) {
27427
28240
  if (err.code !== "ENOENT") {
27428
28241
  console.error(`neat ${client.id}: failed to read ${rulesPath} \u2014 ${err.message}`);
@@ -27456,11 +28269,11 @@ async function runEditorInstall(client, opts) {
27456
28269
  );
27457
28270
  return { exitCode: 0 };
27458
28271
  }
27459
- await import_node_fs54.promises.mkdir(import_node_path89.default.dirname(mcpPath), { recursive: true });
27460
- await import_node_fs54.promises.writeFile(mcpPath, mcp.text, "utf8");
28272
+ await import_node_fs55.promises.mkdir(import_node_path90.default.dirname(mcpPath), { recursive: true });
28273
+ await import_node_fs55.promises.writeFile(mcpPath, mcp.text, "utf8");
27461
28274
  if (hasRules) {
27462
- await import_node_fs54.promises.mkdir(import_node_path89.default.dirname(rulesPath), { recursive: true });
27463
- await import_node_fs54.promises.writeFile(rulesPath, newRules, "utf8");
28275
+ await import_node_fs55.promises.mkdir(import_node_path90.default.dirname(rulesPath), { recursive: true });
28276
+ await import_node_fs55.promises.writeFile(rulesPath, newRules, "utf8");
27464
28277
  }
27465
28278
  console.log(`neat ${client.id}: wired NEAT into ${client.label}`);
27466
28279
  console.log(` MCP server: ${mcpPath} (${client.mcpContainerKey}.neat \u2192 npx -y @neat.is/mcp)`);
@@ -27473,7 +28286,7 @@ async function runEditorInstall(client, opts) {
27473
28286
  function indent(text) {
27474
28287
  return text.split("\n").map((line) => line.length > 0 ? ` ${line}` : line).join("\n");
27475
28288
  }
27476
- function usage3(client) {
28289
+ function usage4(client) {
27477
28290
  const hasRules = typeof client.rulesFileName === "string";
27478
28291
  console.log(
27479
28292
  hasRules ? `neat ${client.id} \u2014 install NEAT's MCP server + graph-first guidance into ${client.label}` : `neat ${client.id} \u2014 install NEAT's MCP server into ${client.label}`
@@ -27504,11 +28317,11 @@ async function runEditorCommand(clientId, args, projectDir = process.cwd()) {
27504
28317
  break;
27505
28318
  case "-h":
27506
28319
  case "--help":
27507
- usage3(client);
28320
+ usage4(client);
27508
28321
  return 0;
27509
28322
  default:
27510
28323
  console.error(`neat ${client.id}: unknown flag "${arg}"`);
27511
- usage3(client);
28324
+ usage4(client);
27512
28325
  return 2;
27513
28326
  }
27514
28327
  }
@@ -27523,11 +28336,11 @@ async function runEditorCommand(clientId, args, projectDir = process.cwd()) {
27523
28336
 
27524
28337
  // src/monitor.ts
27525
28338
  init_cjs_shims();
27526
- var import_types96 = require("@neat.is/types");
28339
+ var import_types97 = require("@neat.is/types");
27527
28340
 
27528
28341
  // src/cli-client.ts
27529
28342
  init_cjs_shims();
27530
- var import_types95 = require("@neat.is/types");
28343
+ var import_types96 = require("@neat.is/types");
27531
28344
  var HttpError = class extends Error {
27532
28345
  constructor(status2, message, responseBody = "") {
27533
28346
  super(message);
@@ -27552,10 +28365,10 @@ function createHttpClient(baseUrl, bearerToken) {
27552
28365
  const root = baseUrl.replace(/\/$/, "");
27553
28366
  const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
27554
28367
  return {
27555
- async get(path92) {
28368
+ async get(path93) {
27556
28369
  let res;
27557
28370
  try {
27558
- res = await fetch(`${root}${path92}`, {
28371
+ res = await fetch(`${root}${path93}`, {
27559
28372
  headers: { ...authHeader }
27560
28373
  });
27561
28374
  } catch (err) {
@@ -27567,16 +28380,16 @@ function createHttpClient(baseUrl, bearerToken) {
27567
28380
  const body = await res.text().catch(() => "");
27568
28381
  throw new HttpError(
27569
28382
  res.status,
27570
- `${res.status} ${res.statusText} on GET ${path92}: ${body}`,
28383
+ `${res.status} ${res.statusText} on GET ${path93}: ${body}`,
27571
28384
  body
27572
28385
  );
27573
28386
  }
27574
28387
  return await res.json();
27575
28388
  },
27576
- async post(path92, body) {
28389
+ async post(path93, body) {
27577
28390
  let res;
27578
28391
  try {
27579
- res = await fetch(`${root}${path92}`, {
28392
+ res = await fetch(`${root}${path93}`, {
27580
28393
  method: "POST",
27581
28394
  headers: { "content-type": "application/json", ...authHeader },
27582
28395
  body: JSON.stringify(body)
@@ -27590,7 +28403,7 @@ function createHttpClient(baseUrl, bearerToken) {
27590
28403
  const text = await res.text().catch(() => "");
27591
28404
  throw new HttpError(
27592
28405
  res.status,
27593
- `${res.status} ${res.statusText} on POST ${path92}: ${text}`,
28406
+ `${res.status} ${res.statusText} on POST ${path93}: ${text}`,
27594
28407
  text
27595
28408
  );
27596
28409
  }
@@ -27604,12 +28417,12 @@ function projectPath(project, suffix) {
27604
28417
  }
27605
28418
  async function runRootCause(client, input) {
27606
28419
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
27607
- const path92 = projectPath(
28420
+ const path93 = projectPath(
27608
28421
  input.project,
27609
28422
  `/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
27610
28423
  );
27611
28424
  try {
27612
- const result = await client.get(path92);
28425
+ const result = await client.get(path93);
27613
28426
  const arrowPath = result.traversalPath.join(" \u2190 ");
27614
28427
  const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
27615
28428
  const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
@@ -27635,12 +28448,12 @@ async function runRootCause(client, input) {
27635
28448
  }
27636
28449
  async function runBlastRadius(client, input) {
27637
28450
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
27638
- const path92 = projectPath(
28451
+ const path93 = projectPath(
27639
28452
  input.project,
27640
28453
  `/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
27641
28454
  );
27642
28455
  try {
27643
- const result = await client.get(path92);
28456
+ const result = await client.get(path93);
27644
28457
  if (result.totalAffected === 0) {
27645
28458
  return {
27646
28459
  summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
@@ -27669,17 +28482,17 @@ async function runBlastRadius(client, input) {
27669
28482
  }
27670
28483
  }
27671
28484
  function formatBlastEntry(n) {
27672
- const tag = n.edgeProvenance === import_types95.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
28485
+ const tag = n.edgeProvenance === import_types96.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
27673
28486
  return ` \u2022 ${n.nodeId} (distance ${n.distance}, ${n.edgeProvenance})${tag}`;
27674
28487
  }
27675
28488
  async function runDependencies(client, input) {
27676
28489
  const depth = input.depth ?? 3;
27677
- const path92 = projectPath(
28490
+ const path93 = projectPath(
27678
28491
  input.project,
27679
28492
  `/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
27680
28493
  );
27681
28494
  try {
27682
- const result = await client.get(path92);
28495
+ const result = await client.get(path93);
27683
28496
  if (result.total === 0) {
27684
28497
  return {
27685
28498
  summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
@@ -27726,7 +28539,7 @@ async function runObservedDependencies(client, input) {
27726
28539
  if (result.observed) {
27727
28540
  return {
27728
28541
  summary: `${input.nodeId} makes no outbound runtime calls, but OTel has observed it receiving traffic on ${result.inboundObservedCount} inbound call path${result.inboundObservedCount === 1 ? "" : "s"} \u2014 it's a pure receiver.`,
27729
- provenance: import_types95.Provenance.OBSERVED
28542
+ provenance: import_types96.Provenance.OBSERVED
27730
28543
  };
27731
28544
  }
27732
28545
  const note = result.hasExtractedOutbound ? " Static (EXTRACTED) dependencies exist but no runtime traffic has been seen \u2014 is OTel running?" : "";
@@ -27736,7 +28549,7 @@ async function runObservedDependencies(client, input) {
27736
28549
  return {
27737
28550
  summary: `${input.nodeId} has ${result.dependencies.length} runtime dependenc${result.dependencies.length === 1 ? "y" : "ies"} confirmed by OTel.`,
27738
28551
  block: blockLines.join("\n"),
27739
- provenance: import_types95.Provenance.OBSERVED
28552
+ provenance: import_types96.Provenance.OBSERVED
27740
28553
  };
27741
28554
  } catch (err) {
27742
28555
  if (err instanceof HttpError && err.status === 404) {
@@ -27771,9 +28584,9 @@ function formatDuration(ms) {
27771
28584
  return `${Math.round(h / 24)}d`;
27772
28585
  }
27773
28586
  async function runIncidents(client, input) {
27774
- const path92 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
28587
+ const path93 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
27775
28588
  try {
27776
- const body = await client.get(path92);
28589
+ const body = await client.get(path93);
27777
28590
  const events = body.events;
27778
28591
  if (events.length === 0) {
27779
28592
  return {
@@ -27790,7 +28603,7 @@ async function runIncidents(client, input) {
27790
28603
  return {
27791
28604
  summary: `${target} has ${body.total} recorded incident${body.total === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
27792
28605
  block: blockLines.join("\n"),
27793
- provenance: import_types95.Provenance.OBSERVED
28606
+ provenance: import_types96.Provenance.OBSERVED
27794
28607
  };
27795
28608
  } catch (err) {
27796
28609
  if (err instanceof HttpError && err.status === 404) {
@@ -27899,7 +28712,7 @@ async function runStaleEdges(client, input) {
27899
28712
  return {
27900
28713
  summary: `${events.length} stale-edge transition${events.length === 1 ? "" : "s"} recorded${input.edgeType ? ` for ${input.edgeType}` : ""}.`,
27901
28714
  block: blockLines.join("\n"),
27902
- provenance: import_types95.Provenance.STALE
28715
+ provenance: import_types96.Provenance.STALE
27903
28716
  };
27904
28717
  }
27905
28718
  async function runPolicies(client, input) {
@@ -28014,6 +28827,33 @@ async function runDivergences(client, input) {
28014
28827
  provenance: "composite (EXTRACTED + OBSERVED)"
28015
28828
  };
28016
28829
  }
28830
+ async function runAsk(client, input) {
28831
+ const result = await client.get(
28832
+ projectPath(input.project, `/graph/ask?q=${encodeURIComponent(input.question)}`)
28833
+ );
28834
+ const blockLines = [];
28835
+ if (result.matched.length > 0) {
28836
+ blockLines.push(
28837
+ `Matched: ${result.matched.map((m) => `${m.nodeId} [${m.via} ${m.score.toFixed(2)}]`).join(", ")}`
28838
+ );
28839
+ blockLines.push(`Intent: ${result.intent}`);
28840
+ } else if (result.scope === "global") {
28841
+ blockLines.push(`Graph-wide answer (${result.intent}) \u2014 no entity named.`);
28842
+ }
28843
+ for (const section of result.sections) {
28844
+ blockLines.push("", section.heading + ":");
28845
+ for (const fact of section.facts) {
28846
+ const tag = fact.provenance ? ` [${fact.provenance}${fact.confidence !== void 0 ? ` ${fact.confidence.toFixed(2)}` : ""}]` : fact.confidence !== void 0 ? ` [confidence ${fact.confidence.toFixed(2)}]` : "";
28847
+ blockLines.push(` \u2022 ${fact.text}${tag}`);
28848
+ }
28849
+ }
28850
+ return {
28851
+ summary: result.answer,
28852
+ block: blockLines.join("\n").trim(),
28853
+ ...result.confidence !== void 0 ? { confidence: result.confidence } : {},
28854
+ ...result.provenance.length > 0 ? { provenance: result.provenance } : {}
28855
+ };
28856
+ }
28017
28857
  function formatFooter(confidence, provenance) {
28018
28858
  const c = confidence === void 0 ? "n/a" : confidence.toFixed(2);
28019
28859
  const p = provenance === void 0 ? "n/a" : Array.isArray(provenance) ? [...new Set(provenance)].join(", ") : provenance;
@@ -28058,10 +28898,10 @@ async function pushSnapshotToRemote(input) {
28058
28898
 
28059
28899
  // src/monitor.ts
28060
28900
  var OBSERVED_DEP_EDGE_TYPES = /* @__PURE__ */ new Set([
28061
- import_types96.EdgeType.CALLS,
28062
- import_types96.EdgeType.CONNECTS_TO,
28063
- import_types96.EdgeType.PUBLISHES_TO,
28064
- import_types96.EdgeType.CONSUMES_FROM
28901
+ import_types97.EdgeType.CALLS,
28902
+ import_types97.EdgeType.CONNECTS_TO,
28903
+ import_types97.EdgeType.PUBLISHES_TO,
28904
+ import_types97.EdgeType.CONSUMES_FROM
28065
28905
  ]);
28066
28906
  function divergenceKey(d) {
28067
28907
  const column = "column" in d && d.column ? d.column : "";
@@ -28106,7 +28946,7 @@ function formatDivergenceLine2(d) {
28106
28946
  }
28107
28947
  }
28108
28948
  function formatStaleLine(edgeId) {
28109
- const parsed = (0, import_types96.parseEdgeId)(edgeId);
28949
+ const parsed = (0, import_types97.parseEdgeId)(edgeId);
28110
28950
  if (parsed) {
28111
28951
  return `\u22EF stale ${parsed.source} \u2192 ${parsed.target} (observed edge went quiet)`;
28112
28952
  }
@@ -28119,7 +28959,7 @@ function divergenceJson(d) {
28119
28959
  return JSON.stringify({ kind: "divergence", ...d });
28120
28960
  }
28121
28961
  function staleJson(edgeId) {
28122
- const parsed = (0, import_types96.parseEdgeId)(edgeId);
28962
+ const parsed = (0, import_types97.parseEdgeId)(edgeId);
28123
28963
  return JSON.stringify({
28124
28964
  kind: "stale",
28125
28965
  edgeId,
@@ -28189,7 +29029,7 @@ var MonitorEmitter = class {
28189
29029
  // ignores non-OBSERVED edges and non-dependency edge types (structural
28190
29030
  // ownership), so only real runtime dependencies reach stdout.
28191
29031
  emitObservedEdge(edge) {
28192
- if (edge.provenance !== import_types96.Provenance.OBSERVED) return false;
29032
+ if (edge.provenance !== import_types97.Provenance.OBSERVED) return false;
28193
29033
  if (!OBSERVED_DEP_EDGE_TYPES.has(edge.type)) return false;
28194
29034
  const key = `edge|${edge.id}`;
28195
29035
  if (this.seen.has(key)) return false;
@@ -28347,7 +29187,7 @@ async function runMonitor(opts) {
28347
29187
  case "edge-added": {
28348
29188
  const payload = safeParse(frame.data);
28349
29189
  const edge = payload?.edge;
28350
- if (edge && edge.provenance === import_types96.Provenance.OBSERVED) {
29190
+ if (edge && edge.provenance === import_types97.Provenance.OBSERVED) {
28351
29191
  emitter.emitObservedEdge(edge);
28352
29192
  divergences.schedule();
28353
29193
  }
@@ -28427,7 +29267,7 @@ function sleep(ms, signal) {
28427
29267
 
28428
29268
  // src/cli-verbs.ts
28429
29269
  init_cjs_shims();
28430
- var import_node_path90 = __toESM(require("path"), 1);
29270
+ var import_node_path91 = __toESM(require("path"), 1);
28431
29271
  async function resolveProjectEntry(opts) {
28432
29272
  const entries = await listProjects();
28433
29273
  if (opts.project) {
@@ -28437,7 +29277,7 @@ async function resolveProjectEntry(opts) {
28437
29277
  const cwd = opts.cwd ?? process.cwd();
28438
29278
  const resolvedCwd = await normalizeProjectPath(cwd);
28439
29279
  for (const entry2 of entries) {
28440
- if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path90.default.sep}`)) {
29280
+ if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path91.default.sep}`)) {
28441
29281
  return entry2;
28442
29282
  }
28443
29283
  }
@@ -28590,7 +29430,7 @@ async function runSync(opts) {
28590
29430
  }
28591
29431
 
28592
29432
  // src/cli.ts
28593
- var import_types97 = require("@neat.is/types");
29433
+ var import_types98 = require("@neat.is/types");
28594
29434
  function isNpxInvocation() {
28595
29435
  if (process.env.npm_command === "exec") return true;
28596
29436
  const execpath = process.env.npm_execpath ?? "";
@@ -28602,7 +29442,7 @@ function isNpxInvocation() {
28602
29442
  function commandPrefix() {
28603
29443
  return isNpxInvocation() ? "npx neat.is" : "neat";
28604
29444
  }
28605
- function usage4() {
29445
+ function usage5() {
28606
29446
  const neat = commandPrefix();
28607
29447
  console.log("Installed via npx? Prefix commands with `npx neat.is`, or install once: `npm i -g neat.is`.");
28608
29448
  console.log("");
@@ -28650,13 +29490,21 @@ function usage4() {
28650
29490
  console.log(" --print-config print the JSON snippet to stdout");
28651
29491
  console.log(" --apply merge mcpServers.neat into ~/.claude.json");
28652
29492
  console.log(" hooks Wire NEAT into your agent so it queries the graph before");
28653
- console.log(" grepping. Installs a gentle Claude Code search-nudge hook and");
28654
- console.log(" writes agent-agnostic graph-first guidance for other harnesses.");
29493
+ console.log(" grepping. Installs a Claude Code search hook (nudge by default,");
29494
+ console.log(" or a hard gate) and agent-agnostic graph-first guidance.");
28655
29495
  console.log(" Flags:");
28656
29496
  console.log(" --apply install the hook + guidance");
29497
+ console.log(" --gate with --apply: DENY search until `neat ask`");
29498
+ console.log(" has run this session (default: nudge-only)");
28657
29499
  console.log(" --print-hook print the hook script");
28658
29500
  console.log(" --print-guide print the graph-first guidance");
28659
29501
  console.log(" --print-settings print the settings.json block --apply adds");
29502
+ console.log(" claude Write the query-first directive into ./CLAUDE.md as a `## neat`");
29503
+ console.log(" section so an agent reaches for `neat ask` before Read/Grep/Bash.");
29504
+ console.log(" Subcommands:");
29505
+ console.log(" install write (or refresh) the `## neat` section");
29506
+ console.log(" uninstall remove the `## neat` section");
29507
+ console.log(" print print the directive block to stdout");
28660
29508
  console.log(" codex Install NEAT into the OpenAI Codex CLI: add [mcp_servers.neat]");
28661
29509
  console.log(" to ~/.codex/config.toml and the graph-first block to ./AGENTS.md.");
28662
29510
  console.log(" Plan by default; --apply to write.");
@@ -28733,6 +29581,10 @@ function usage4() {
28733
29581
  console.log(" run time; the config file is written owner-only (0600).");
28734
29582
  console.log("");
28735
29583
  console.log("query commands (mirror the MCP tools, ADR-050):");
29584
+ console.log(" ask <question> Plain-language door: resolves the question to");
29585
+ console.log(" nodes and routes it to the right traversal, with");
29586
+ console.log(" a compact provenance-tagged answer. Reach here first.");
29587
+ console.log(` example: ${neat} ask "why is checkout failing?"`);
28736
29588
  console.log(" root-cause <node-id> Walk inbound edges to find what broke first.");
28737
29589
  console.log(` example: ${neat} root-cause service:<name>`);
28738
29590
  console.log(" blast-radius <node-id> BFS inbound \u2014 the dependents that break if this dies.");
@@ -28952,7 +29804,7 @@ async function buildPatchSections(services, project) {
28952
29804
  }
28953
29805
  async function runInit(opts) {
28954
29806
  const written = [];
28955
- const stat = await import_node_fs55.promises.stat(opts.scanPath).catch(() => null);
29807
+ const stat = await import_node_fs56.promises.stat(opts.scanPath).catch(() => null);
28956
29808
  if (!stat || !stat.isDirectory()) {
28957
29809
  console.error(`neat init: ${opts.scanPath} is not a directory`);
28958
29810
  return { exitCode: 2, writtenFiles: written };
@@ -28961,13 +29813,13 @@ async function runInit(opts) {
28961
29813
  printDiscoveryReport(opts, services);
28962
29814
  const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
28963
29815
  const patch = renderPatch(sections);
28964
- const patchPath = import_node_path91.default.join(opts.scanPath, "neat.patch");
29816
+ const patchPath = import_node_path92.default.join(opts.scanPath, "neat.patch");
28965
29817
  if (opts.dryRun) {
28966
- await import_node_fs55.promises.writeFile(patchPath, patch, "utf8");
29818
+ await import_node_fs56.promises.writeFile(patchPath, patch, "utf8");
28967
29819
  written.push(patchPath);
28968
29820
  console.log(`dry-run: patch written to ${patchPath}`);
28969
- const gitignorePath = import_node_path91.default.join(opts.scanPath, ".gitignore");
28970
- const gitignoreExists = await import_node_fs55.promises.stat(gitignorePath).then(() => true).catch(() => false);
29821
+ const gitignorePath = import_node_path92.default.join(opts.scanPath, ".gitignore");
29822
+ const gitignoreExists = await import_node_fs56.promises.stat(gitignorePath).then(() => true).catch(() => false);
28971
29823
  const verb = gitignoreExists ? "append" : "create";
28972
29824
  console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
28973
29825
  console.log("rerun without --dry-run to register and snapshot.");
@@ -28978,9 +29830,9 @@ async function runInit(opts) {
28978
29830
  const graph = getGraph(graphKey);
28979
29831
  const projectPaths = pathsForProject(
28980
29832
  graphKey,
28981
- import_node_path91.default.join(opts.scanPath, "neat-out")
29833
+ import_node_path92.default.join(opts.scanPath, "neat-out")
28982
29834
  );
28983
- const errorsPath = import_node_path91.default.join(import_node_path91.default.dirname(opts.outPath), import_node_path91.default.basename(projectPaths.errorsPath));
29835
+ const errorsPath = import_node_path92.default.join(import_node_path92.default.dirname(opts.outPath), import_node_path92.default.basename(projectPaths.errorsPath));
28984
29836
  const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
28985
29837
  await saveGraphToDisk(graph, opts.outPath);
28986
29838
  written.push(opts.outPath);
@@ -29059,7 +29911,7 @@ async function runInit(opts) {
29059
29911
  console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
29060
29912
  }
29061
29913
  } else {
29062
- await import_node_fs55.promises.writeFile(patchPath, patch, "utf8");
29914
+ await import_node_fs56.promises.writeFile(patchPath, patch, "utf8");
29063
29915
  written.push(patchPath);
29064
29916
  }
29065
29917
  }
@@ -29099,9 +29951,9 @@ var CLAUDE_SKILL_CONFIG = {
29099
29951
  };
29100
29952
  function claudeConfigPath() {
29101
29953
  const override = process.env.NEAT_CLAUDE_CONFIG;
29102
- if (override && override.length > 0) return import_node_path91.default.resolve(override);
29954
+ if (override && override.length > 0) return import_node_path92.default.resolve(override);
29103
29955
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
29104
- return import_node_path91.default.join(home, ".claude.json");
29956
+ return import_node_path92.default.join(home, ".claude.json");
29105
29957
  }
29106
29958
  async function runSkill(opts) {
29107
29959
  const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
@@ -29113,7 +29965,7 @@ async function runSkill(opts) {
29113
29965
  const target = claudeConfigPath();
29114
29966
  let existing = {};
29115
29967
  try {
29116
- existing = JSON.parse(await import_node_fs55.promises.readFile(target, "utf8"));
29968
+ existing = JSON.parse(await import_node_fs56.promises.readFile(target, "utf8"));
29117
29969
  } catch (err) {
29118
29970
  if (err.code !== "ENOENT") {
29119
29971
  console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
@@ -29125,8 +29977,8 @@ async function runSkill(opts) {
29125
29977
  ...existing,
29126
29978
  mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
29127
29979
  };
29128
- await import_node_fs55.promises.mkdir(import_node_path91.default.dirname(target), { recursive: true });
29129
- await import_node_fs55.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
29980
+ await import_node_fs56.promises.mkdir(import_node_path92.default.dirname(target), { recursive: true });
29981
+ await import_node_fs56.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
29130
29982
  console.log(`neat skill: wrote mcpServers.neat to ${target}`);
29131
29983
  console.log("restart Claude Code to pick up the new MCP server.");
29132
29984
  console.log("");
@@ -29153,7 +30005,7 @@ async function main() {
29153
30005
  const argv = process.argv.slice(2);
29154
30006
  const cmd0 = argv[0];
29155
30007
  if (cmd0 === "-h" || cmd0 === "--help") {
29156
- usage4();
30008
+ usage5();
29157
30009
  process.exit(0);
29158
30010
  }
29159
30011
  if (cmd0 === "--version" || cmd0 === "-v" || cmd0 === "version") {
@@ -29175,6 +30027,11 @@ async function main() {
29175
30027
  if (code !== 0) process.exit(code);
29176
30028
  return;
29177
30029
  }
30030
+ if (cmd0 === "claude") {
30031
+ const code = await runClaudeCommand(argv.slice(1));
30032
+ if (code !== 0) process.exit(code);
30033
+ return;
30034
+ }
29178
30035
  const EDITOR_VERBS = [
29179
30036
  "cursor",
29180
30037
  "devin",
@@ -29205,19 +30062,19 @@ async function main() {
29205
30062
  const target = positional[0];
29206
30063
  if (!target) {
29207
30064
  console.error("neat init: missing <path>");
29208
- usage4();
30065
+ usage5();
29209
30066
  process.exit(2);
29210
30067
  }
29211
30068
  if (apply6 && dryRun) {
29212
30069
  console.error("neat init: --apply and --dry-run are mutually exclusive");
29213
30070
  process.exit(2);
29214
30071
  }
29215
- const scanPath = import_node_path91.default.resolve(target);
30072
+ const scanPath = import_node_path92.default.resolve(target);
29216
30073
  const projectExplicit = parsed.project !== null;
29217
- const projectName = projectExplicit ? project : import_node_path91.default.basename(scanPath);
30074
+ const projectName = projectExplicit ? project : import_node_path92.default.basename(scanPath);
29218
30075
  const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
29219
- const fallback = pathsForProject(projectKey, import_node_path91.default.join(scanPath, "neat-out")).snapshotPath;
29220
- const outPath = import_node_path91.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
30076
+ const fallback = pathsForProject(projectKey, import_node_path92.default.join(scanPath, "neat-out")).snapshotPath;
30077
+ const outPath = import_node_path92.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
29221
30078
  const result = await runInit({
29222
30079
  scanPath,
29223
30080
  outPath,
@@ -29235,24 +30092,24 @@ async function main() {
29235
30092
  const target = positional[0];
29236
30093
  if (!target) {
29237
30094
  console.error("neat watch: missing <path>");
29238
- usage4();
30095
+ usage5();
29239
30096
  process.exit(2);
29240
30097
  }
29241
- const scanPath = import_node_path91.default.resolve(target);
29242
- const stat = await import_node_fs55.promises.stat(scanPath).catch(() => null);
30098
+ const scanPath = import_node_path92.default.resolve(target);
30099
+ const stat = await import_node_fs56.promises.stat(scanPath).catch(() => null);
29243
30100
  if (!stat || !stat.isDirectory()) {
29244
30101
  console.error(`neat watch: ${scanPath} is not a directory`);
29245
30102
  process.exit(2);
29246
30103
  }
29247
- const projectPaths = pathsForProject(project, import_node_path91.default.join(scanPath, "neat-out"));
29248
- const outPath = import_node_path91.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
29249
- const errorsPath = import_node_path91.default.resolve(
29250
- process.env.NEAT_ERRORS_PATH ?? import_node_path91.default.join(import_node_path91.default.dirname(outPath), import_node_path91.default.basename(projectPaths.errorsPath))
30104
+ const projectPaths = pathsForProject(project, import_node_path92.default.join(scanPath, "neat-out"));
30105
+ const outPath = import_node_path92.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
30106
+ const errorsPath = import_node_path92.default.resolve(
30107
+ process.env.NEAT_ERRORS_PATH ?? import_node_path92.default.join(import_node_path92.default.dirname(outPath), import_node_path92.default.basename(projectPaths.errorsPath))
29251
30108
  );
29252
- const staleEventsPath = import_node_path91.default.resolve(
29253
- process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path91.default.join(import_node_path91.default.dirname(outPath), import_node_path91.default.basename(projectPaths.staleEventsPath))
30109
+ const staleEventsPath = import_node_path92.default.resolve(
30110
+ process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path92.default.join(import_node_path92.default.dirname(outPath), import_node_path92.default.basename(projectPaths.staleEventsPath))
29254
30111
  );
29255
- const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path91.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
30112
+ const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path92.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
29256
30113
  const handle = await startWatch(getGraph(project), {
29257
30114
  scanPath,
29258
30115
  outPath,
@@ -29261,7 +30118,7 @@ async function main() {
29261
30118
  project,
29262
30119
  // Resolve NEAT_HOME so a `neat watch` picks up connectors added to
29263
30120
  // ~/.neat/connectors.json (#871). Same resolution the rest of the CLI uses.
29264
- neatHome: process.env.NEAT_HOME ? import_node_path91.default.resolve(process.env.NEAT_HOME) : import_node_path91.default.join(import_node_os8.default.homedir(), ".neat"),
30121
+ neatHome: process.env.NEAT_HOME ? import_node_path92.default.resolve(process.env.NEAT_HOME) : import_node_path92.default.join(import_node_os8.default.homedir(), ".neat"),
29265
30122
  ...embeddingsCachePath ? { embeddingsCachePath } : {},
29266
30123
  host: process.env.HOST ?? "0.0.0.0",
29267
30124
  port: Number(process.env.PORT ?? 8080),
@@ -29297,7 +30154,7 @@ async function main() {
29297
30154
  const name = positional[0];
29298
30155
  if (!name) {
29299
30156
  console.error("neat pause: missing <name>");
29300
- usage4();
30157
+ usage5();
29301
30158
  process.exit(2);
29302
30159
  }
29303
30160
  const daemon = await findDaemonByProject(name);
@@ -29322,7 +30179,7 @@ async function main() {
29322
30179
  const name = positional[0];
29323
30180
  if (!name) {
29324
30181
  console.error("neat resume: missing <name>");
29325
- usage4();
30182
+ usage5();
29326
30183
  process.exit(2);
29327
30184
  }
29328
30185
  const daemon = await findDaemonByProject(name);
@@ -29352,7 +30209,7 @@ async function main() {
29352
30209
  const name = positional[0];
29353
30210
  if (!name) {
29354
30211
  console.error("neat uninstall: missing <name>");
29355
- usage4();
30212
+ usage5();
29356
30213
  process.exit(2);
29357
30214
  }
29358
30215
  const daemon = await findDaemonByProject(name);
@@ -29439,15 +30296,15 @@ async function main() {
29439
30296
  return;
29440
30297
  }
29441
30298
  console.error(`neat: unknown command "${cmd}"`);
29442
- usage4();
30299
+ usage5();
29443
30300
  process.exit(1);
29444
30301
  }
29445
30302
  async function tryOrchestrator(cmd, parsed) {
29446
- const scanPath = import_node_path91.default.resolve(cmd);
29447
- const stat = await import_node_fs55.promises.stat(scanPath).catch(() => null);
30303
+ const scanPath = import_node_path92.default.resolve(cmd);
30304
+ const stat = await import_node_fs56.promises.stat(scanPath).catch(() => null);
29448
30305
  if (!stat || !stat.isDirectory()) return null;
29449
30306
  const projectExplicit = parsed.project !== null;
29450
- const projectName = projectExplicit ? parsed.project : import_node_path91.default.basename(scanPath);
30307
+ const projectName = projectExplicit ? parsed.project : import_node_path92.default.basename(scanPath);
29451
30308
  const result = await runOrchestrator({
29452
30309
  scanPath,
29453
30310
  project: projectName,
@@ -29469,7 +30326,10 @@ var QUERY_VERBS = /* @__PURE__ */ new Set([
29469
30326
  "stale-edges",
29470
30327
  "policies",
29471
30328
  // Tenth verb (ADR-060) — amends ADR-050's locked allowlist of nine.
29472
- "divergences"
30329
+ "divergences",
30330
+ // Twelfth verb (ADR-198) — the plain-language door over the whole tool
30331
+ // surface. Amends ADR-050's locked allowlist the same way `divergences` did.
30332
+ "ask"
29473
30333
  ]);
29474
30334
  function resolveProjectFlag(parsed) {
29475
30335
  if (parsed.project) return parsed.project;
@@ -29591,6 +30451,15 @@ async function runQueryVerb(cmd, parsed) {
29591
30451
  makeWork = (project) => runSearch(client, { query: q, ...project ? { project } : {} });
29592
30452
  break;
29593
30453
  }
30454
+ case "ask": {
30455
+ const question = positional.join(" ").trim();
30456
+ if (!question) {
30457
+ console.error("neat ask: missing <question>");
30458
+ return 2;
30459
+ }
30460
+ makeWork = (project) => runAsk(client, { question, ...project ? { project } : {} });
30461
+ break;
30462
+ }
29594
30463
  case "diff": {
29595
30464
  const against = parsed.against ?? parsed.since;
29596
30465
  if (!against) {
@@ -29636,10 +30505,10 @@ async function runQueryVerb(cmd, parsed) {
29636
30505
  const parts = parsed.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
29637
30506
  const out = [];
29638
30507
  for (const p of parts) {
29639
- const r = import_types97.DivergenceTypeSchema.safeParse(p);
30508
+ const r = import_types98.DivergenceTypeSchema.safeParse(p);
29640
30509
  if (!r.success) {
29641
30510
  console.error(
29642
- `neat divergences: unknown --type "${p}". allowed: ${import_types97.DivergenceTypeSchema.options.join(", ")}`
30511
+ `neat divergences: unknown --type "${p}". allowed: ${import_types98.DivergenceTypeSchema.options.join(", ")}`
29643
30512
  );
29644
30513
  return 2;
29645
30514
  }