@neat.is/core 0.9.2-dev.20260822 → 0.9.3-dev.20260823
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/{chunk-RQQUI3NQ.js → chunk-LRYPKC3B.js} +745 -50
- package/dist/chunk-LRYPKC3B.js.map +1 -0
- package/dist/{chunk-L4SZIIER.js → chunk-WJGZYEUG.js} +2 -2
- package/dist/cli.cjs +1203 -283
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +333 -110
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +788 -92
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +788 -92
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +787 -91
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-RQQUI3NQ.js.map +0 -1
- /package/dist/{chunk-L4SZIIER.js.map → chunk-WJGZYEUG.js.map} +0 -0
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
|
|
65
|
-
if (exactUnauthPaths.has(
|
|
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
|
|
419
|
-
if (
|
|
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: () =>
|
|
802
|
+
usage: () => usage5
|
|
803
803
|
});
|
|
804
804
|
module.exports = __toCommonJS(cli_exports);
|
|
805
805
|
init_cjs_shims();
|
|
806
|
-
var
|
|
806
|
+
var import_node_path92 = __toESM(require("path"), 1);
|
|
807
807
|
var import_node_os8 = __toESM(require("os"), 1);
|
|
808
|
-
var
|
|
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,
|
|
1377
|
-
if (
|
|
1378
|
-
best = { path: [...
|
|
1376
|
+
function step(node, path93, edges) {
|
|
1377
|
+
if (path93.length > best.path.length) {
|
|
1378
|
+
best = { path: [...path93], edges: [...edges] };
|
|
1379
1379
|
}
|
|
1380
|
-
if (
|
|
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
|
-
|
|
1385
|
+
path93.push(srcId);
|
|
1386
1386
|
edges.push(edge);
|
|
1387
|
-
step(srcId,
|
|
1388
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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,
|
|
1561
|
-
const ids = [
|
|
1562
|
-
for (const edgeId of graph.outboundEdges(
|
|
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,
|
|
1586
|
+
function dominantFailingCall(graph, serviceId16, visited) {
|
|
1587
1587
|
let best = null;
|
|
1588
|
-
for (const src of callSourcesForService(graph,
|
|
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
|
|
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
|
-
|
|
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:
|
|
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,
|
|
1627
|
+
function dominantStaleCall(graph, serviceId16, visited) {
|
|
1628
1628
|
const bestByCallee = /* @__PURE__ */ new Map();
|
|
1629
|
-
for (const src of callSourcesForService(graph,
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
|
2162
|
-
if (
|
|
2163
|
-
traversalPath =
|
|
2164
|
-
edgeProvenances =
|
|
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(
|
|
3613
|
-
return
|
|
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
|
|
4286
|
-
if (
|
|
4287
|
-
return { path:
|
|
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
|
|
4841
|
-
if (!
|
|
4840
|
+
const tokens2 = attr.childForFieldName("arguments");
|
|
4841
|
+
if (!tokens2 || tokens2.type !== "token_tree") return;
|
|
4842
4842
|
const strings = [];
|
|
4843
|
-
for (let i = 0; i <
|
|
4844
|
-
const s = rustStringContent(
|
|
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:
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
7876
|
-
if (!graph.hasNode(
|
|
7877
|
-
const node = graph.getNodeAttributes(
|
|
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(
|
|
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
|
|
7907
|
+
let compose2;
|
|
7908
7908
|
try {
|
|
7909
|
-
|
|
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 (!
|
|
7919
|
-
for (const [composeName, svc] of Object.entries(
|
|
7920
|
-
const
|
|
7921
|
-
if (!
|
|
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,
|
|
7925
|
+
addAliases(graph, serviceId16, aliases);
|
|
7926
7926
|
}
|
|
7927
7927
|
}
|
|
7928
7928
|
var LABEL_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -9912,12 +9912,12 @@ async function resolveConfigs(literals, keys, serviceDir, looksLike, parse12) {
|
|
|
9912
9912
|
const value = await interpolateEnvRefs(raw, serviceDir);
|
|
9913
9913
|
if (!looksLike(value)) continue;
|
|
9914
9914
|
const parsed = parse12(value);
|
|
9915
|
-
if (parsed) out.push(parsed);
|
|
9915
|
+
if (parsed) out.push({ ...parsed, hostSource: "config" });
|
|
9916
9916
|
}
|
|
9917
9917
|
for (const lit of literals) {
|
|
9918
9918
|
if (!looksLike(lit)) continue;
|
|
9919
9919
|
const parsed = parse12(lit);
|
|
9920
|
-
if (parsed) out.push(parsed);
|
|
9920
|
+
if (parsed) out.push({ ...parsed, hostSource: "literal" });
|
|
9921
9921
|
}
|
|
9922
9922
|
return out;
|
|
9923
9923
|
}
|
|
@@ -10186,7 +10186,10 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
10186
10186
|
type: import_types23.EdgeType.CONNECTS_TO,
|
|
10187
10187
|
provenance: import_types23.Provenance.EXTRACTED,
|
|
10188
10188
|
confidence: (0, import_types23.confidenceForExtracted)("structural"),
|
|
10189
|
-
|
|
10189
|
+
// Carry how the host was recovered (ADR-213) so the divergence ranker
|
|
10190
|
+
// can tell a real declared store from a hardcoded fault-injection /
|
|
10191
|
+
// flag-gated probe. Only set when the parser distinguished the two.
|
|
10192
|
+
evidence: config.hostSource ? { file: evidenceFile, hostSource: config.hostSource } : { file: evidenceFile }
|
|
10190
10193
|
};
|
|
10191
10194
|
if (!graph.hasEdge(edge.id)) {
|
|
10192
10195
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -14475,9 +14478,9 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
14475
14478
|
}
|
|
14476
14479
|
}
|
|
14477
14480
|
if (!composePath) return { nodesAdded, edgesAdded };
|
|
14478
|
-
let
|
|
14481
|
+
let compose2;
|
|
14479
14482
|
try {
|
|
14480
|
-
|
|
14483
|
+
compose2 = await readYaml(composePath);
|
|
14481
14484
|
} catch (err) {
|
|
14482
14485
|
recordExtractionError(
|
|
14483
14486
|
"infra docker-compose",
|
|
@@ -14486,10 +14489,10 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
14486
14489
|
);
|
|
14487
14490
|
return { nodesAdded, edgesAdded };
|
|
14488
14491
|
}
|
|
14489
|
-
if (!
|
|
14492
|
+
if (!compose2?.services) return { nodesAdded, edgesAdded };
|
|
14490
14493
|
const evidenceFile = import_node_path58.default.relative(scanPath, composePath).split(import_node_path58.default.sep).join("/");
|
|
14491
14494
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
14492
|
-
for (const [composeName, svc] of Object.entries(
|
|
14495
|
+
for (const [composeName, svc] of Object.entries(compose2.services)) {
|
|
14493
14496
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
14494
14497
|
if (matchedServiceId) {
|
|
14495
14498
|
composeNameToNodeId.set(composeName, matchedServiceId);
|
|
@@ -14503,7 +14506,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
14503
14506
|
}
|
|
14504
14507
|
composeNameToNodeId.set(composeName, node.id);
|
|
14505
14508
|
}
|
|
14506
|
-
for (const [composeName, svc] of Object.entries(
|
|
14509
|
+
for (const [composeName, svc] of Object.entries(compose2.services)) {
|
|
14507
14510
|
const sourceId = composeNameToNodeId.get(composeName);
|
|
14508
14511
|
if (!sourceId) continue;
|
|
14509
14512
|
for (const dep of dependsOnList(svc.depends_on)) {
|
|
@@ -15224,7 +15227,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
|
|
|
15224
15227
|
|
|
15225
15228
|
// src/extract/infra/index.ts
|
|
15226
15229
|
async function addInfra(graph, scanPath, services) {
|
|
15227
|
-
const
|
|
15230
|
+
const compose2 = await addComposeInfra(graph, scanPath, services);
|
|
15228
15231
|
const dockerfile = await addDockerfileRuntimes(graph, services, scanPath);
|
|
15229
15232
|
const terraform = await addTerraformResources(graph, scanPath);
|
|
15230
15233
|
const k8s = await addK8sResources(graph, scanPath);
|
|
@@ -15233,8 +15236,8 @@ async function addInfra(graph, scanPath, services) {
|
|
|
15233
15236
|
const railway = await addRailwayServices(graph, services, scanPath);
|
|
15234
15237
|
const supabase = await addSupabaseProjects(graph, services, scanPath);
|
|
15235
15238
|
return {
|
|
15236
|
-
nodesAdded:
|
|
15237
|
-
edgesAdded:
|
|
15239
|
+
nodesAdded: compose2.nodesAdded + dockerfile.nodesAdded + terraform.nodesAdded + k8s.nodesAdded + cloudflare.nodesAdded + vercel.nodesAdded + railway.nodesAdded + supabase.nodesAdded,
|
|
15240
|
+
edgesAdded: compose2.edgesAdded + dockerfile.edgesAdded + terraform.edgesAdded + k8s.edgesAdded + cloudflare.edgesAdded + vercel.edgesAdded + railway.edgesAdded + supabase.edgesAdded
|
|
15238
15241
|
};
|
|
15239
15242
|
}
|
|
15240
15243
|
|
|
@@ -15981,6 +15984,53 @@ function detectColumnDrift(node) {
|
|
|
15981
15984
|
function involvesNode(d, nodeId) {
|
|
15982
15985
|
return d.source === nodeId || d.target === nodeId;
|
|
15983
15986
|
}
|
|
15987
|
+
function datastoreEverObserved(graph, nodeId) {
|
|
15988
|
+
if (!graph.hasNode(nodeId)) return false;
|
|
15989
|
+
const n = graph.getNodeAttributes(nodeId);
|
|
15990
|
+
if (n.type === import_types57.NodeType.DatabaseNode) {
|
|
15991
|
+
const via = n.discoveredVia;
|
|
15992
|
+
if (via === "otel" || via === "merged") return true;
|
|
15993
|
+
}
|
|
15994
|
+
for (const edgeId of graph.inboundEdges(nodeId)) {
|
|
15995
|
+
const e = graph.getEdgeAttributes(edgeId);
|
|
15996
|
+
if (e.provenance === import_types57.Provenance.OBSERVED || e.provenance === import_types57.Provenance.STALE) return true;
|
|
15997
|
+
}
|
|
15998
|
+
return false;
|
|
15999
|
+
}
|
|
16000
|
+
function serviceHasObservedSameEngineStore(graph, buckets2, serviceId16, engine, excludeTarget) {
|
|
16001
|
+
for (const bucket of buckets2.values()) {
|
|
16002
|
+
if (bucket.type !== import_types57.EdgeType.CONNECTS_TO) continue;
|
|
16003
|
+
if (bucket.source !== serviceId16) continue;
|
|
16004
|
+
if (bucket.target === excludeTarget) continue;
|
|
16005
|
+
if (!graph.hasNode(bucket.target)) continue;
|
|
16006
|
+
const target = graph.getNodeAttributes(bucket.target);
|
|
16007
|
+
if (target.type !== import_types57.NodeType.DatabaseNode) continue;
|
|
16008
|
+
if (target.engine !== engine) continue;
|
|
16009
|
+
if (bucket.observed || datastoreEverObserved(graph, bucket.target)) return true;
|
|
16010
|
+
}
|
|
16011
|
+
return false;
|
|
16012
|
+
}
|
|
16013
|
+
var DEAD_CODE_PROBE_CONFIDENCE = 0.1;
|
|
16014
|
+
function dampenDeadCodeProbes(graph, buckets2, all) {
|
|
16015
|
+
return all.map((d) => {
|
|
16016
|
+
if (d.type !== "missing-observed") return d;
|
|
16017
|
+
if (!d.extracted || d.edgeType !== import_types57.EdgeType.CONNECTS_TO) return d;
|
|
16018
|
+
if (!graph.hasNode(d.target)) return d;
|
|
16019
|
+
const target = graph.getNodeAttributes(d.target);
|
|
16020
|
+
if (target.type !== import_types57.NodeType.DatabaseNode) return d;
|
|
16021
|
+
if (d.extracted.evidence?.hostSource !== "literal") return d;
|
|
16022
|
+
if (datastoreEverObserved(graph, d.target)) return d;
|
|
16023
|
+
const engine = target.engine;
|
|
16024
|
+
if (!serviceHasObservedSameEngineStore(graph, buckets2, d.source, engine, d.target)) return d;
|
|
16025
|
+
const host = target.host ?? target.name;
|
|
16026
|
+
return {
|
|
16027
|
+
...d,
|
|
16028
|
+
confidence: Math.min(d.confidence, DEAD_CODE_PROBE_CONFIDENCE),
|
|
16029
|
+
reason: `${d.source} declares a ${engine} connection to a hardcoded-literal host (${host}) that production has never observed, while it does observe another ${engine} store \u2014 this reads as a flag-gated or dead-code declaration (e.g. a fault-injection probe), not a real declared-vs-observed gap.`,
|
|
16030
|
+
recommendation: "Confirm this is an intentional dead alternate \u2014 a fault-injection probe or a flag-gated branch. If it is meant to run in production, check the feature flag or conditional that gates it; if not, it can be ignored or removed."
|
|
16031
|
+
};
|
|
16032
|
+
});
|
|
16033
|
+
}
|
|
15984
16034
|
function suppressHostMismatchHalves(all) {
|
|
15985
16035
|
const observedHalf = /* @__PURE__ */ new Set();
|
|
15986
16036
|
const declaredHalf = /* @__PURE__ */ new Set();
|
|
@@ -16017,7 +16067,8 @@ function computeDivergences(graph, opts = {}) {
|
|
|
16017
16067
|
}
|
|
16018
16068
|
});
|
|
16019
16069
|
const reconciled = suppressHostMismatchHalves(all);
|
|
16020
|
-
|
|
16070
|
+
const dampened = dampenDeadCodeProbes(graph, buckets2, reconciled);
|
|
16071
|
+
let filtered = dampened;
|
|
16021
16072
|
if (opts.type) {
|
|
16022
16073
|
const allowed = opts.type;
|
|
16023
16074
|
filtered = filtered.filter((d) => allowed.has(d.type));
|
|
@@ -16343,7 +16394,7 @@ var import_chokidar = __toESM(require("chokidar"), 1);
|
|
|
16343
16394
|
init_cjs_shims();
|
|
16344
16395
|
var import_fastify2 = __toESM(require("fastify"), 1);
|
|
16345
16396
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
16346
|
-
var
|
|
16397
|
+
var import_types94 = require("@neat.is/types");
|
|
16347
16398
|
|
|
16348
16399
|
// src/extend/index.ts
|
|
16349
16400
|
init_cjs_shims();
|
|
@@ -16733,6 +16784,639 @@ function queryLogEntries(opts) {
|
|
|
16733
16784
|
return merged;
|
|
16734
16785
|
}
|
|
16735
16786
|
|
|
16787
|
+
// src/ask.ts
|
|
16788
|
+
init_cjs_shims();
|
|
16789
|
+
var import_types60 = require("@neat.is/types");
|
|
16790
|
+
var DEFAULT_MAX_NODES = 3;
|
|
16791
|
+
var MAX_FACTS_PER_SECTION = 6;
|
|
16792
|
+
var INTENT_RULES = [
|
|
16793
|
+
{
|
|
16794
|
+
intent: "root-cause",
|
|
16795
|
+
test: /\b(why|root[\s-]?cause|failing|fail(?:s|ed)?|breaking|broke(?:n)?|crash(?:ing|ed)?|throw(?:ing|s)?|culprit|5\d\d)\b/
|
|
16796
|
+
},
|
|
16797
|
+
{
|
|
16798
|
+
intent: "blast-radius",
|
|
16799
|
+
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/
|
|
16800
|
+
},
|
|
16801
|
+
{
|
|
16802
|
+
intent: "divergence",
|
|
16803
|
+
test: /\b(diverg\w*|weird|mismatch|drift|out\s+of\s+sync|inconsistent|disagree\w*|declared\s+vs|anything\s+wrong)\b/
|
|
16804
|
+
},
|
|
16805
|
+
{
|
|
16806
|
+
intent: "incidents",
|
|
16807
|
+
test: /\b(incidents?|recent\s+(?:errors?|failures?)|error\s+history|failure\s+history)\b/
|
|
16808
|
+
},
|
|
16809
|
+
{
|
|
16810
|
+
intent: "observed",
|
|
16811
|
+
test: /\b(at\s+runtime|in\s+prod(?:uction)?|actually\s+call\w*|really\s+call\w*|observed|runtime\s+traffic)\b/
|
|
16812
|
+
},
|
|
16813
|
+
{
|
|
16814
|
+
intent: "dependencies",
|
|
16815
|
+
test: /\b(depend\w*|calls?|uses?|imports?|relies\s+on|needs?)\b/
|
|
16816
|
+
}
|
|
16817
|
+
];
|
|
16818
|
+
function classifyIntent(question) {
|
|
16819
|
+
const q = question.toLowerCase();
|
|
16820
|
+
for (const rule of INTENT_RULES) {
|
|
16821
|
+
if (rule.test.test(q)) return rule.intent;
|
|
16822
|
+
}
|
|
16823
|
+
return "overview";
|
|
16824
|
+
}
|
|
16825
|
+
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
16826
|
+
"the",
|
|
16827
|
+
"a",
|
|
16828
|
+
"an",
|
|
16829
|
+
"is",
|
|
16830
|
+
"are",
|
|
16831
|
+
"was",
|
|
16832
|
+
"were",
|
|
16833
|
+
"be",
|
|
16834
|
+
"been",
|
|
16835
|
+
"being",
|
|
16836
|
+
"of",
|
|
16837
|
+
"to",
|
|
16838
|
+
"in",
|
|
16839
|
+
"on",
|
|
16840
|
+
"at",
|
|
16841
|
+
"by",
|
|
16842
|
+
"for",
|
|
16843
|
+
"with",
|
|
16844
|
+
"and",
|
|
16845
|
+
"or",
|
|
16846
|
+
"but",
|
|
16847
|
+
"if",
|
|
16848
|
+
"as",
|
|
16849
|
+
"this",
|
|
16850
|
+
"that",
|
|
16851
|
+
"these",
|
|
16852
|
+
"those",
|
|
16853
|
+
"it",
|
|
16854
|
+
"its",
|
|
16855
|
+
"do",
|
|
16856
|
+
"does",
|
|
16857
|
+
"did",
|
|
16858
|
+
"has",
|
|
16859
|
+
"have",
|
|
16860
|
+
"had",
|
|
16861
|
+
"what",
|
|
16862
|
+
"which",
|
|
16863
|
+
"who",
|
|
16864
|
+
"whom",
|
|
16865
|
+
"how",
|
|
16866
|
+
"when",
|
|
16867
|
+
"where",
|
|
16868
|
+
"my",
|
|
16869
|
+
"me",
|
|
16870
|
+
"i",
|
|
16871
|
+
"we",
|
|
16872
|
+
"you",
|
|
16873
|
+
"they",
|
|
16874
|
+
"them",
|
|
16875
|
+
"from",
|
|
16876
|
+
"into",
|
|
16877
|
+
"about",
|
|
16878
|
+
"would",
|
|
16879
|
+
"will",
|
|
16880
|
+
"can",
|
|
16881
|
+
"could",
|
|
16882
|
+
"should",
|
|
16883
|
+
"anything",
|
|
16884
|
+
"everything",
|
|
16885
|
+
"something",
|
|
16886
|
+
"all",
|
|
16887
|
+
"any",
|
|
16888
|
+
"some",
|
|
16889
|
+
"no",
|
|
16890
|
+
"not",
|
|
16891
|
+
"so",
|
|
16892
|
+
"up",
|
|
16893
|
+
"out",
|
|
16894
|
+
"over",
|
|
16895
|
+
"get",
|
|
16896
|
+
"got",
|
|
16897
|
+
"show",
|
|
16898
|
+
"tell",
|
|
16899
|
+
"explain",
|
|
16900
|
+
"find",
|
|
16901
|
+
"give",
|
|
16902
|
+
"happen",
|
|
16903
|
+
"happens",
|
|
16904
|
+
"happening",
|
|
16905
|
+
"here",
|
|
16906
|
+
"there",
|
|
16907
|
+
"system",
|
|
16908
|
+
"code",
|
|
16909
|
+
"service",
|
|
16910
|
+
"node",
|
|
16911
|
+
"graph"
|
|
16912
|
+
]);
|
|
16913
|
+
var INTENT_WORDS = /* @__PURE__ */ new Set([
|
|
16914
|
+
"why",
|
|
16915
|
+
"root",
|
|
16916
|
+
"cause",
|
|
16917
|
+
"failing",
|
|
16918
|
+
"fail",
|
|
16919
|
+
"fails",
|
|
16920
|
+
"failed",
|
|
16921
|
+
"breaking",
|
|
16922
|
+
"broke",
|
|
16923
|
+
"broken",
|
|
16924
|
+
"crash",
|
|
16925
|
+
"crashing",
|
|
16926
|
+
"crashed",
|
|
16927
|
+
"throw",
|
|
16928
|
+
"throwing",
|
|
16929
|
+
"throws",
|
|
16930
|
+
"culprit",
|
|
16931
|
+
"blast",
|
|
16932
|
+
"radius",
|
|
16933
|
+
"break",
|
|
16934
|
+
"breaks",
|
|
16935
|
+
"impact",
|
|
16936
|
+
"downstream",
|
|
16937
|
+
"dependent",
|
|
16938
|
+
"dependents",
|
|
16939
|
+
"redeploy",
|
|
16940
|
+
"affect",
|
|
16941
|
+
"affects",
|
|
16942
|
+
"affected",
|
|
16943
|
+
"diverge",
|
|
16944
|
+
"divergence",
|
|
16945
|
+
"divergences",
|
|
16946
|
+
"weird",
|
|
16947
|
+
"mismatch",
|
|
16948
|
+
"drift",
|
|
16949
|
+
"inconsistent",
|
|
16950
|
+
"disagree",
|
|
16951
|
+
"declared",
|
|
16952
|
+
"observed",
|
|
16953
|
+
"incident",
|
|
16954
|
+
"incidents",
|
|
16955
|
+
"recent",
|
|
16956
|
+
"errors",
|
|
16957
|
+
"error",
|
|
16958
|
+
"failures",
|
|
16959
|
+
"failure",
|
|
16960
|
+
"history",
|
|
16961
|
+
"runtime",
|
|
16962
|
+
"production",
|
|
16963
|
+
"prod",
|
|
16964
|
+
"traffic",
|
|
16965
|
+
"actually",
|
|
16966
|
+
"really",
|
|
16967
|
+
"depend",
|
|
16968
|
+
"depends",
|
|
16969
|
+
"dependency",
|
|
16970
|
+
"dependencies",
|
|
16971
|
+
"call",
|
|
16972
|
+
"calls",
|
|
16973
|
+
"use",
|
|
16974
|
+
"uses",
|
|
16975
|
+
"using",
|
|
16976
|
+
"import",
|
|
16977
|
+
"imports",
|
|
16978
|
+
"relies",
|
|
16979
|
+
"rely",
|
|
16980
|
+
"needs",
|
|
16981
|
+
"need",
|
|
16982
|
+
"sync"
|
|
16983
|
+
]);
|
|
16984
|
+
function tokens(input) {
|
|
16985
|
+
const spaced = input.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
|
|
16986
|
+
return (spaced.toLowerCase().match(/[a-z0-9]+/g) ?? []).filter((t) => t.length >= 2);
|
|
16987
|
+
}
|
|
16988
|
+
function queryTokens(question) {
|
|
16989
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16990
|
+
const out = [];
|
|
16991
|
+
for (const t of tokens(question)) {
|
|
16992
|
+
if (STOPWORDS.has(t) || INTENT_WORDS.has(t)) continue;
|
|
16993
|
+
if (seen.has(t)) continue;
|
|
16994
|
+
seen.add(t);
|
|
16995
|
+
out.push(t);
|
|
16996
|
+
}
|
|
16997
|
+
return out;
|
|
16998
|
+
}
|
|
16999
|
+
function nodeName(node) {
|
|
17000
|
+
return node.name ?? node.id;
|
|
17001
|
+
}
|
|
17002
|
+
function idBody(id) {
|
|
17003
|
+
const colon = id.indexOf(":");
|
|
17004
|
+
return colon >= 0 ? id.slice(colon + 1) : id;
|
|
17005
|
+
}
|
|
17006
|
+
var EMBED_MIN_SCORE = 0.35;
|
|
17007
|
+
async function resolveEntities(graph, question, searchIndex, maxNodes) {
|
|
17008
|
+
const qTokens = queryTokens(question);
|
|
17009
|
+
const normalized = question.toLowerCase();
|
|
17010
|
+
const best = /* @__PURE__ */ new Map();
|
|
17011
|
+
const consider = (cand) => {
|
|
17012
|
+
const cur = best.get(cand.nodeId);
|
|
17013
|
+
if (!cur || cand.score > cur.score) best.set(cand.nodeId, cand);
|
|
17014
|
+
};
|
|
17015
|
+
graph.forEachNode((id, attrs) => {
|
|
17016
|
+
const node = attrs;
|
|
17017
|
+
if (node.type === import_types60.NodeType.FrontierNode) return;
|
|
17018
|
+
const name = nodeName(node);
|
|
17019
|
+
const body = idBody(id);
|
|
17020
|
+
const labelTokens = /* @__PURE__ */ new Set([...tokens(body), ...tokens(name)]);
|
|
17021
|
+
if (labelTokens.size === 0) return;
|
|
17022
|
+
let matched = 0;
|
|
17023
|
+
for (const t of qTokens) if (labelTokens.has(t)) matched += 1;
|
|
17024
|
+
const named = body.length >= 2 && normalized.includes(body.toLowerCase()) || name.length >= 2 && normalized.includes(name.toLowerCase());
|
|
17025
|
+
if (matched === 0 && !named) return;
|
|
17026
|
+
const coverage = qTokens.length > 0 ? matched / qTokens.length : 0;
|
|
17027
|
+
const via = named ? "id" : matched > 0 && tokens(name).some((t) => qTokens.includes(t)) ? "label" : "token";
|
|
17028
|
+
const score = named ? Math.max(0.9, coverage) : Math.min(0.85, 0.3 + 0.7 * coverage);
|
|
17029
|
+
consider({ nodeId: id, label: name, via, score: Math.min(1, score) });
|
|
17030
|
+
});
|
|
17031
|
+
if (searchIndex) {
|
|
17032
|
+
try {
|
|
17033
|
+
const res = await searchIndex.search(question, 10);
|
|
17034
|
+
if (res.provider !== "substring") {
|
|
17035
|
+
for (const m of res.matches) {
|
|
17036
|
+
if (m.node.type === import_types60.NodeType.FrontierNode) continue;
|
|
17037
|
+
const already = best.get(m.node.id);
|
|
17038
|
+
if (!already && m.score < EMBED_MIN_SCORE) continue;
|
|
17039
|
+
consider({
|
|
17040
|
+
nodeId: m.node.id,
|
|
17041
|
+
label: nodeName(m.node),
|
|
17042
|
+
via: "embedding",
|
|
17043
|
+
score: Math.max(0, Math.min(1, m.score))
|
|
17044
|
+
});
|
|
17045
|
+
}
|
|
17046
|
+
}
|
|
17047
|
+
} catch {
|
|
17048
|
+
}
|
|
17049
|
+
}
|
|
17050
|
+
const viaRank = { id: 3, label: 2, token: 1, embedding: 0 };
|
|
17051
|
+
return [...best.values()].sort(
|
|
17052
|
+
(a, b) => b.score - a.score || viaRank[b.via] - viaRank[a.via] || a.nodeId.localeCompare(b.nodeId)
|
|
17053
|
+
).slice(0, maxNodes).map((s) => ({ nodeId: s.nodeId, label: s.label, via: s.via, score: Number(s.score.toFixed(3)) }));
|
|
17054
|
+
}
|
|
17055
|
+
function incidentsForNode(nodeId, incidents) {
|
|
17056
|
+
if (!incidents || incidents.length === 0) return [];
|
|
17057
|
+
const svc = nodeId.replace(/^service:/, "");
|
|
17058
|
+
return incidents.filter((e) => e.affectedNode === nodeId || e.service === svc);
|
|
17059
|
+
}
|
|
17060
|
+
function edgeSignalNote(e) {
|
|
17061
|
+
const bits = [];
|
|
17062
|
+
if (e.signal) {
|
|
17063
|
+
bits.push(`${e.signal.spanCount} spans`);
|
|
17064
|
+
if (e.signal.errorCount > 0) bits.push(`${e.signal.errorCount} errors`);
|
|
17065
|
+
if (e.signal.latencyMs?.p95 !== void 0) bits.push(`p95 ${Math.round(e.signal.latencyMs.p95)}ms`);
|
|
17066
|
+
} else if (e.callCount !== void 0) {
|
|
17067
|
+
bits.push(`${e.callCount} calls`);
|
|
17068
|
+
}
|
|
17069
|
+
return bits.length ? ` (${bits.join(", ")})` : "";
|
|
17070
|
+
}
|
|
17071
|
+
function buildRootCauseSection(graph, node, incidents, now) {
|
|
17072
|
+
const result = getRootCause(graph, node, void 0, incidents, { now });
|
|
17073
|
+
if (!result) return null;
|
|
17074
|
+
const lastProv = result.edgeProvenances[result.edgeProvenances.length - 1] ?? import_types60.Provenance.OBSERVED;
|
|
17075
|
+
const facts = [
|
|
17076
|
+
{
|
|
17077
|
+
text: `Root cause: ${result.rootCauseNode} \u2014 ${result.rootCauseReason}`,
|
|
17078
|
+
provenance: lastProv,
|
|
17079
|
+
confidence: result.confidence
|
|
17080
|
+
}
|
|
17081
|
+
];
|
|
17082
|
+
const candidates = result.candidates ?? [];
|
|
17083
|
+
for (const c of candidates.slice(0, 3)) {
|
|
17084
|
+
facts.push({
|
|
17085
|
+
text: `Candidate ${c.node} \u2014 ${c.classification}: ${c.reason}`,
|
|
17086
|
+
...c.provenance ? { provenance: c.provenance } : {},
|
|
17087
|
+
confidence: c.confidence
|
|
17088
|
+
});
|
|
17089
|
+
}
|
|
17090
|
+
if (result.traversalPath.length > 1) {
|
|
17091
|
+
facts.push({ text: `Traversal: ${result.traversalPath.join(" \u2190 ")}` });
|
|
17092
|
+
}
|
|
17093
|
+
if (result.fixRecommendation) {
|
|
17094
|
+
facts.push({ text: `Recommended fix: ${result.fixRecommendation}` });
|
|
17095
|
+
}
|
|
17096
|
+
return { heading: "Root cause (navigation)", facts: facts.slice(0, MAX_FACTS_PER_SECTION) };
|
|
17097
|
+
}
|
|
17098
|
+
function buildDependenciesSection(graph, node) {
|
|
17099
|
+
const result = getTransitiveDependencies(graph, node, 2);
|
|
17100
|
+
if (result.total === 0) return null;
|
|
17101
|
+
const facts = result.dependencies.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
|
|
17102
|
+
text: `${d.nodeId} \u2014 ${d.edgeType} (distance ${d.distance})`,
|
|
17103
|
+
// Per-fact provenance: the transitive walk mixes EXTRACTED and OBSERVED
|
|
17104
|
+
// edges, so the tag is on the fact, not asserted for the whole section.
|
|
17105
|
+
provenance: d.provenance
|
|
17106
|
+
}));
|
|
17107
|
+
return { heading: "Dependencies", facts };
|
|
17108
|
+
}
|
|
17109
|
+
function buildObservedSection(graph, node) {
|
|
17110
|
+
const result = getObservedDependencies(graph, node);
|
|
17111
|
+
const facts = [];
|
|
17112
|
+
for (const e of result.dependencies.slice(0, MAX_FACTS_PER_SECTION)) {
|
|
17113
|
+
const via = e.source !== node ? ` via ${e.source}` : "";
|
|
17114
|
+
facts.push({
|
|
17115
|
+
text: `calls ${e.target}${via}${edgeSignalNote(e)}`,
|
|
17116
|
+
provenance: e.provenance,
|
|
17117
|
+
confidence: confidenceForEdge(e)
|
|
17118
|
+
});
|
|
17119
|
+
}
|
|
17120
|
+
if (facts.length === 0) {
|
|
17121
|
+
if (result.observed && result.inboundObservedCount > 0) {
|
|
17122
|
+
facts.push({
|
|
17123
|
+
text: `no outbound runtime calls, but OTel observed ${result.inboundObservedCount} inbound call path${result.inboundObservedCount === 1 ? "" : "s"} \u2014 a pure receiver`,
|
|
17124
|
+
provenance: import_types60.Provenance.OBSERVED
|
|
17125
|
+
});
|
|
17126
|
+
} else {
|
|
17127
|
+
return null;
|
|
17128
|
+
}
|
|
17129
|
+
}
|
|
17130
|
+
return { heading: "Runtime dependencies (OBSERVED)", facts };
|
|
17131
|
+
}
|
|
17132
|
+
function buildBlastSection(graph, node) {
|
|
17133
|
+
const result = getBlastRadius(graph, node);
|
|
17134
|
+
if (result.totalAffected === 0) return null;
|
|
17135
|
+
const facts = result.affectedNodes.slice(0, MAX_FACTS_PER_SECTION).map((n) => ({
|
|
17136
|
+
text: `${n.nodeId} (distance ${n.distance})`,
|
|
17137
|
+
provenance: n.edgeProvenance,
|
|
17138
|
+
confidence: n.confidence
|
|
17139
|
+
}));
|
|
17140
|
+
return {
|
|
17141
|
+
heading: `Blast radius \u2014 ${result.totalAffected} dependent${result.totalAffected === 1 ? "" : "s"}`,
|
|
17142
|
+
facts
|
|
17143
|
+
};
|
|
17144
|
+
}
|
|
17145
|
+
function buildIncidentsSection(node, incidents) {
|
|
17146
|
+
const relevant = incidentsForNode(node, incidents);
|
|
17147
|
+
if (relevant.length === 0) return null;
|
|
17148
|
+
const ordered = [...relevant].sort((a, b) => b.timestamp.localeCompare(a.timestamp)).slice(0, 4);
|
|
17149
|
+
const facts = ordered.map((ev) => ({
|
|
17150
|
+
text: `${ev.timestamp} \u2014 ${ev.service}: ${ev.errorMessage}`,
|
|
17151
|
+
// ErrorEvents are observation records — OBSERVED by definition.
|
|
17152
|
+
provenance: import_types60.Provenance.OBSERVED
|
|
17153
|
+
}));
|
|
17154
|
+
return { heading: `Recent incidents (OBSERVED) \u2014 ${relevant.length} recorded`, facts };
|
|
17155
|
+
}
|
|
17156
|
+
function divergenceLine(d) {
|
|
17157
|
+
if ((d.type === "missing-observed" || d.type === "missing-extracted") && d.column) {
|
|
17158
|
+
return `[${d.type}] ${d.table ?? d.source} column ${d.column} \u2014 ${d.reason}`;
|
|
17159
|
+
}
|
|
17160
|
+
return `[${d.type}] ${d.source} \u2192 ${d.target} \u2014 ${d.reason}`;
|
|
17161
|
+
}
|
|
17162
|
+
function buildDivergenceSection(graph, node) {
|
|
17163
|
+
const result = computeDivergences(graph, { node });
|
|
17164
|
+
if (result.totalAffected === 0) return null;
|
|
17165
|
+
const facts = result.divergences.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
|
|
17166
|
+
text: divergenceLine(d),
|
|
17167
|
+
confidence: d.confidence
|
|
17168
|
+
// A divergence is composite by construction (EXTRACTED vs OBSERVED), so it
|
|
17169
|
+
// carries no single provenance — the fact leaves it unset.
|
|
17170
|
+
}));
|
|
17171
|
+
return {
|
|
17172
|
+
heading: `Divergences (EXTRACTED vs OBSERVED) \u2014 ${result.totalAffected}`,
|
|
17173
|
+
facts
|
|
17174
|
+
};
|
|
17175
|
+
}
|
|
17176
|
+
function buildGlobalDivergenceSection(graph) {
|
|
17177
|
+
const result = computeDivergences(graph);
|
|
17178
|
+
if (result.totalAffected === 0) {
|
|
17179
|
+
return {
|
|
17180
|
+
heading: "Divergences (EXTRACTED vs OBSERVED)",
|
|
17181
|
+
facts: [{ text: "None \u2014 declared code and observed runtime agree across the graph." }]
|
|
17182
|
+
};
|
|
17183
|
+
}
|
|
17184
|
+
const facts = result.divergences.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
|
|
17185
|
+
text: divergenceLine(d),
|
|
17186
|
+
confidence: d.confidence
|
|
17187
|
+
// Composite by construction (EXTRACTED vs OBSERVED), so no single provenance.
|
|
17188
|
+
}));
|
|
17189
|
+
return {
|
|
17190
|
+
heading: `Divergences (EXTRACTED vs OBSERVED) \u2014 ${result.totalAffected}`,
|
|
17191
|
+
facts
|
|
17192
|
+
};
|
|
17193
|
+
}
|
|
17194
|
+
function buildGlobalIncidentsSection(incidents) {
|
|
17195
|
+
if (!incidents || incidents.length === 0) {
|
|
17196
|
+
return {
|
|
17197
|
+
heading: "Incidents across the system",
|
|
17198
|
+
facts: [{ text: "None recorded \u2014 the OBSERVED incident store is empty." }]
|
|
17199
|
+
};
|
|
17200
|
+
}
|
|
17201
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
17202
|
+
for (const ev of incidents) {
|
|
17203
|
+
const key = ev.affectedNode || (0, import_types60.serviceId)(ev.service);
|
|
17204
|
+
const cur = byKey.get(key);
|
|
17205
|
+
if (!cur) {
|
|
17206
|
+
byKey.set(key, { key, count: 1, latest: ev.timestamp, sampleMsg: ev.errorMessage });
|
|
17207
|
+
} else {
|
|
17208
|
+
cur.count += 1;
|
|
17209
|
+
if (ev.timestamp > cur.latest) {
|
|
17210
|
+
cur.latest = ev.timestamp;
|
|
17211
|
+
cur.sampleMsg = ev.errorMessage;
|
|
17212
|
+
}
|
|
17213
|
+
}
|
|
17214
|
+
}
|
|
17215
|
+
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);
|
|
17216
|
+
const facts = rows.map((r) => ({
|
|
17217
|
+
text: `${r.key} \u2014 ${r.count} incident${r.count === 1 ? "" : "s"}, latest ${r.latest}: ${r.sampleMsg}`,
|
|
17218
|
+
provenance: import_types60.Provenance.OBSERVED
|
|
17219
|
+
}));
|
|
17220
|
+
return {
|
|
17221
|
+
heading: `Incidents across the system \u2014 ${incidents.length} recorded`,
|
|
17222
|
+
facts
|
|
17223
|
+
};
|
|
17224
|
+
}
|
|
17225
|
+
function buildOverviewSections(graph, incidents) {
|
|
17226
|
+
const nodeByType = /* @__PURE__ */ new Map();
|
|
17227
|
+
const services = [];
|
|
17228
|
+
graph.forEachNode((_id, attrs) => {
|
|
17229
|
+
const node = attrs;
|
|
17230
|
+
nodeByType.set(node.type, (nodeByType.get(node.type) ?? 0) + 1);
|
|
17231
|
+
if (node.type === import_types60.NodeType.ServiceNode) services.push(node.id);
|
|
17232
|
+
});
|
|
17233
|
+
const edgeByProv = /* @__PURE__ */ new Map();
|
|
17234
|
+
graph.forEachEdge((_id, attrs) => {
|
|
17235
|
+
const p = attrs.provenance;
|
|
17236
|
+
edgeByProv.set(p, (edgeByProv.get(p) ?? 0) + 1);
|
|
17237
|
+
});
|
|
17238
|
+
const sections = [];
|
|
17239
|
+
const count = (t) => nodeByType.get(t) ?? 0;
|
|
17240
|
+
const shapeFacts = [
|
|
17241
|
+
{ text: `${graph.order} nodes, ${graph.size} edges` },
|
|
17242
|
+
{
|
|
17243
|
+
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`
|
|
17244
|
+
}
|
|
17245
|
+
];
|
|
17246
|
+
for (const p of [import_types60.Provenance.EXTRACTED, import_types60.Provenance.OBSERVED, import_types60.Provenance.INFERRED, import_types60.Provenance.STALE]) {
|
|
17247
|
+
const n = edgeByProv.get(p) ?? 0;
|
|
17248
|
+
if (n > 0) shapeFacts.push({ text: `${n} ${p} edge${n === 1 ? "" : "s"}`, provenance: p });
|
|
17249
|
+
}
|
|
17250
|
+
sections.push({ heading: "System shape", facts: shapeFacts });
|
|
17251
|
+
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);
|
|
17252
|
+
if (depCounts.length > 0) {
|
|
17253
|
+
sections.push({
|
|
17254
|
+
heading: "Busiest services (by direct dependencies)",
|
|
17255
|
+
facts: depCounts.map((r) => ({
|
|
17256
|
+
text: `${r.s} \u2014 ${r.n} direct dependenc${r.n === 1 ? "y" : "ies"}`
|
|
17257
|
+
}))
|
|
17258
|
+
});
|
|
17259
|
+
}
|
|
17260
|
+
if (incidents && incidents.length > 0) {
|
|
17261
|
+
const incCount = /* @__PURE__ */ new Map();
|
|
17262
|
+
for (const ev of incidents) {
|
|
17263
|
+
const key = ev.affectedNode || (0, import_types60.serviceId)(ev.service);
|
|
17264
|
+
incCount.set(key, (incCount.get(key) ?? 0) + 1);
|
|
17265
|
+
}
|
|
17266
|
+
const top = [...incCount.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, MAX_FACTS_PER_SECTION);
|
|
17267
|
+
sections.push({
|
|
17268
|
+
heading: `Nodes with incidents \u2014 ${incidents.length} recorded`,
|
|
17269
|
+
facts: top.map(([k, n]) => ({
|
|
17270
|
+
text: `${k} \u2014 ${n} incident${n === 1 ? "" : "s"}`,
|
|
17271
|
+
provenance: import_types60.Provenance.OBSERVED
|
|
17272
|
+
}))
|
|
17273
|
+
});
|
|
17274
|
+
}
|
|
17275
|
+
const div = computeDivergences(graph);
|
|
17276
|
+
sections.push({
|
|
17277
|
+
heading: "Divergences",
|
|
17278
|
+
facts: [
|
|
17279
|
+
{
|
|
17280
|
+
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.`
|
|
17281
|
+
}
|
|
17282
|
+
]
|
|
17283
|
+
});
|
|
17284
|
+
return sections;
|
|
17285
|
+
}
|
|
17286
|
+
function buildGlobalSections(intent, graph, incidents) {
|
|
17287
|
+
switch (intent) {
|
|
17288
|
+
case "divergence":
|
|
17289
|
+
return [buildGlobalDivergenceSection(graph)];
|
|
17290
|
+
case "incidents":
|
|
17291
|
+
return [buildGlobalIncidentsSection(incidents)];
|
|
17292
|
+
case "overview":
|
|
17293
|
+
return buildOverviewSections(graph, incidents);
|
|
17294
|
+
default:
|
|
17295
|
+
return null;
|
|
17296
|
+
}
|
|
17297
|
+
}
|
|
17298
|
+
var SECTION_ORDER = {
|
|
17299
|
+
"root-cause": ["root-cause", "incidents", "blast", "observed", "divergence"],
|
|
17300
|
+
"blast-radius": ["blast", "dependencies", "observed", "incidents"],
|
|
17301
|
+
dependencies: ["dependencies", "observed", "blast"],
|
|
17302
|
+
observed: ["observed", "dependencies", "incidents"],
|
|
17303
|
+
incidents: ["incidents", "root-cause", "observed"],
|
|
17304
|
+
divergence: ["divergence", "observed", "dependencies", "incidents"],
|
|
17305
|
+
overview: ["dependencies", "observed", "blast", "incidents", "divergence"]
|
|
17306
|
+
};
|
|
17307
|
+
function buildSection(kind, graph, node, incidents, now) {
|
|
17308
|
+
switch (kind) {
|
|
17309
|
+
case "root-cause":
|
|
17310
|
+
return buildRootCauseSection(graph, node, incidents, now);
|
|
17311
|
+
case "dependencies":
|
|
17312
|
+
return buildDependenciesSection(graph, node);
|
|
17313
|
+
case "observed":
|
|
17314
|
+
return buildObservedSection(graph, node);
|
|
17315
|
+
case "blast":
|
|
17316
|
+
return buildBlastSection(graph, node);
|
|
17317
|
+
case "incidents":
|
|
17318
|
+
return buildIncidentsSection(node, incidents);
|
|
17319
|
+
case "divergence":
|
|
17320
|
+
return buildDivergenceSection(graph, node);
|
|
17321
|
+
}
|
|
17322
|
+
}
|
|
17323
|
+
function summarizeGlobal(intent, sections) {
|
|
17324
|
+
const lead = sections[0];
|
|
17325
|
+
switch (intent) {
|
|
17326
|
+
case "divergence": {
|
|
17327
|
+
const none = lead?.facts[0]?.text.startsWith("None") ?? true;
|
|
17328
|
+
return none ? "No divergences across the graph \u2014 declared code and observed runtime agree." : `${lead.heading} across the graph, highest-confidence first below.`;
|
|
17329
|
+
}
|
|
17330
|
+
case "incidents": {
|
|
17331
|
+
const none = lead?.facts[0]?.text.startsWith("None") ?? true;
|
|
17332
|
+
return none ? "No incidents recorded across the system \u2014 the OBSERVED incident store is empty." : `${lead.heading}, aggregated by node below.`;
|
|
17333
|
+
}
|
|
17334
|
+
case "overview":
|
|
17335
|
+
default:
|
|
17336
|
+
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.`;
|
|
17337
|
+
}
|
|
17338
|
+
}
|
|
17339
|
+
function summarize(question, intent, matched, primary, sections, scope) {
|
|
17340
|
+
if (scope === "global") {
|
|
17341
|
+
return summarizeGlobal(intent, sections);
|
|
17342
|
+
}
|
|
17343
|
+
if (!primary) {
|
|
17344
|
+
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.`;
|
|
17345
|
+
}
|
|
17346
|
+
const lead = sections[0];
|
|
17347
|
+
const others = matched.slice(1);
|
|
17348
|
+
const alsoNote = others.length ? ` Also matched: ${others.map((m) => m.nodeId).join(", ")}.` : "";
|
|
17349
|
+
let core;
|
|
17350
|
+
switch (intent) {
|
|
17351
|
+
case "root-cause": {
|
|
17352
|
+
const rc = sections.find((s) => s.heading === "Root cause (navigation)");
|
|
17353
|
+
if (rc) {
|
|
17354
|
+
core = rc.facts[0]?.text ?? "";
|
|
17355
|
+
} else if (lead) {
|
|
17356
|
+
core = `No root cause surfaced for ${primary} \u2014 it may be healthy, or the failure isn't recorded. Nearest context: ${lead.heading.toLowerCase()}.`;
|
|
17357
|
+
} else {
|
|
17358
|
+
core = `No root cause surfaced for ${primary} \u2014 it may be healthy.`;
|
|
17359
|
+
}
|
|
17360
|
+
break;
|
|
17361
|
+
}
|
|
17362
|
+
case "blast-radius":
|
|
17363
|
+
core = lead ? `${lead.heading} of ${primary}.` : `${primary} has no dependents \u2014 nothing else would break if it failed.`;
|
|
17364
|
+
break;
|
|
17365
|
+
case "dependencies":
|
|
17366
|
+
core = lead ? `${primary}: ${lead.heading.toLowerCase()} listed below.` : `${primary} has no declared dependencies in the graph.`;
|
|
17367
|
+
break;
|
|
17368
|
+
case "observed":
|
|
17369
|
+
core = lead ? `${primary} at runtime: ${lead.facts.length} OBSERVED fact${lead.facts.length === 1 ? "" : "s"}.` : `No runtime traffic OBSERVED for ${primary}.`;
|
|
17370
|
+
break;
|
|
17371
|
+
case "incidents":
|
|
17372
|
+
core = lead ? `${primary}: ${lead.heading.toLowerCase()}.` : `No incidents recorded against ${primary}.`;
|
|
17373
|
+
break;
|
|
17374
|
+
case "divergence":
|
|
17375
|
+
core = lead ? `${lead.heading} involving ${primary}.` : `No divergences involve ${primary} \u2014 declared and observed agree here.`;
|
|
17376
|
+
break;
|
|
17377
|
+
default:
|
|
17378
|
+
core = `${primary}: ${sections.length} view${sections.length === 1 ? "" : "s"} of its fused local context below.`;
|
|
17379
|
+
}
|
|
17380
|
+
return `${core}${alsoNote}`;
|
|
17381
|
+
}
|
|
17382
|
+
async function askGraph(graph, question, opts = {}) {
|
|
17383
|
+
const now = opts.now ?? Date.now();
|
|
17384
|
+
const maxNodes = opts.maxNodes ?? DEFAULT_MAX_NODES;
|
|
17385
|
+
const intent = classifyIntent(question);
|
|
17386
|
+
const matched = await resolveEntities(graph, question, opts.searchIndex, maxNodes);
|
|
17387
|
+
const primary = matched[0]?.nodeId;
|
|
17388
|
+
const sections = [];
|
|
17389
|
+
let scope;
|
|
17390
|
+
if (primary) {
|
|
17391
|
+
scope = "node";
|
|
17392
|
+
for (const kind of SECTION_ORDER[intent]) {
|
|
17393
|
+
const s = buildSection(kind, graph, primary, opts.incidents, now);
|
|
17394
|
+
if (s && s.facts.length > 0) sections.push(s);
|
|
17395
|
+
}
|
|
17396
|
+
} else {
|
|
17397
|
+
const global = buildGlobalSections(intent, graph, opts.incidents);
|
|
17398
|
+
if (global) {
|
|
17399
|
+
scope = "global";
|
|
17400
|
+
for (const s of global) if (s.facts.length > 0) sections.push(s);
|
|
17401
|
+
}
|
|
17402
|
+
}
|
|
17403
|
+
const answer = summarize(question, intent, matched, primary, sections, scope);
|
|
17404
|
+
const provSet = /* @__PURE__ */ new Set();
|
|
17405
|
+
for (const s of sections) for (const f of s.facts) if (f.provenance) provSet.add(f.provenance);
|
|
17406
|
+
const confidence = sections[0]?.facts[0]?.confidence;
|
|
17407
|
+
return import_types60.AskResultSchema.parse({
|
|
17408
|
+
question,
|
|
17409
|
+
intent,
|
|
17410
|
+
matched,
|
|
17411
|
+
...primary ? { primaryNode: primary } : {},
|
|
17412
|
+
...scope ? { scope } : {},
|
|
17413
|
+
sections,
|
|
17414
|
+
answer,
|
|
17415
|
+
...confidence !== void 0 ? { confidence } : {},
|
|
17416
|
+
provenance: [...provSet]
|
|
17417
|
+
});
|
|
17418
|
+
}
|
|
17419
|
+
|
|
16736
17420
|
// src/diff.ts
|
|
16737
17421
|
init_cjs_shims();
|
|
16738
17422
|
var import_node_fs38 = require("fs");
|
|
@@ -16867,7 +17551,7 @@ init_cjs_shims();
|
|
|
16867
17551
|
var import_node_fs39 = require("fs");
|
|
16868
17552
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
16869
17553
|
var import_node_path74 = __toESM(require("path"), 1);
|
|
16870
|
-
var
|
|
17554
|
+
var import_types61 = require("@neat.is/types");
|
|
16871
17555
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
16872
17556
|
var LOCK_RETRY_MS = 50;
|
|
16873
17557
|
function neatHome() {
|
|
@@ -17121,10 +17805,10 @@ async function readRegistry() {
|
|
|
17121
17805
|
throw err;
|
|
17122
17806
|
}
|
|
17123
17807
|
const parsed = JSON.parse(raw);
|
|
17124
|
-
return
|
|
17808
|
+
return import_types61.RegistryFileSchema.parse(parsed);
|
|
17125
17809
|
}
|
|
17126
17810
|
async function writeRegistry(reg) {
|
|
17127
|
-
const validated =
|
|
17811
|
+
const validated = import_types61.RegistryFileSchema.parse(reg);
|
|
17128
17812
|
await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
|
|
17129
17813
|
}
|
|
17130
17814
|
var ProjectNameCollisionError = class extends Error {
|
|
@@ -17637,15 +18321,15 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
|
|
|
17637
18321
|
|
|
17638
18322
|
// src/connectors/index.ts
|
|
17639
18323
|
init_cjs_shims();
|
|
17640
|
-
var
|
|
18324
|
+
var import_types62 = require("@neat.is/types");
|
|
17641
18325
|
var NO_ENV = "unknown";
|
|
17642
18326
|
function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
17643
18327
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
17644
18328
|
const sites = [];
|
|
17645
18329
|
for (const edgeId of graph.inboundEdges(targetNodeId)) {
|
|
17646
18330
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
17647
|
-
if (edge.provenance !==
|
|
17648
|
-
const parsed = (0,
|
|
18331
|
+
if (edge.provenance !== import_types62.Provenance.EXTRACTED) continue;
|
|
18332
|
+
const parsed = (0, import_types62.parseFileId)(edge.source);
|
|
17649
18333
|
if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
|
|
17650
18334
|
const site = { relPath: edge.evidence.file };
|
|
17651
18335
|
if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
|
|
@@ -17656,7 +18340,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
|
17656
18340
|
function routeCallSiteFor(graph, targetNodeId) {
|
|
17657
18341
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
17658
18342
|
const attrs = graph.getNodeAttributes(targetNodeId);
|
|
17659
|
-
if (attrs.type !==
|
|
18343
|
+
if (attrs.type !== import_types62.NodeType.RouteNode || !attrs.path) return void 0;
|
|
17660
18344
|
const site = { relPath: attrs.path };
|
|
17661
18345
|
if (attrs.line !== void 0) site.line = attrs.line;
|
|
17662
18346
|
return site;
|
|
@@ -18154,10 +18838,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
|
|
|
18154
18838
|
// src/connectors/supabase/map.ts
|
|
18155
18839
|
var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
|
|
18156
18840
|
var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
|
|
18157
|
-
function targetFromRestPath(
|
|
18158
|
-
const rpcMatch = REST_RPC_PATH_RE.exec(
|
|
18841
|
+
function targetFromRestPath(path93) {
|
|
18842
|
+
const rpcMatch = REST_RPC_PATH_RE.exec(path93);
|
|
18159
18843
|
if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
|
|
18160
|
-
const tableMatch = REST_TABLE_PATH_RE.exec(
|
|
18844
|
+
const tableMatch = REST_TABLE_PATH_RE.exec(path93);
|
|
18161
18845
|
if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
|
|
18162
18846
|
return null;
|
|
18163
18847
|
}
|
|
@@ -18268,23 +18952,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
|
|
|
18268
18952
|
|
|
18269
18953
|
// src/connectors/supabase/resolve.ts
|
|
18270
18954
|
init_cjs_shims();
|
|
18271
|
-
var
|
|
18955
|
+
var import_types64 = require("@neat.is/types");
|
|
18272
18956
|
function createSupabaseResolveTarget(graph, config) {
|
|
18273
18957
|
return (signal, _ctx) => {
|
|
18274
18958
|
if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
|
|
18275
18959
|
return null;
|
|
18276
18960
|
}
|
|
18277
|
-
const subResourceId = (0,
|
|
18961
|
+
const subResourceId = (0, import_types64.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
|
|
18278
18962
|
if (graph.hasNode(subResourceId)) {
|
|
18279
|
-
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType:
|
|
18963
|
+
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types64.EdgeType.CALLS };
|
|
18280
18964
|
}
|
|
18281
|
-
const bareResourceId = (0,
|
|
18965
|
+
const bareResourceId = (0, import_types64.infraId)(signal.targetKind, signal.targetName);
|
|
18282
18966
|
if (graph.hasNode(bareResourceId)) {
|
|
18283
|
-
return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType:
|
|
18967
|
+
return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types64.EdgeType.CALLS };
|
|
18284
18968
|
}
|
|
18285
|
-
const projectLevelId = (0,
|
|
18969
|
+
const projectLevelId = (0, import_types64.infraId)("supabase", config.nodeRef);
|
|
18286
18970
|
if (graph.hasNode(projectLevelId)) {
|
|
18287
|
-
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType:
|
|
18971
|
+
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types64.EdgeType.CALLS };
|
|
18288
18972
|
}
|
|
18289
18973
|
return null;
|
|
18290
18974
|
};
|
|
@@ -18377,7 +19061,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
|
|
|
18377
19061
|
|
|
18378
19062
|
// src/connectors/railway/index.ts
|
|
18379
19063
|
init_cjs_shims();
|
|
18380
|
-
var
|
|
19064
|
+
var import_types68 = require("@neat.is/types");
|
|
18381
19065
|
|
|
18382
19066
|
// src/connectors/railway/client.ts
|
|
18383
19067
|
init_cjs_shims();
|
|
@@ -18528,7 +19212,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
|
|
|
18528
19212
|
const out = [];
|
|
18529
19213
|
graph.forEachNode((_id, attrs) => {
|
|
18530
19214
|
const node = attrs;
|
|
18531
|
-
if (node.type !==
|
|
19215
|
+
if (node.type !== import_types68.NodeType.RouteNode) return;
|
|
18532
19216
|
const route = attrs;
|
|
18533
19217
|
if (route.service !== serviceName) return;
|
|
18534
19218
|
out.push({
|
|
@@ -18632,12 +19316,12 @@ function createRailwayResolveTarget(config) {
|
|
|
18632
19316
|
const serviceName = config.serviceNameById[config.serviceId];
|
|
18633
19317
|
if (!serviceName) return null;
|
|
18634
19318
|
if (signal.targetKind === ROUTE_TARGET_KIND) {
|
|
18635
|
-
return { targetNodeId: signal.targetName, serviceName, edgeType:
|
|
19319
|
+
return { targetNodeId: signal.targetName, serviceName, edgeType: import_types68.EdgeType.CALLS };
|
|
18636
19320
|
}
|
|
18637
19321
|
if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
|
|
18638
19322
|
const peerName = config.serviceNameById[signal.targetName];
|
|
18639
19323
|
if (!peerName) return null;
|
|
18640
|
-
return { targetNodeId: (0,
|
|
19324
|
+
return { targetNodeId: (0, import_types68.serviceId)(peerName), serviceName, edgeType: import_types68.EdgeType.CONNECTS_TO };
|
|
18641
19325
|
}
|
|
18642
19326
|
return null;
|
|
18643
19327
|
};
|
|
@@ -18761,9 +19445,9 @@ function parseFirebaseTargetName(targetName) {
|
|
|
18761
19445
|
const secondSep = rest.indexOf(FIELD_SEP);
|
|
18762
19446
|
if (secondSep === -1) return null;
|
|
18763
19447
|
const method = rest.slice(0, secondSep);
|
|
18764
|
-
const
|
|
18765
|
-
if (!resourceName || !method || !
|
|
18766
|
-
return { resourceName, method, path:
|
|
19448
|
+
const path93 = rest.slice(secondSep + 1);
|
|
19449
|
+
if (!resourceName || !method || !path93) return null;
|
|
19450
|
+
return { resourceName, method, path: path93 };
|
|
18767
19451
|
}
|
|
18768
19452
|
function resourceNameFor(type, labels) {
|
|
18769
19453
|
if (!labels) return null;
|
|
@@ -18801,14 +19485,14 @@ function mapLogEntryToSignal(entry2) {
|
|
|
18801
19485
|
if (!req) return null;
|
|
18802
19486
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
18803
19487
|
const method = req.requestMethod.toUpperCase();
|
|
18804
|
-
const
|
|
18805
|
-
if (
|
|
19488
|
+
const path93 = pathFromRequestUrl(req.requestUrl);
|
|
19489
|
+
if (path93 === null) return null;
|
|
18806
19490
|
const timestamp = entry2.timestamp;
|
|
18807
19491
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
18808
19492
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
|
|
18809
19493
|
return {
|
|
18810
19494
|
targetKind: resourceType,
|
|
18811
|
-
targetName: packFirebaseTargetName({ resourceName, method, path:
|
|
19495
|
+
targetName: packFirebaseTargetName({ resourceName, method, path: path93 }),
|
|
18812
19496
|
callCount: 1,
|
|
18813
19497
|
errorCount: isError ? 1 : 0,
|
|
18814
19498
|
lastObservedIso: timestamp
|
|
@@ -18825,7 +19509,7 @@ function mapLogEntriesToSignals(entries) {
|
|
|
18825
19509
|
|
|
18826
19510
|
// src/connectors/firebase/resolve.ts
|
|
18827
19511
|
init_cjs_shims();
|
|
18828
|
-
var
|
|
19512
|
+
var import_types69 = require("@neat.is/types");
|
|
18829
19513
|
function neatServiceNameFor(resourceType, resourceName, serviceMap) {
|
|
18830
19514
|
switch (resourceType) {
|
|
18831
19515
|
case "cloud_function":
|
|
@@ -18840,7 +19524,7 @@ function routeEntriesFor(graph, serviceName) {
|
|
|
18840
19524
|
const entries = [];
|
|
18841
19525
|
graph.forEachNode((_id, attrs) => {
|
|
18842
19526
|
const node = attrs;
|
|
18843
|
-
if (node.type !==
|
|
19527
|
+
if (node.type !== import_types69.NodeType.RouteNode) return;
|
|
18844
19528
|
const route = attrs;
|
|
18845
19529
|
if (route.service !== serviceName) return;
|
|
18846
19530
|
entries.push({
|
|
@@ -18872,7 +19556,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
|
|
|
18872
19556
|
return {
|
|
18873
19557
|
targetNodeId: match.routeNodeId,
|
|
18874
19558
|
serviceName,
|
|
18875
|
-
edgeType:
|
|
19559
|
+
edgeType: import_types69.EdgeType.CALLS
|
|
18876
19560
|
};
|
|
18877
19561
|
};
|
|
18878
19562
|
}
|
|
@@ -18899,7 +19583,7 @@ init_cjs_shims();
|
|
|
18899
19583
|
|
|
18900
19584
|
// src/connectors/cloudflare/connector.ts
|
|
18901
19585
|
init_cjs_shims();
|
|
18902
|
-
var
|
|
19586
|
+
var import_types71 = require("@neat.is/types");
|
|
18903
19587
|
|
|
18904
19588
|
// src/connectors/cloudflare/client.ts
|
|
18905
19589
|
init_cjs_shims();
|
|
@@ -19015,7 +19699,7 @@ function mapEventToSignal(event) {
|
|
|
19015
19699
|
if (Number.isNaN(observedAt.getTime())) return null;
|
|
19016
19700
|
const statusCode = metadata?.statusCode;
|
|
19017
19701
|
const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
|
|
19018
|
-
const
|
|
19702
|
+
const path93 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
|
|
19019
19703
|
return {
|
|
19020
19704
|
targetKind: CLOUDFLARE_TARGET_KIND,
|
|
19021
19705
|
targetName: scriptName,
|
|
@@ -19023,7 +19707,7 @@ function mapEventToSignal(event) {
|
|
|
19023
19707
|
errorCount: isError ? 1 : 0,
|
|
19024
19708
|
lastObservedIso: observedAt.toISOString(),
|
|
19025
19709
|
method,
|
|
19026
|
-
...
|
|
19710
|
+
...path93 ? { path: path93 } : {},
|
|
19027
19711
|
...typeof statusCode === "number" ? { statusCode } : {},
|
|
19028
19712
|
...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
|
|
19029
19713
|
};
|
|
@@ -19063,19 +19747,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
19063
19747
|
graph.forEachNode((id, attrs) => {
|
|
19064
19748
|
if (found) return;
|
|
19065
19749
|
const a = attrs;
|
|
19066
|
-
if (a.type ===
|
|
19750
|
+
if (a.type === import_types71.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
|
|
19067
19751
|
found = id;
|
|
19068
19752
|
}
|
|
19069
19753
|
});
|
|
19070
19754
|
return found;
|
|
19071
19755
|
}
|
|
19072
|
-
function findMatchingRouteNode(graph, serviceName, method,
|
|
19073
|
-
const normalizedPath = normalizePathTemplate(
|
|
19756
|
+
function findMatchingRouteNode(graph, serviceName, method, path93) {
|
|
19757
|
+
const normalizedPath = normalizePathTemplate(path93);
|
|
19074
19758
|
let found = null;
|
|
19075
19759
|
graph.forEachNode((id, attrs) => {
|
|
19076
19760
|
if (found) return;
|
|
19077
19761
|
const a = attrs;
|
|
19078
|
-
if (a.type !==
|
|
19762
|
+
if (a.type !== import_types71.NodeType.RouteNode || a.service !== serviceName) return;
|
|
19079
19763
|
if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
|
|
19080
19764
|
const routeMethod = (a.method ?? "").toUpperCase();
|
|
19081
19765
|
if (routeMethod !== "ALL" && routeMethod !== method) return;
|
|
@@ -19087,18 +19771,18 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
19087
19771
|
return (signal) => {
|
|
19088
19772
|
if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
|
|
19089
19773
|
const scriptName = signal.targetName;
|
|
19090
|
-
const { method, path:
|
|
19774
|
+
const { method, path: path93 } = signal;
|
|
19091
19775
|
const resolveRouteGrain = (serviceName, wholeFileId) => {
|
|
19092
|
-
if (!method || !
|
|
19093
|
-
return findMatchingRouteNode(graph, serviceName, method,
|
|
19776
|
+
if (!method || !path93) return wholeFileId;
|
|
19777
|
+
return findMatchingRouteNode(graph, serviceName, method, path93) ?? wholeFileId;
|
|
19094
19778
|
};
|
|
19095
19779
|
const mapping = config.workers?.[scriptName];
|
|
19096
19780
|
if (mapping) {
|
|
19097
|
-
const wholeFileId = (0,
|
|
19781
|
+
const wholeFileId = (0, import_types71.fileId)(mapping.service, mapping.entryFile);
|
|
19098
19782
|
return {
|
|
19099
19783
|
targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
|
|
19100
19784
|
serviceName: mapping.service,
|
|
19101
|
-
edgeType:
|
|
19785
|
+
edgeType: import_types71.EdgeType.CALLS
|
|
19102
19786
|
};
|
|
19103
19787
|
}
|
|
19104
19788
|
const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
|
|
@@ -19107,13 +19791,13 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
19107
19791
|
return {
|
|
19108
19792
|
targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
|
|
19109
19793
|
serviceName: fileNode.service,
|
|
19110
|
-
edgeType:
|
|
19794
|
+
edgeType: import_types71.EdgeType.CALLS
|
|
19111
19795
|
};
|
|
19112
19796
|
}
|
|
19113
19797
|
return {
|
|
19114
|
-
targetNodeId: (0,
|
|
19798
|
+
targetNodeId: (0, import_types71.infraId)("cloudflare-worker", scriptName),
|
|
19115
19799
|
serviceName: scriptName,
|
|
19116
|
-
edgeType:
|
|
19800
|
+
edgeType: import_types71.EdgeType.CALLS,
|
|
19117
19801
|
ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
|
|
19118
19802
|
};
|
|
19119
19803
|
};
|
|
@@ -19309,14 +19993,14 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
|
|
|
19309
19993
|
|
|
19310
19994
|
// src/connectors/neon/resolve.ts
|
|
19311
19995
|
init_cjs_shims();
|
|
19312
|
-
var
|
|
19996
|
+
var import_types75 = require("@neat.is/types");
|
|
19313
19997
|
function createNeonResolveTarget(config) {
|
|
19314
19998
|
return (signal) => {
|
|
19315
19999
|
if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
|
|
19316
20000
|
return {
|
|
19317
|
-
targetNodeId: (0,
|
|
20001
|
+
targetNodeId: (0, import_types75.infraId)("sql-table", signal.targetName),
|
|
19318
20002
|
serviceName: config.serviceName,
|
|
19319
|
-
edgeType:
|
|
20003
|
+
edgeType: import_types75.EdgeType.CALLS,
|
|
19320
20004
|
ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
|
|
19321
20005
|
};
|
|
19322
20006
|
};
|
|
@@ -19442,9 +20126,9 @@ function parseCloudRunTargetName(targetName) {
|
|
|
19442
20126
|
const secondSep = rest.indexOf(FIELD_SEP2);
|
|
19443
20127
|
if (secondSep === -1) return null;
|
|
19444
20128
|
const method = rest.slice(0, secondSep);
|
|
19445
|
-
const
|
|
19446
|
-
if (!serviceName || !method || !
|
|
19447
|
-
return { serviceName, method, path:
|
|
20129
|
+
const path93 = rest.slice(secondSep + 1);
|
|
20130
|
+
if (!serviceName || !method || !path93) return null;
|
|
20131
|
+
return { serviceName, method, path: path93 };
|
|
19448
20132
|
}
|
|
19449
20133
|
|
|
19450
20134
|
// src/connectors/cloud-run/map.ts
|
|
@@ -19473,14 +20157,14 @@ function mapLogEntryToSignal2(entry2) {
|
|
|
19473
20157
|
if (!req) return null;
|
|
19474
20158
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
19475
20159
|
const method = req.requestMethod.toUpperCase();
|
|
19476
|
-
const
|
|
19477
|
-
if (
|
|
20160
|
+
const path93 = pathFromRequestUrl2(req.requestUrl);
|
|
20161
|
+
if (path93 === null) return null;
|
|
19478
20162
|
const timestamp = entry2.timestamp;
|
|
19479
20163
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
19480
20164
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
|
|
19481
20165
|
return {
|
|
19482
20166
|
targetKind: CLOUD_RUN_TARGET_KIND,
|
|
19483
|
-
targetName: packCloudRunTargetName({ serviceName, method, path:
|
|
20167
|
+
targetName: packCloudRunTargetName({ serviceName, method, path: path93 }),
|
|
19484
20168
|
callCount: 1,
|
|
19485
20169
|
errorCount: isError ? 1 : 0,
|
|
19486
20170
|
lastObservedIso: timestamp
|
|
@@ -19497,14 +20181,14 @@ function mapLogEntriesToSignals2(entries) {
|
|
|
19497
20181
|
|
|
19498
20182
|
// src/connectors/cloud-run/resolve.ts
|
|
19499
20183
|
init_cjs_shims();
|
|
19500
|
-
var
|
|
20184
|
+
var import_types79 = require("@neat.is/types");
|
|
19501
20185
|
var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
|
|
19502
20186
|
function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
|
|
19503
20187
|
let found = null;
|
|
19504
20188
|
graph.forEachNode((_id, attrs) => {
|
|
19505
20189
|
if (found) return;
|
|
19506
20190
|
const node = attrs;
|
|
19507
|
-
if (node.type !==
|
|
20191
|
+
if (node.type !== import_types79.NodeType.RouteNode) return;
|
|
19508
20192
|
const route = attrs;
|
|
19509
20193
|
if (route.service !== serviceName || !route.pathTemplate) return;
|
|
19510
20194
|
if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
|
|
@@ -19519,23 +20203,23 @@ function createCloudRunResolveTarget(graph, config) {
|
|
|
19519
20203
|
if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
|
|
19520
20204
|
const identity = parseCloudRunTargetName(signal.targetName);
|
|
19521
20205
|
if (!identity) return null;
|
|
19522
|
-
const { serviceName: gcpServiceName, method, path:
|
|
20206
|
+
const { serviceName: gcpServiceName, method, path: path93 } = identity;
|
|
19523
20207
|
const mappedService = config.serviceMap?.[gcpServiceName];
|
|
19524
20208
|
if (mappedService) {
|
|
19525
20209
|
const routeNodeId = findMatchingRouteNode2(
|
|
19526
20210
|
graph,
|
|
19527
20211
|
mappedService,
|
|
19528
20212
|
method,
|
|
19529
|
-
normalizePathTemplate(
|
|
20213
|
+
normalizePathTemplate(path93)
|
|
19530
20214
|
);
|
|
19531
20215
|
if (routeNodeId) {
|
|
19532
|
-
return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType:
|
|
20216
|
+
return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types79.EdgeType.CALLS };
|
|
19533
20217
|
}
|
|
19534
20218
|
}
|
|
19535
20219
|
return {
|
|
19536
|
-
targetNodeId: (0,
|
|
20220
|
+
targetNodeId: (0, import_types79.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
|
|
19537
20221
|
serviceName: mappedService ?? gcpServiceName,
|
|
19538
|
-
edgeType:
|
|
20222
|
+
edgeType: import_types79.EdgeType.CALLS,
|
|
19539
20223
|
ensureInfraNode: {
|
|
19540
20224
|
kind: CLOUD_RUN_SERVICE_INFRA_KIND,
|
|
19541
20225
|
name: gcpServiceName,
|
|
@@ -19576,7 +20260,7 @@ function createCloudRunConnector(graph, config = {}) {
|
|
|
19576
20260
|
|
|
19577
20261
|
// src/connectors/render/index.ts
|
|
19578
20262
|
init_cjs_shims();
|
|
19579
|
-
var
|
|
20263
|
+
var import_types82 = require("@neat.is/types");
|
|
19580
20264
|
|
|
19581
20265
|
// src/connectors/render/types.ts
|
|
19582
20266
|
init_cjs_shims();
|
|
@@ -19654,7 +20338,7 @@ function buildRenderRouteIndex(graph, serviceName) {
|
|
|
19654
20338
|
const out = [];
|
|
19655
20339
|
graph.forEachNode((_id, attrs) => {
|
|
19656
20340
|
const node = attrs;
|
|
19657
|
-
if (node.type !==
|
|
20341
|
+
if (node.type !== import_types82.NodeType.RouteNode) return;
|
|
19658
20342
|
const route = attrs;
|
|
19659
20343
|
if (route.service !== serviceName) return;
|
|
19660
20344
|
out.push({
|
|
@@ -19739,7 +20423,7 @@ function mapRenderRequestLogsToSignals(entries, routeIndex) {
|
|
|
19739
20423
|
function createRenderResolveTarget(config) {
|
|
19740
20424
|
return (signal) => {
|
|
19741
20425
|
if (signal.targetKind === ROUTE_TARGET_KIND2) {
|
|
19742
|
-
return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType:
|
|
20426
|
+
return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types82.EdgeType.CALLS };
|
|
19743
20427
|
}
|
|
19744
20428
|
return null;
|
|
19745
20429
|
};
|
|
@@ -19877,21 +20561,21 @@ function mapInsightsToSignals(rows, observedAtIso) {
|
|
|
19877
20561
|
|
|
19878
20562
|
// src/connectors/planetscale/resolve.ts
|
|
19879
20563
|
init_cjs_shims();
|
|
19880
|
-
var
|
|
20564
|
+
var import_types86 = require("@neat.is/types");
|
|
19881
20565
|
var PLANETSCALE_DATABASE_KIND = "planetscale-database";
|
|
19882
20566
|
function createPlanetscaleResolveTarget(graph, config) {
|
|
19883
20567
|
const databaseName = `${config.organization}/${config.database}`;
|
|
19884
20568
|
return (signal, _ctx) => {
|
|
19885
20569
|
if (signal.targetKind !== PLANETSCALE_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
|
|
19886
|
-
const tableId = (0,
|
|
20570
|
+
const tableId = (0, import_types86.infraId)("sql-table", signal.targetName);
|
|
19887
20571
|
if (graph.hasNode(tableId)) {
|
|
19888
|
-
return { targetNodeId: tableId, serviceName: config.serviceName, edgeType:
|
|
20572
|
+
return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: import_types86.EdgeType.CALLS };
|
|
19889
20573
|
}
|
|
19890
|
-
const providerId = (0,
|
|
20574
|
+
const providerId = (0, import_types86.infraId)(PLANETSCALE_DATABASE_KIND, databaseName);
|
|
19891
20575
|
return {
|
|
19892
20576
|
targetNodeId: providerId,
|
|
19893
20577
|
serviceName: config.serviceName,
|
|
19894
|
-
edgeType:
|
|
20578
|
+
edgeType: import_types86.EdgeType.CALLS,
|
|
19895
20579
|
ensureInfraNode: { kind: PLANETSCALE_DATABASE_KIND, name: databaseName, provider: "planetscale" }
|
|
19896
20580
|
};
|
|
19897
20581
|
};
|
|
@@ -20156,7 +20840,7 @@ function mapBuildsToSignals(builds, serviceName) {
|
|
|
20156
20840
|
|
|
20157
20841
|
// src/connectors/eas/resolve.ts
|
|
20158
20842
|
init_cjs_shims();
|
|
20159
|
-
var
|
|
20843
|
+
var import_types91 = require("@neat.is/types");
|
|
20160
20844
|
var NO_ENV2 = "unknown";
|
|
20161
20845
|
var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
|
|
20162
20846
|
var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
|
|
@@ -20172,8 +20856,8 @@ function configBasenamesForPhase(phase) {
|
|
|
20172
20856
|
function configNodeService(graph, configNodeId) {
|
|
20173
20857
|
for (const edgeId of graph.inboundEdges(configNodeId)) {
|
|
20174
20858
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
20175
|
-
if (edge.type !==
|
|
20176
|
-
const parsed = (0,
|
|
20859
|
+
if (edge.type !== import_types91.EdgeType.CONFIGURED_BY) continue;
|
|
20860
|
+
const parsed = (0, import_types91.parseFileId)(edge.source);
|
|
20177
20861
|
if (parsed) return parsed.service;
|
|
20178
20862
|
}
|
|
20179
20863
|
return null;
|
|
@@ -20184,7 +20868,7 @@ function findConfigNode(graph, basenames, serviceName) {
|
|
|
20184
20868
|
graph.forEachNode((id, attrs) => {
|
|
20185
20869
|
if (scoped) return;
|
|
20186
20870
|
const node = attrs;
|
|
20187
|
-
if (node.type !==
|
|
20871
|
+
if (node.type !== import_types91.NodeType.ConfigNode) return;
|
|
20188
20872
|
if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
|
|
20189
20873
|
if (anyMatch === null) anyMatch = id;
|
|
20190
20874
|
if (configNodeService(graph, id) === serviceName) scoped = id;
|
|
@@ -20201,13 +20885,13 @@ function createEasResolveTarget(graph) {
|
|
|
20201
20885
|
if (basenames.length > 0) {
|
|
20202
20886
|
const configNodeId = findConfigNode(graph, basenames, serviceName);
|
|
20203
20887
|
if (configNodeId) {
|
|
20204
|
-
return { targetNodeId: configNodeId, serviceName, edgeType:
|
|
20888
|
+
return { targetNodeId: configNodeId, serviceName, edgeType: import_types91.EdgeType.CALLS };
|
|
20205
20889
|
}
|
|
20206
20890
|
}
|
|
20207
20891
|
return {
|
|
20208
20892
|
targetNodeId: resolveFusedServiceId(graph, serviceName, NO_ENV2),
|
|
20209
20893
|
serviceName,
|
|
20210
|
-
edgeType:
|
|
20894
|
+
edgeType: import_types91.EdgeType.CALLS
|
|
20211
20895
|
};
|
|
20212
20896
|
};
|
|
20213
20897
|
}
|
|
@@ -21011,11 +21695,11 @@ function registerRoutes(scope, ctx) {
|
|
|
21011
21695
|
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
21012
21696
|
const parsed = [];
|
|
21013
21697
|
for (const c of candidates) {
|
|
21014
|
-
const r =
|
|
21698
|
+
const r = import_types94.DivergenceTypeSchema.safeParse(c);
|
|
21015
21699
|
if (!r.success) {
|
|
21016
21700
|
return reply.code(400).send({
|
|
21017
21701
|
error: `unknown divergence type "${c}"`,
|
|
21018
|
-
allowed:
|
|
21702
|
+
allowed: import_types94.DivergenceTypeSchema.options
|
|
21019
21703
|
});
|
|
21020
21704
|
}
|
|
21021
21705
|
parsed.push(r.data);
|
|
@@ -21257,6 +21941,18 @@ function registerRoutes(scope, ctx) {
|
|
|
21257
21941
|
matches: matches.slice(0, safeLimit)
|
|
21258
21942
|
};
|
|
21259
21943
|
});
|
|
21944
|
+
scope.get("/graph/ask", async (req, reply) => {
|
|
21945
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
21946
|
+
if (!proj) return;
|
|
21947
|
+
const question = (req.query.q ?? "").trim();
|
|
21948
|
+
if (!question) return reply.code(400).send({ error: "query parameter `q` is required" });
|
|
21949
|
+
const epath = errorsPathFor(proj);
|
|
21950
|
+
const incidents = epath ? await readErrorEvents(epath) : [];
|
|
21951
|
+
return askGraph(proj.graph, question, {
|
|
21952
|
+
...proj.searchIndex ? { searchIndex: proj.searchIndex } : {},
|
|
21953
|
+
incidents
|
|
21954
|
+
});
|
|
21955
|
+
});
|
|
21260
21956
|
scope.get(
|
|
21261
21957
|
"/graph/diff",
|
|
21262
21958
|
async (req, reply) => {
|
|
@@ -21357,7 +22053,7 @@ function registerRoutes(scope, ctx) {
|
|
|
21357
22053
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
21358
22054
|
let violations = await log.readAll();
|
|
21359
22055
|
if (req.query.severity) {
|
|
21360
|
-
const sev =
|
|
22056
|
+
const sev = import_types94.PolicySeveritySchema.safeParse(req.query.severity);
|
|
21361
22057
|
if (!sev.success) {
|
|
21362
22058
|
return reply.code(400).send({
|
|
21363
22059
|
error: "invalid severity",
|
|
@@ -21396,7 +22092,7 @@ function registerRoutes(scope, ctx) {
|
|
|
21396
22092
|
scope.post("/policies/check", async (req, reply) => {
|
|
21397
22093
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
21398
22094
|
if (!proj) return;
|
|
21399
|
-
const parsed =
|
|
22095
|
+
const parsed = import_types94.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
21400
22096
|
if (!parsed.success) {
|
|
21401
22097
|
return reply.code(400).send({
|
|
21402
22098
|
error: "invalid /policies/check body",
|
|
@@ -21729,7 +22425,7 @@ var import_node_fs41 = require("fs");
|
|
|
21729
22425
|
var import_node_path76 = __toESM(require("path"), 1);
|
|
21730
22426
|
|
|
21731
22427
|
// src/daemon.ts
|
|
21732
|
-
var
|
|
22428
|
+
var import_types95 = require("@neat.is/types");
|
|
21733
22429
|
function daemonJsonPath(scanPath) {
|
|
21734
22430
|
return import_node_path77.default.join(scanPath, "neat-out", "daemon.json");
|
|
21735
22431
|
}
|
|
@@ -23655,8 +24351,8 @@ function scriptHasShellChain(script) {
|
|
|
23655
24351
|
function entryFromScript(script) {
|
|
23656
24352
|
if (!script) return void 0;
|
|
23657
24353
|
if (scriptHasShellChain(script)) return void 0;
|
|
23658
|
-
const
|
|
23659
|
-
for (const token of
|
|
24354
|
+
const tokens2 = script.split(/\s+/).filter((t) => t.length > 0);
|
|
24355
|
+
for (const token of tokens2) {
|
|
23660
24356
|
const lower = token.toLowerCase();
|
|
23661
24357
|
if (SCRIPT_LAUNCHERS.has(lower)) continue;
|
|
23662
24358
|
const cleaned = token.startsWith("./") ? token.slice(2) : token;
|
|
@@ -25681,7 +26377,7 @@ async function extractAndPersist(opts) {
|
|
|
25681
26377
|
}
|
|
25682
26378
|
async function applyInstallersOver(services, project, options = {}) {
|
|
25683
26379
|
const resolveManager = options.resolveManager ?? detectPackageManager;
|
|
25684
|
-
const
|
|
26380
|
+
const runInstall2 = options.runInstall ?? runPackageManagerInstall;
|
|
25685
26381
|
let instrumented = 0;
|
|
25686
26382
|
let already = 0;
|
|
25687
26383
|
let libOnly = 0;
|
|
@@ -25804,7 +26500,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
25804
26500
|
const packageManagerInstalls = [];
|
|
25805
26501
|
for (const cmd of installPlans.values()) {
|
|
25806
26502
|
console.log(`running \`${cmd.pm} ${cmd.args.join(" ")}\` in ${cmd.cwd}`);
|
|
25807
|
-
const result = await
|
|
26503
|
+
const result = await runInstall2(cmd);
|
|
25808
26504
|
packageManagerInstalls.push(result);
|
|
25809
26505
|
if (result.exitCode !== 0) {
|
|
25810
26506
|
console.error(
|
|
@@ -26783,6 +27479,9 @@ function claudeSettingsPath() {
|
|
|
26783
27479
|
function installedHookPath() {
|
|
26784
27480
|
return import_node_path87.default.join(neatHome3(), "hooks", HOOK_FILENAME);
|
|
26785
27481
|
}
|
|
27482
|
+
function gateFlagPath() {
|
|
27483
|
+
return import_node_path87.default.join(neatHome3(), "hooks", "gate-enabled");
|
|
27484
|
+
}
|
|
26786
27485
|
function isNeatSearchEntry(entry2) {
|
|
26787
27486
|
return (entry2.hooks ?? []).some(
|
|
26788
27487
|
(h) => typeof h.command === "string" && h.command.includes(HOOK_FILENAME)
|
|
@@ -26845,13 +27544,30 @@ async function runHooks(opts) {
|
|
|
26845
27544
|
};
|
|
26846
27545
|
await import_node_fs52.promises.mkdir(import_node_path87.default.dirname(settingsFile), { recursive: true });
|
|
26847
27546
|
await import_node_fs52.promises.writeFile(settingsFile, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
26848
|
-
|
|
27547
|
+
const flag = gateFlagPath();
|
|
27548
|
+
if (opts.gate) {
|
|
27549
|
+
await import_node_fs52.promises.mkdir(import_node_path87.default.dirname(flag), { recursive: true });
|
|
27550
|
+
await import_node_fs52.promises.writeFile(flag, "1\n", "utf8");
|
|
27551
|
+
} else {
|
|
27552
|
+
await import_node_fs52.promises.rm(flag, { force: true });
|
|
27553
|
+
}
|
|
27554
|
+
const mode = opts.gate ? "GATE (deny search until you ask the graph)" : "nudge (search still runs)";
|
|
27555
|
+
console.log(`neat hooks: installed the search hook in ${opts.gate ? "gate" : "nudge"} mode`);
|
|
26849
27556
|
console.log(` script: ${scriptPath}`);
|
|
26850
27557
|
console.log(` settings: ${settingsFile} (PreToolUse \u2192 ${HOOK_MATCHER})`);
|
|
26851
27558
|
console.log(` guidance: ${guidePath}`);
|
|
27559
|
+
console.log(` mode: ${mode}`);
|
|
26852
27560
|
console.log("");
|
|
26853
|
-
|
|
26854
|
-
|
|
27561
|
+
if (opts.gate) {
|
|
27562
|
+
console.log("restart Claude Code to load the hook. A Grep/Glob or Bash grep is now DENIED");
|
|
27563
|
+
console.log('until you run `neat ask "<question>"` (or the ask MCP tool) once this session;');
|
|
27564
|
+
console.log("after that, search is allowed as a fallback. Set NEAT_SEARCH_GATE=0 to fall");
|
|
27565
|
+
console.log("back to nudge-only without re-running.");
|
|
27566
|
+
} else {
|
|
27567
|
+
console.log("restart Claude Code to load the hook. On a Grep/Glob or a Bash grep,");
|
|
27568
|
+
console.log("your agent will now be nudged to query NEAT first (the search still runs).");
|
|
27569
|
+
console.log("Re-run with --gate to hard-force the graph-first orientation.");
|
|
27570
|
+
}
|
|
26855
27571
|
console.log("");
|
|
26856
27572
|
console.log("The hook is Claude-Code-specific. For agents on other harnesses, paste");
|
|
26857
27573
|
console.log(`the guidance above into your project instructions (CLAUDE.md / AGENTS.md).`);
|
|
@@ -26863,28 +27579,36 @@ async function runHooks(opts) {
|
|
|
26863
27579
|
function usage() {
|
|
26864
27580
|
console.log("neat hooks \u2014 wire NEAT into your agent so it queries the graph before grepping");
|
|
26865
27581
|
console.log("");
|
|
26866
|
-
console.log(" --apply install the Claude Code search
|
|
27582
|
+
console.log(" --apply install the Claude Code search hook and write the");
|
|
26867
27583
|
console.log(" graph-first guidance to ~/.neat/, merging into");
|
|
26868
27584
|
console.log(" ~/.claude/settings.json without touching your other hooks");
|
|
27585
|
+
console.log(" --gate with --apply, enable hard-gate mode: DENY Grep/Glob/grep-Bash");
|
|
27586
|
+
console.log(" until `neat ask` has run this session (default is nudge-only).");
|
|
27587
|
+
console.log(" Toggle off at run time with NEAT_SEARCH_GATE=0.");
|
|
26869
27588
|
console.log(" --print-hook print the hook script to stdout");
|
|
26870
27589
|
console.log(" --print-guide print the agent-agnostic graph-first guidance to stdout");
|
|
26871
27590
|
console.log(" --print-settings print the settings.json PreToolUse block --apply would add");
|
|
26872
27591
|
console.log("");
|
|
26873
|
-
console.log("
|
|
26874
|
-
console.log("
|
|
27592
|
+
console.log("By default the hook is a gentle, non-blocking nudge \u2014 searches still run.");
|
|
27593
|
+
console.log("--gate turns it into a hard forcing mechanism. It is Claude-Code-specific;");
|
|
27594
|
+
console.log("other harnesses get the same steer from the graph-first guidance.");
|
|
26875
27595
|
}
|
|
26876
27596
|
async function runHooksCommand(args) {
|
|
26877
27597
|
const opts = {
|
|
26878
27598
|
apply: false,
|
|
26879
27599
|
printHook: false,
|
|
26880
27600
|
printGuide: false,
|
|
26881
|
-
printSettings: false
|
|
27601
|
+
printSettings: false,
|
|
27602
|
+
gate: false
|
|
26882
27603
|
};
|
|
26883
27604
|
for (const arg of args) {
|
|
26884
27605
|
switch (arg) {
|
|
26885
27606
|
case "--apply":
|
|
26886
27607
|
opts.apply = true;
|
|
26887
27608
|
break;
|
|
27609
|
+
case "--gate":
|
|
27610
|
+
opts.gate = true;
|
|
27611
|
+
break;
|
|
26888
27612
|
case "--print-hook":
|
|
26889
27613
|
opts.printHook = true;
|
|
26890
27614
|
break;
|
|
@@ -26913,11 +27637,151 @@ async function runHooksCommand(args) {
|
|
|
26913
27637
|
}
|
|
26914
27638
|
}
|
|
26915
27639
|
|
|
26916
|
-
// src/
|
|
27640
|
+
// src/claude-cli.ts
|
|
26917
27641
|
init_cjs_shims();
|
|
26918
27642
|
var import_node_path88 = __toESM(require("path"), 1);
|
|
26919
|
-
var import_node_os6 = __toESM(require("os"), 1);
|
|
26920
27643
|
var import_node_fs53 = require("fs");
|
|
27644
|
+
var NEAT_SECTION_HEADING = "## neat";
|
|
27645
|
+
var NEAT_DIRECTIVE_BODY = `This project has NEAT wired in: a live, fused semantic graph of the system \u2014
|
|
27646
|
+
code and runtime behaviour (OpenTelemetry) in one model, every fact tagged with
|
|
27647
|
+
its provenance (EXTRACTED / OBSERVED / INFERRED / STALE) and a confidence.
|
|
27648
|
+
|
|
27649
|
+
For ANY question about this system's behaviour, dependencies, failures, root
|
|
27650
|
+
cause, or blast radius, call \`neat ask\` FIRST \u2014 before Read, Grep, Glob, or Bash:
|
|
27651
|
+
|
|
27652
|
+
neat ask "why is checkout failing?"
|
|
27653
|
+
neat ask "what breaks if I change the orders table?"
|
|
27654
|
+
neat ask "what does the payments service depend on at runtime?"
|
|
27655
|
+
|
|
27656
|
+
\`ask\` resolves your question to graph nodes and routes it to the right traversal
|
|
27657
|
+
\u2014 root cause, dependencies, observed runtime calls, incidents, divergences,
|
|
27658
|
+
blast radius \u2014 and returns one compact, provenance-tagged answer. The same door
|
|
27659
|
+
is the \`ask\` MCP tool (\`mcp__neat__ask\`) when NEAT's MCP server is wired in.
|
|
27660
|
+
|
|
27661
|
+
The graph is live and fused: it is faster and more accurate than scanning files,
|
|
27662
|
+
and it can tell you what the system actually does at runtime, not only what the
|
|
27663
|
+
source declares. Fall back to Read/Grep only when the graph does not have what
|
|
27664
|
+
you need \u2014 comments, string literals, config minutiae. Ask the graph first,
|
|
27665
|
+
then scan.
|
|
27666
|
+
|
|
27667
|
+
If \`neat ask\` errors, the daemon may not be running (\`neat list\`) \u2014 start it
|
|
27668
|
+
with \`neat <path>\`, then re-ask.`;
|
|
27669
|
+
function neatSection() {
|
|
27670
|
+
return `${NEAT_SECTION_HEADING}
|
|
27671
|
+
|
|
27672
|
+
${NEAT_DIRECTIVE_BODY}
|
|
27673
|
+
`;
|
|
27674
|
+
}
|
|
27675
|
+
function claudeMdPath() {
|
|
27676
|
+
const override = process.env.NEAT_CLAUDE_MD;
|
|
27677
|
+
if (override && override.length > 0) return import_node_path88.default.resolve(override);
|
|
27678
|
+
return import_node_path88.default.join(process.cwd(), "CLAUDE.md");
|
|
27679
|
+
}
|
|
27680
|
+
function splitAroundSection(raw) {
|
|
27681
|
+
const lines = raw.split("\n");
|
|
27682
|
+
const startIdx = lines.findIndex((l) => l.replace(/\s+$/, "") === NEAT_SECTION_HEADING);
|
|
27683
|
+
if (startIdx === -1) {
|
|
27684
|
+
return { before: raw.replace(/\n*$/, ""), after: "", found: false };
|
|
27685
|
+
}
|
|
27686
|
+
let endIdx = lines.length;
|
|
27687
|
+
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
27688
|
+
if (/^#{1,2}\s+/.test(lines[i] ?? "")) {
|
|
27689
|
+
endIdx = i;
|
|
27690
|
+
break;
|
|
27691
|
+
}
|
|
27692
|
+
}
|
|
27693
|
+
const before = lines.slice(0, startIdx).join("\n").replace(/\n*$/, "");
|
|
27694
|
+
const after = lines.slice(endIdx).join("\n").replace(/^\n*/, "");
|
|
27695
|
+
return { before, after, found: true };
|
|
27696
|
+
}
|
|
27697
|
+
function compose(before, after) {
|
|
27698
|
+
const parts = [];
|
|
27699
|
+
if (before.length > 0) parts.push(before);
|
|
27700
|
+
parts.push(neatSection().replace(/\n+$/, ""));
|
|
27701
|
+
if (after.length > 0) parts.push(after);
|
|
27702
|
+
return parts.join("\n\n").replace(/\n*$/, "") + "\n";
|
|
27703
|
+
}
|
|
27704
|
+
async function readIfExists2(file) {
|
|
27705
|
+
try {
|
|
27706
|
+
return await import_node_fs53.promises.readFile(file, "utf8");
|
|
27707
|
+
} catch (err) {
|
|
27708
|
+
if (err.code === "ENOENT") return null;
|
|
27709
|
+
throw err;
|
|
27710
|
+
}
|
|
27711
|
+
}
|
|
27712
|
+
async function runInstall() {
|
|
27713
|
+
const file = claudeMdPath();
|
|
27714
|
+
const raw = await readIfExists2(file) ?? "";
|
|
27715
|
+
const { before, after, found } = splitAroundSection(raw);
|
|
27716
|
+
const next = compose(before, after);
|
|
27717
|
+
await import_node_fs53.promises.mkdir(import_node_path88.default.dirname(file), { recursive: true });
|
|
27718
|
+
await import_node_fs53.promises.writeFile(file, next, "utf8");
|
|
27719
|
+
const verb = raw.length === 0 ? "created" : found ? "refreshed" : "added";
|
|
27720
|
+
console.log(`neat claude: ${verb} the \`${NEAT_SECTION_HEADING}\` section in ${file}`);
|
|
27721
|
+
console.log("Your agent will now reach for `neat ask` before Read/Grep/Bash. Restart the");
|
|
27722
|
+
console.log("session (or reload CLAUDE.md) to pick it up.");
|
|
27723
|
+
return { exitCode: 0 };
|
|
27724
|
+
}
|
|
27725
|
+
async function runUninstall() {
|
|
27726
|
+
const file = claudeMdPath();
|
|
27727
|
+
const raw = await readIfExists2(file);
|
|
27728
|
+
if (raw === null) {
|
|
27729
|
+
console.log(`neat claude: no CLAUDE.md at ${file} \u2014 nothing to remove.`);
|
|
27730
|
+
return { exitCode: 0 };
|
|
27731
|
+
}
|
|
27732
|
+
const { before, after, found } = splitAroundSection(raw);
|
|
27733
|
+
if (!found) {
|
|
27734
|
+
console.log(`neat claude: no \`${NEAT_SECTION_HEADING}\` section in ${file} \u2014 nothing to remove.`);
|
|
27735
|
+
return { exitCode: 0 };
|
|
27736
|
+
}
|
|
27737
|
+
const remaining = [before, after].filter((s) => s.length > 0).join("\n\n");
|
|
27738
|
+
const next = remaining.length > 0 ? remaining.replace(/\n*$/, "") + "\n" : "";
|
|
27739
|
+
await import_node_fs53.promises.writeFile(file, next, "utf8");
|
|
27740
|
+
console.log(`neat claude: removed the \`${NEAT_SECTION_HEADING}\` section from ${file}.`);
|
|
27741
|
+
return { exitCode: 0 };
|
|
27742
|
+
}
|
|
27743
|
+
function usage2() {
|
|
27744
|
+
console.log("neat claude \u2014 make the query-first directive always-on in Claude Code");
|
|
27745
|
+
console.log("");
|
|
27746
|
+
console.log(" install write (or refresh) a `## neat` section in ./CLAUDE.md so your");
|
|
27747
|
+
console.log(" agent reaches for `neat ask` before Read/Grep/Bash");
|
|
27748
|
+
console.log(" uninstall remove the `## neat` section from ./CLAUDE.md");
|
|
27749
|
+
console.log(" print print the directive block to stdout (for a manual paste)");
|
|
27750
|
+
console.log("");
|
|
27751
|
+
console.log("Idempotent: re-running install replaces its own section, never duplicates it.");
|
|
27752
|
+
console.log("Target file overridable via NEAT_CLAUDE_MD.");
|
|
27753
|
+
}
|
|
27754
|
+
async function runClaudeCommand(args) {
|
|
27755
|
+
const sub = args[0];
|
|
27756
|
+
if (sub === "-h" || sub === "--help" || sub === void 0) {
|
|
27757
|
+
usage2();
|
|
27758
|
+
return sub === void 0 ? 2 : 0;
|
|
27759
|
+
}
|
|
27760
|
+
try {
|
|
27761
|
+
switch (sub) {
|
|
27762
|
+
case "install":
|
|
27763
|
+
return (await runInstall()).exitCode;
|
|
27764
|
+
case "uninstall":
|
|
27765
|
+
return (await runUninstall()).exitCode;
|
|
27766
|
+
case "print":
|
|
27767
|
+
process.stdout.write(neatSection());
|
|
27768
|
+
return 0;
|
|
27769
|
+
default:
|
|
27770
|
+
console.error(`neat claude: unknown subcommand "${sub}"`);
|
|
27771
|
+
usage2();
|
|
27772
|
+
return 2;
|
|
27773
|
+
}
|
|
27774
|
+
} catch (err) {
|
|
27775
|
+
console.error(`neat claude: ${err.message}`);
|
|
27776
|
+
return 1;
|
|
27777
|
+
}
|
|
27778
|
+
}
|
|
27779
|
+
|
|
27780
|
+
// src/codex-cli.ts
|
|
27781
|
+
init_cjs_shims();
|
|
27782
|
+
var import_node_path89 = __toESM(require("path"), 1);
|
|
27783
|
+
var import_node_os6 = __toESM(require("os"), 1);
|
|
27784
|
+
var import_node_fs54 = require("fs");
|
|
26921
27785
|
var import_node_util = require("util");
|
|
26922
27786
|
var import_smol_toml6 = require("smol-toml");
|
|
26923
27787
|
var CODEX_MCP_SERVER = {
|
|
@@ -26935,14 +27799,14 @@ var NEAT_GRAPH_FIRST_START = "<!-- neat:graph-first -->";
|
|
|
26935
27799
|
var NEAT_GRAPH_FIRST_END = "<!-- /neat:graph-first -->";
|
|
26936
27800
|
function codexConfigPath() {
|
|
26937
27801
|
const override = process.env.NEAT_CODEX_CONFIG;
|
|
26938
|
-
if (override && override.length > 0) return
|
|
27802
|
+
if (override && override.length > 0) return import_node_path89.default.resolve(override);
|
|
26939
27803
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? import_node_os6.default.homedir();
|
|
26940
|
-
return
|
|
27804
|
+
return import_node_path89.default.join(home, ".codex", "config.toml");
|
|
26941
27805
|
}
|
|
26942
27806
|
function agentsFilePath() {
|
|
26943
27807
|
const override = process.env.NEAT_CODEX_AGENTS;
|
|
26944
|
-
if (override && override.length > 0) return
|
|
26945
|
-
return
|
|
27808
|
+
if (override && override.length > 0) return import_node_path89.default.resolve(override);
|
|
27809
|
+
return import_node_path89.default.join(process.cwd(), "AGENTS.md");
|
|
26946
27810
|
}
|
|
26947
27811
|
function isTableHeader(line) {
|
|
26948
27812
|
return /^\s*\[\[?[^\]]+\]\]?\s*$/.test(line);
|
|
@@ -27076,7 +27940,7 @@ async function runCodex(opts) {
|
|
|
27076
27940
|
const agentsPath = agentsFilePath();
|
|
27077
27941
|
let configRaw = "";
|
|
27078
27942
|
try {
|
|
27079
|
-
configRaw = await
|
|
27943
|
+
configRaw = await import_node_fs54.promises.readFile(configPath, "utf8");
|
|
27080
27944
|
} catch (err) {
|
|
27081
27945
|
if (err.code !== "ENOENT") {
|
|
27082
27946
|
console.error(`neat codex: failed to read ${configPath} \u2014 ${err.message}`);
|
|
@@ -27085,7 +27949,7 @@ async function runCodex(opts) {
|
|
|
27085
27949
|
}
|
|
27086
27950
|
let agentsRaw = "";
|
|
27087
27951
|
try {
|
|
27088
|
-
agentsRaw = await
|
|
27952
|
+
agentsRaw = await import_node_fs54.promises.readFile(agentsPath, "utf8");
|
|
27089
27953
|
} catch (err) {
|
|
27090
27954
|
if (err.code !== "ENOENT") {
|
|
27091
27955
|
console.error(`neat codex: failed to read ${agentsPath} \u2014 ${err.message}`);
|
|
@@ -27125,15 +27989,15 @@ async function runCodex(opts) {
|
|
|
27125
27989
|
return { exitCode: 0 };
|
|
27126
27990
|
}
|
|
27127
27991
|
if (config.changed) {
|
|
27128
|
-
await
|
|
27129
|
-
await
|
|
27992
|
+
await import_node_fs54.promises.mkdir(import_node_path89.default.dirname(configPath), { recursive: true });
|
|
27993
|
+
await import_node_fs54.promises.writeFile(configPath, config.text, "utf8");
|
|
27130
27994
|
console.log(`neat codex: wrote [mcp_servers.neat] to ${configPath}`);
|
|
27131
27995
|
} else {
|
|
27132
27996
|
console.log(`neat codex: ${configPath} already has NEAT's MCP server`);
|
|
27133
27997
|
}
|
|
27134
27998
|
if (agents.changed) {
|
|
27135
|
-
await
|
|
27136
|
-
await
|
|
27999
|
+
await import_node_fs54.promises.mkdir(import_node_path89.default.dirname(agentsPath), { recursive: true });
|
|
28000
|
+
await import_node_fs54.promises.writeFile(agentsPath, agents.text, "utf8");
|
|
27137
28001
|
console.log(`neat codex: wrote the graph-first block to ${agentsPath}`);
|
|
27138
28002
|
} else {
|
|
27139
28003
|
console.log(`neat codex: ${agentsPath} already has the graph-first block`);
|
|
@@ -27143,7 +28007,7 @@ async function runCodex(opts) {
|
|
|
27143
28007
|
console.log("points the server at the local daemon \u2014 edit it for a non-default one.");
|
|
27144
28008
|
return { exitCode: 0 };
|
|
27145
28009
|
}
|
|
27146
|
-
function
|
|
28010
|
+
function usage3() {
|
|
27147
28011
|
console.log("neat codex \u2014 install NEAT into the OpenAI Codex CLI (MCP server + AGENTS.md)");
|
|
27148
28012
|
console.log("");
|
|
27149
28013
|
console.log(" (no flag) plan: print what would change, write nothing");
|
|
@@ -27171,11 +28035,11 @@ async function runCodexCommand(args) {
|
|
|
27171
28035
|
break;
|
|
27172
28036
|
case "-h":
|
|
27173
28037
|
case "--help":
|
|
27174
|
-
|
|
28038
|
+
usage3();
|
|
27175
28039
|
return 0;
|
|
27176
28040
|
default:
|
|
27177
28041
|
console.error(`neat codex: unknown flag "${arg}"`);
|
|
27178
|
-
|
|
28042
|
+
usage3();
|
|
27179
28043
|
return 2;
|
|
27180
28044
|
}
|
|
27181
28045
|
}
|
|
@@ -27190,9 +28054,9 @@ async function runCodexCommand(args) {
|
|
|
27190
28054
|
|
|
27191
28055
|
// src/editors-cli.ts
|
|
27192
28056
|
init_cjs_shims();
|
|
27193
|
-
var
|
|
28057
|
+
var import_node_path90 = __toESM(require("path"), 1);
|
|
27194
28058
|
var import_node_os7 = __toESM(require("os"), 1);
|
|
27195
|
-
var
|
|
28059
|
+
var import_node_fs55 = require("fs");
|
|
27196
28060
|
var import_node_util2 = require("util");
|
|
27197
28061
|
var jsonc = __toESM(require("jsonc-parser"), 1);
|
|
27198
28062
|
var NEAT_MCP_SERVER = {
|
|
@@ -27216,17 +28080,17 @@ function homeDir() {
|
|
|
27216
28080
|
}
|
|
27217
28081
|
function xdgConfigDir() {
|
|
27218
28082
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
27219
|
-
return xdg && xdg.length > 0 ?
|
|
28083
|
+
return xdg && xdg.length > 0 ? import_node_path90.default.resolve(xdg) : import_node_path90.default.join(homeDir(), ".config");
|
|
27220
28084
|
}
|
|
27221
28085
|
function envOverride(name) {
|
|
27222
28086
|
const v = process.env[name];
|
|
27223
|
-
return v && v.length > 0 ?
|
|
28087
|
+
return v && v.length > 0 ? import_node_path90.default.resolve(v) : void 0;
|
|
27224
28088
|
}
|
|
27225
28089
|
var CURSOR_CLIENT = {
|
|
27226
28090
|
id: "cursor",
|
|
27227
28091
|
label: "Cursor",
|
|
27228
28092
|
docsUrl: "https://docs.cursor.com/context/mcp",
|
|
27229
|
-
mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ??
|
|
28093
|
+
mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ?? import_node_path90.default.join(homeDir(), ".cursor", "mcp.json"),
|
|
27230
28094
|
mcpContainerKey: "mcpServers",
|
|
27231
28095
|
format: "json",
|
|
27232
28096
|
// Cursor still reads a single `.cursorrules` at the project root (the modern
|
|
@@ -27238,7 +28102,7 @@ var DEVIN_CLIENT = {
|
|
|
27238
28102
|
id: "devin",
|
|
27239
28103
|
label: "Devin Desktop (Cascade)",
|
|
27240
28104
|
docsUrl: "https://docs.devin.ai/desktop/cascade/mcp",
|
|
27241
|
-
mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ??
|
|
28105
|
+
mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ?? import_node_path90.default.join(homeDir(), ".codeium", "windsurf", "mcp_config.json"),
|
|
27242
28106
|
mcpContainerKey: "mcpServers",
|
|
27243
28107
|
format: "json",
|
|
27244
28108
|
rulesFileName: ".windsurfrules"
|
|
@@ -27247,7 +28111,7 @@ var GEMINI_CLIENT = {
|
|
|
27247
28111
|
id: "gemini",
|
|
27248
28112
|
label: "Gemini CLI",
|
|
27249
28113
|
docsUrl: "https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md",
|
|
27250
|
-
mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ??
|
|
28114
|
+
mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ?? import_node_path90.default.join(homeDir(), ".gemini", "settings.json"),
|
|
27251
28115
|
mcpContainerKey: "mcpServers",
|
|
27252
28116
|
format: "json",
|
|
27253
28117
|
rulesFileName: "GEMINI.md"
|
|
@@ -27256,7 +28120,7 @@ var QWEN_CLIENT = {
|
|
|
27256
28120
|
id: "qwen",
|
|
27257
28121
|
label: "Qwen Code",
|
|
27258
28122
|
docsUrl: "https://qwenlm.github.io/qwen-code-docs/en/users/features/mcp/",
|
|
27259
|
-
mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ??
|
|
28123
|
+
mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ?? import_node_path90.default.join(homeDir(), ".qwen", "settings.json"),
|
|
27260
28124
|
mcpContainerKey: "mcpServers",
|
|
27261
28125
|
format: "json",
|
|
27262
28126
|
rulesFileName: "QWEN.md"
|
|
@@ -27265,7 +28129,7 @@ var AMAZONQ_CLIENT = {
|
|
|
27265
28129
|
id: "amazonq",
|
|
27266
28130
|
label: "Amazon Q Developer CLI",
|
|
27267
28131
|
docsUrl: "https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-mcp-configuration.html",
|
|
27268
|
-
mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ??
|
|
28132
|
+
mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ?? import_node_path90.default.join(homeDir(), ".aws", "amazonq", "mcp.json"),
|
|
27269
28133
|
mcpContainerKey: "mcpServers",
|
|
27270
28134
|
format: "json"
|
|
27271
28135
|
};
|
|
@@ -27273,7 +28137,7 @@ var ROOCODE_CLIENT = {
|
|
|
27273
28137
|
id: "roocode",
|
|
27274
28138
|
label: "Roo Code",
|
|
27275
28139
|
docsUrl: "https://roocodeinc.github.io/Roo-Code/features/mcp/using-mcp-in-roo",
|
|
27276
|
-
mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ??
|
|
28140
|
+
mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ?? import_node_path90.default.join(process.cwd(), ".roo", "mcp.json"),
|
|
27277
28141
|
mcpContainerKey: "mcpServers",
|
|
27278
28142
|
format: "json"
|
|
27279
28143
|
};
|
|
@@ -27286,9 +28150,9 @@ var ZED_CLIENT = {
|
|
|
27286
28150
|
if (override) return override;
|
|
27287
28151
|
if (process.platform === "win32") {
|
|
27288
28152
|
const appData = process.env.APPDATA;
|
|
27289
|
-
if (appData && appData.length > 0) return
|
|
28153
|
+
if (appData && appData.length > 0) return import_node_path90.default.join(appData, "Zed", "settings.json");
|
|
27290
28154
|
}
|
|
27291
|
-
return
|
|
28155
|
+
return import_node_path90.default.join(homeDir(), ".config", "zed", "settings.json");
|
|
27292
28156
|
},
|
|
27293
28157
|
mcpContainerKey: "context_servers",
|
|
27294
28158
|
format: "jsonc",
|
|
@@ -27298,7 +28162,7 @@ var OPENCODE_CLIENT = {
|
|
|
27298
28162
|
id: "opencode",
|
|
27299
28163
|
label: "OpenCode",
|
|
27300
28164
|
docsUrl: "https://opencode.ai/docs/mcp-servers/",
|
|
27301
|
-
mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ??
|
|
28165
|
+
mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ?? import_node_path90.default.join(xdgConfigDir(), "opencode", "opencode.json"),
|
|
27302
28166
|
mcpContainerKey: "mcp",
|
|
27303
28167
|
format: "json",
|
|
27304
28168
|
serverEntry: NEAT_OPENCODE_SERVER,
|
|
@@ -27308,7 +28172,7 @@ var CRUSH_CLIENT = {
|
|
|
27308
28172
|
id: "crush",
|
|
27309
28173
|
label: "Crush",
|
|
27310
28174
|
docsUrl: "https://charmbracelet-crush.mintlify.app/configuration/mcp",
|
|
27311
|
-
mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ??
|
|
28175
|
+
mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ?? import_node_path90.default.join(xdgConfigDir(), "crush", "crush.json"),
|
|
27312
28176
|
mcpContainerKey: "mcp",
|
|
27313
28177
|
format: "json",
|
|
27314
28178
|
serverEntry: NEAT_CRUSH_SERVER,
|
|
@@ -27371,7 +28235,7 @@ async function planMcp(client, mcpPath) {
|
|
|
27371
28235
|
const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
|
|
27372
28236
|
let raw = "";
|
|
27373
28237
|
try {
|
|
27374
|
-
raw = await
|
|
28238
|
+
raw = await import_node_fs55.promises.readFile(mcpPath, "utf8");
|
|
27375
28239
|
} catch (err) {
|
|
27376
28240
|
const e = err;
|
|
27377
28241
|
if (e.code === "ENOENT") {
|
|
@@ -27413,7 +28277,7 @@ async function runEditorInstall(client, opts) {
|
|
|
27413
28277
|
const mcpPath = client.mcpConfigPath();
|
|
27414
28278
|
const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
|
|
27415
28279
|
const hasRules = typeof client.rulesFileName === "string";
|
|
27416
|
-
const rulesPath = hasRules ?
|
|
28280
|
+
const rulesPath = hasRules ? import_node_path90.default.join(opts.projectDir, client.rulesFileName) : "";
|
|
27417
28281
|
const mcp = await planMcp(client, mcpPath);
|
|
27418
28282
|
if (mcp === null) return { exitCode: 1 };
|
|
27419
28283
|
let existingRules = "";
|
|
@@ -27422,7 +28286,7 @@ async function runEditorInstall(client, opts) {
|
|
|
27422
28286
|
let block = "";
|
|
27423
28287
|
if (hasRules) {
|
|
27424
28288
|
try {
|
|
27425
|
-
existingRules = await
|
|
28289
|
+
existingRules = await import_node_fs55.promises.readFile(rulesPath, "utf8");
|
|
27426
28290
|
} catch (err) {
|
|
27427
28291
|
if (err.code !== "ENOENT") {
|
|
27428
28292
|
console.error(`neat ${client.id}: failed to read ${rulesPath} \u2014 ${err.message}`);
|
|
@@ -27456,11 +28320,11 @@ async function runEditorInstall(client, opts) {
|
|
|
27456
28320
|
);
|
|
27457
28321
|
return { exitCode: 0 };
|
|
27458
28322
|
}
|
|
27459
|
-
await
|
|
27460
|
-
await
|
|
28323
|
+
await import_node_fs55.promises.mkdir(import_node_path90.default.dirname(mcpPath), { recursive: true });
|
|
28324
|
+
await import_node_fs55.promises.writeFile(mcpPath, mcp.text, "utf8");
|
|
27461
28325
|
if (hasRules) {
|
|
27462
|
-
await
|
|
27463
|
-
await
|
|
28326
|
+
await import_node_fs55.promises.mkdir(import_node_path90.default.dirname(rulesPath), { recursive: true });
|
|
28327
|
+
await import_node_fs55.promises.writeFile(rulesPath, newRules, "utf8");
|
|
27464
28328
|
}
|
|
27465
28329
|
console.log(`neat ${client.id}: wired NEAT into ${client.label}`);
|
|
27466
28330
|
console.log(` MCP server: ${mcpPath} (${client.mcpContainerKey}.neat \u2192 npx -y @neat.is/mcp)`);
|
|
@@ -27473,7 +28337,7 @@ async function runEditorInstall(client, opts) {
|
|
|
27473
28337
|
function indent(text) {
|
|
27474
28338
|
return text.split("\n").map((line) => line.length > 0 ? ` ${line}` : line).join("\n");
|
|
27475
28339
|
}
|
|
27476
|
-
function
|
|
28340
|
+
function usage4(client) {
|
|
27477
28341
|
const hasRules = typeof client.rulesFileName === "string";
|
|
27478
28342
|
console.log(
|
|
27479
28343
|
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 +28368,11 @@ async function runEditorCommand(clientId, args, projectDir = process.cwd()) {
|
|
|
27504
28368
|
break;
|
|
27505
28369
|
case "-h":
|
|
27506
28370
|
case "--help":
|
|
27507
|
-
|
|
28371
|
+
usage4(client);
|
|
27508
28372
|
return 0;
|
|
27509
28373
|
default:
|
|
27510
28374
|
console.error(`neat ${client.id}: unknown flag "${arg}"`);
|
|
27511
|
-
|
|
28375
|
+
usage4(client);
|
|
27512
28376
|
return 2;
|
|
27513
28377
|
}
|
|
27514
28378
|
}
|
|
@@ -27523,11 +28387,11 @@ async function runEditorCommand(clientId, args, projectDir = process.cwd()) {
|
|
|
27523
28387
|
|
|
27524
28388
|
// src/monitor.ts
|
|
27525
28389
|
init_cjs_shims();
|
|
27526
|
-
var
|
|
28390
|
+
var import_types97 = require("@neat.is/types");
|
|
27527
28391
|
|
|
27528
28392
|
// src/cli-client.ts
|
|
27529
28393
|
init_cjs_shims();
|
|
27530
|
-
var
|
|
28394
|
+
var import_types96 = require("@neat.is/types");
|
|
27531
28395
|
var HttpError = class extends Error {
|
|
27532
28396
|
constructor(status2, message, responseBody = "") {
|
|
27533
28397
|
super(message);
|
|
@@ -27552,10 +28416,10 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
27552
28416
|
const root = baseUrl.replace(/\/$/, "");
|
|
27553
28417
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
27554
28418
|
return {
|
|
27555
|
-
async get(
|
|
28419
|
+
async get(path93) {
|
|
27556
28420
|
let res;
|
|
27557
28421
|
try {
|
|
27558
|
-
res = await fetch(`${root}${
|
|
28422
|
+
res = await fetch(`${root}${path93}`, {
|
|
27559
28423
|
headers: { ...authHeader }
|
|
27560
28424
|
});
|
|
27561
28425
|
} catch (err) {
|
|
@@ -27567,16 +28431,16 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
27567
28431
|
const body = await res.text().catch(() => "");
|
|
27568
28432
|
throw new HttpError(
|
|
27569
28433
|
res.status,
|
|
27570
|
-
`${res.status} ${res.statusText} on GET ${
|
|
28434
|
+
`${res.status} ${res.statusText} on GET ${path93}: ${body}`,
|
|
27571
28435
|
body
|
|
27572
28436
|
);
|
|
27573
28437
|
}
|
|
27574
28438
|
return await res.json();
|
|
27575
28439
|
},
|
|
27576
|
-
async post(
|
|
28440
|
+
async post(path93, body) {
|
|
27577
28441
|
let res;
|
|
27578
28442
|
try {
|
|
27579
|
-
res = await fetch(`${root}${
|
|
28443
|
+
res = await fetch(`${root}${path93}`, {
|
|
27580
28444
|
method: "POST",
|
|
27581
28445
|
headers: { "content-type": "application/json", ...authHeader },
|
|
27582
28446
|
body: JSON.stringify(body)
|
|
@@ -27590,7 +28454,7 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
27590
28454
|
const text = await res.text().catch(() => "");
|
|
27591
28455
|
throw new HttpError(
|
|
27592
28456
|
res.status,
|
|
27593
|
-
`${res.status} ${res.statusText} on POST ${
|
|
28457
|
+
`${res.status} ${res.statusText} on POST ${path93}: ${text}`,
|
|
27594
28458
|
text
|
|
27595
28459
|
);
|
|
27596
28460
|
}
|
|
@@ -27604,12 +28468,12 @@ function projectPath(project, suffix) {
|
|
|
27604
28468
|
}
|
|
27605
28469
|
async function runRootCause(client, input) {
|
|
27606
28470
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
27607
|
-
const
|
|
28471
|
+
const path93 = projectPath(
|
|
27608
28472
|
input.project,
|
|
27609
28473
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
27610
28474
|
);
|
|
27611
28475
|
try {
|
|
27612
|
-
const result = await client.get(
|
|
28476
|
+
const result = await client.get(path93);
|
|
27613
28477
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
27614
28478
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
27615
28479
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -27635,12 +28499,12 @@ async function runRootCause(client, input) {
|
|
|
27635
28499
|
}
|
|
27636
28500
|
async function runBlastRadius(client, input) {
|
|
27637
28501
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
27638
|
-
const
|
|
28502
|
+
const path93 = projectPath(
|
|
27639
28503
|
input.project,
|
|
27640
28504
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
27641
28505
|
);
|
|
27642
28506
|
try {
|
|
27643
|
-
const result = await client.get(
|
|
28507
|
+
const result = await client.get(path93);
|
|
27644
28508
|
if (result.totalAffected === 0) {
|
|
27645
28509
|
return {
|
|
27646
28510
|
summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
|
|
@@ -27669,17 +28533,17 @@ async function runBlastRadius(client, input) {
|
|
|
27669
28533
|
}
|
|
27670
28534
|
}
|
|
27671
28535
|
function formatBlastEntry(n) {
|
|
27672
|
-
const tag = n.edgeProvenance ===
|
|
28536
|
+
const tag = n.edgeProvenance === import_types96.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
|
|
27673
28537
|
return ` \u2022 ${n.nodeId} (distance ${n.distance}, ${n.edgeProvenance})${tag}`;
|
|
27674
28538
|
}
|
|
27675
28539
|
async function runDependencies(client, input) {
|
|
27676
28540
|
const depth = input.depth ?? 3;
|
|
27677
|
-
const
|
|
28541
|
+
const path93 = projectPath(
|
|
27678
28542
|
input.project,
|
|
27679
28543
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
27680
28544
|
);
|
|
27681
28545
|
try {
|
|
27682
|
-
const result = await client.get(
|
|
28546
|
+
const result = await client.get(path93);
|
|
27683
28547
|
if (result.total === 0) {
|
|
27684
28548
|
return {
|
|
27685
28549
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -27726,7 +28590,7 @@ async function runObservedDependencies(client, input) {
|
|
|
27726
28590
|
if (result.observed) {
|
|
27727
28591
|
return {
|
|
27728
28592
|
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:
|
|
28593
|
+
provenance: import_types96.Provenance.OBSERVED
|
|
27730
28594
|
};
|
|
27731
28595
|
}
|
|
27732
28596
|
const note = result.hasExtractedOutbound ? " Static (EXTRACTED) dependencies exist but no runtime traffic has been seen \u2014 is OTel running?" : "";
|
|
@@ -27736,7 +28600,7 @@ async function runObservedDependencies(client, input) {
|
|
|
27736
28600
|
return {
|
|
27737
28601
|
summary: `${input.nodeId} has ${result.dependencies.length} runtime dependenc${result.dependencies.length === 1 ? "y" : "ies"} confirmed by OTel.`,
|
|
27738
28602
|
block: blockLines.join("\n"),
|
|
27739
|
-
provenance:
|
|
28603
|
+
provenance: import_types96.Provenance.OBSERVED
|
|
27740
28604
|
};
|
|
27741
28605
|
} catch (err) {
|
|
27742
28606
|
if (err instanceof HttpError && err.status === 404) {
|
|
@@ -27771,9 +28635,9 @@ function formatDuration(ms) {
|
|
|
27771
28635
|
return `${Math.round(h / 24)}d`;
|
|
27772
28636
|
}
|
|
27773
28637
|
async function runIncidents(client, input) {
|
|
27774
|
-
const
|
|
28638
|
+
const path93 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
27775
28639
|
try {
|
|
27776
|
-
const body = await client.get(
|
|
28640
|
+
const body = await client.get(path93);
|
|
27777
28641
|
const events = body.events;
|
|
27778
28642
|
if (events.length === 0) {
|
|
27779
28643
|
return {
|
|
@@ -27790,7 +28654,7 @@ async function runIncidents(client, input) {
|
|
|
27790
28654
|
return {
|
|
27791
28655
|
summary: `${target} has ${body.total} recorded incident${body.total === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
|
|
27792
28656
|
block: blockLines.join("\n"),
|
|
27793
|
-
provenance:
|
|
28657
|
+
provenance: import_types96.Provenance.OBSERVED
|
|
27794
28658
|
};
|
|
27795
28659
|
} catch (err) {
|
|
27796
28660
|
if (err instanceof HttpError && err.status === 404) {
|
|
@@ -27899,7 +28763,7 @@ async function runStaleEdges(client, input) {
|
|
|
27899
28763
|
return {
|
|
27900
28764
|
summary: `${events.length} stale-edge transition${events.length === 1 ? "" : "s"} recorded${input.edgeType ? ` for ${input.edgeType}` : ""}.`,
|
|
27901
28765
|
block: blockLines.join("\n"),
|
|
27902
|
-
provenance:
|
|
28766
|
+
provenance: import_types96.Provenance.STALE
|
|
27903
28767
|
};
|
|
27904
28768
|
}
|
|
27905
28769
|
async function runPolicies(client, input) {
|
|
@@ -28014,6 +28878,33 @@ async function runDivergences(client, input) {
|
|
|
28014
28878
|
provenance: "composite (EXTRACTED + OBSERVED)"
|
|
28015
28879
|
};
|
|
28016
28880
|
}
|
|
28881
|
+
async function runAsk(client, input) {
|
|
28882
|
+
const result = await client.get(
|
|
28883
|
+
projectPath(input.project, `/graph/ask?q=${encodeURIComponent(input.question)}`)
|
|
28884
|
+
);
|
|
28885
|
+
const blockLines = [];
|
|
28886
|
+
if (result.matched.length > 0) {
|
|
28887
|
+
blockLines.push(
|
|
28888
|
+
`Matched: ${result.matched.map((m) => `${m.nodeId} [${m.via} ${m.score.toFixed(2)}]`).join(", ")}`
|
|
28889
|
+
);
|
|
28890
|
+
blockLines.push(`Intent: ${result.intent}`);
|
|
28891
|
+
} else if (result.scope === "global") {
|
|
28892
|
+
blockLines.push(`Graph-wide answer (${result.intent}) \u2014 no entity named.`);
|
|
28893
|
+
}
|
|
28894
|
+
for (const section of result.sections) {
|
|
28895
|
+
blockLines.push("", section.heading + ":");
|
|
28896
|
+
for (const fact of section.facts) {
|
|
28897
|
+
const tag = fact.provenance ? ` [${fact.provenance}${fact.confidence !== void 0 ? ` ${fact.confidence.toFixed(2)}` : ""}]` : fact.confidence !== void 0 ? ` [confidence ${fact.confidence.toFixed(2)}]` : "";
|
|
28898
|
+
blockLines.push(` \u2022 ${fact.text}${tag}`);
|
|
28899
|
+
}
|
|
28900
|
+
}
|
|
28901
|
+
return {
|
|
28902
|
+
summary: result.answer,
|
|
28903
|
+
block: blockLines.join("\n").trim(),
|
|
28904
|
+
...result.confidence !== void 0 ? { confidence: result.confidence } : {},
|
|
28905
|
+
...result.provenance.length > 0 ? { provenance: result.provenance } : {}
|
|
28906
|
+
};
|
|
28907
|
+
}
|
|
28017
28908
|
function formatFooter(confidence, provenance) {
|
|
28018
28909
|
const c = confidence === void 0 ? "n/a" : confidence.toFixed(2);
|
|
28019
28910
|
const p = provenance === void 0 ? "n/a" : Array.isArray(provenance) ? [...new Set(provenance)].join(", ") : provenance;
|
|
@@ -28058,10 +28949,10 @@ async function pushSnapshotToRemote(input) {
|
|
|
28058
28949
|
|
|
28059
28950
|
// src/monitor.ts
|
|
28060
28951
|
var OBSERVED_DEP_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
28061
|
-
|
|
28062
|
-
|
|
28063
|
-
|
|
28064
|
-
|
|
28952
|
+
import_types97.EdgeType.CALLS,
|
|
28953
|
+
import_types97.EdgeType.CONNECTS_TO,
|
|
28954
|
+
import_types97.EdgeType.PUBLISHES_TO,
|
|
28955
|
+
import_types97.EdgeType.CONSUMES_FROM
|
|
28065
28956
|
]);
|
|
28066
28957
|
function divergenceKey(d) {
|
|
28067
28958
|
const column = "column" in d && d.column ? d.column : "";
|
|
@@ -28106,7 +28997,7 @@ function formatDivergenceLine2(d) {
|
|
|
28106
28997
|
}
|
|
28107
28998
|
}
|
|
28108
28999
|
function formatStaleLine(edgeId) {
|
|
28109
|
-
const parsed = (0,
|
|
29000
|
+
const parsed = (0, import_types97.parseEdgeId)(edgeId);
|
|
28110
29001
|
if (parsed) {
|
|
28111
29002
|
return `\u22EF stale ${parsed.source} \u2192 ${parsed.target} (observed edge went quiet)`;
|
|
28112
29003
|
}
|
|
@@ -28119,7 +29010,7 @@ function divergenceJson(d) {
|
|
|
28119
29010
|
return JSON.stringify({ kind: "divergence", ...d });
|
|
28120
29011
|
}
|
|
28121
29012
|
function staleJson(edgeId) {
|
|
28122
|
-
const parsed = (0,
|
|
29013
|
+
const parsed = (0, import_types97.parseEdgeId)(edgeId);
|
|
28123
29014
|
return JSON.stringify({
|
|
28124
29015
|
kind: "stale",
|
|
28125
29016
|
edgeId,
|
|
@@ -28189,7 +29080,7 @@ var MonitorEmitter = class {
|
|
|
28189
29080
|
// ignores non-OBSERVED edges and non-dependency edge types (structural
|
|
28190
29081
|
// ownership), so only real runtime dependencies reach stdout.
|
|
28191
29082
|
emitObservedEdge(edge) {
|
|
28192
|
-
if (edge.provenance !==
|
|
29083
|
+
if (edge.provenance !== import_types97.Provenance.OBSERVED) return false;
|
|
28193
29084
|
if (!OBSERVED_DEP_EDGE_TYPES.has(edge.type)) return false;
|
|
28194
29085
|
const key = `edge|${edge.id}`;
|
|
28195
29086
|
if (this.seen.has(key)) return false;
|
|
@@ -28347,7 +29238,7 @@ async function runMonitor(opts) {
|
|
|
28347
29238
|
case "edge-added": {
|
|
28348
29239
|
const payload = safeParse(frame.data);
|
|
28349
29240
|
const edge = payload?.edge;
|
|
28350
|
-
if (edge && edge.provenance ===
|
|
29241
|
+
if (edge && edge.provenance === import_types97.Provenance.OBSERVED) {
|
|
28351
29242
|
emitter.emitObservedEdge(edge);
|
|
28352
29243
|
divergences.schedule();
|
|
28353
29244
|
}
|
|
@@ -28427,7 +29318,7 @@ function sleep(ms, signal) {
|
|
|
28427
29318
|
|
|
28428
29319
|
// src/cli-verbs.ts
|
|
28429
29320
|
init_cjs_shims();
|
|
28430
|
-
var
|
|
29321
|
+
var import_node_path91 = __toESM(require("path"), 1);
|
|
28431
29322
|
async function resolveProjectEntry(opts) {
|
|
28432
29323
|
const entries = await listProjects();
|
|
28433
29324
|
if (opts.project) {
|
|
@@ -28437,7 +29328,7 @@ async function resolveProjectEntry(opts) {
|
|
|
28437
29328
|
const cwd = opts.cwd ?? process.cwd();
|
|
28438
29329
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
28439
29330
|
for (const entry2 of entries) {
|
|
28440
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
29331
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path91.default.sep}`)) {
|
|
28441
29332
|
return entry2;
|
|
28442
29333
|
}
|
|
28443
29334
|
}
|
|
@@ -28590,7 +29481,7 @@ async function runSync(opts) {
|
|
|
28590
29481
|
}
|
|
28591
29482
|
|
|
28592
29483
|
// src/cli.ts
|
|
28593
|
-
var
|
|
29484
|
+
var import_types98 = require("@neat.is/types");
|
|
28594
29485
|
function isNpxInvocation() {
|
|
28595
29486
|
if (process.env.npm_command === "exec") return true;
|
|
28596
29487
|
const execpath = process.env.npm_execpath ?? "";
|
|
@@ -28602,7 +29493,7 @@ function isNpxInvocation() {
|
|
|
28602
29493
|
function commandPrefix() {
|
|
28603
29494
|
return isNpxInvocation() ? "npx neat.is" : "neat";
|
|
28604
29495
|
}
|
|
28605
|
-
function
|
|
29496
|
+
function usage5() {
|
|
28606
29497
|
const neat = commandPrefix();
|
|
28607
29498
|
console.log("Installed via npx? Prefix commands with `npx neat.is`, or install once: `npm i -g neat.is`.");
|
|
28608
29499
|
console.log("");
|
|
@@ -28650,13 +29541,21 @@ function usage4() {
|
|
|
28650
29541
|
console.log(" --print-config print the JSON snippet to stdout");
|
|
28651
29542
|
console.log(" --apply merge mcpServers.neat into ~/.claude.json");
|
|
28652
29543
|
console.log(" hooks Wire NEAT into your agent so it queries the graph before");
|
|
28653
|
-
console.log(" grepping. Installs a
|
|
28654
|
-
console.log("
|
|
29544
|
+
console.log(" grepping. Installs a Claude Code search hook (nudge by default,");
|
|
29545
|
+
console.log(" or a hard gate) and agent-agnostic graph-first guidance.");
|
|
28655
29546
|
console.log(" Flags:");
|
|
28656
29547
|
console.log(" --apply install the hook + guidance");
|
|
29548
|
+
console.log(" --gate with --apply: DENY search until `neat ask`");
|
|
29549
|
+
console.log(" has run this session (default: nudge-only)");
|
|
28657
29550
|
console.log(" --print-hook print the hook script");
|
|
28658
29551
|
console.log(" --print-guide print the graph-first guidance");
|
|
28659
29552
|
console.log(" --print-settings print the settings.json block --apply adds");
|
|
29553
|
+
console.log(" claude Write the query-first directive into ./CLAUDE.md as a `## neat`");
|
|
29554
|
+
console.log(" section so an agent reaches for `neat ask` before Read/Grep/Bash.");
|
|
29555
|
+
console.log(" Subcommands:");
|
|
29556
|
+
console.log(" install write (or refresh) the `## neat` section");
|
|
29557
|
+
console.log(" uninstall remove the `## neat` section");
|
|
29558
|
+
console.log(" print print the directive block to stdout");
|
|
28660
29559
|
console.log(" codex Install NEAT into the OpenAI Codex CLI: add [mcp_servers.neat]");
|
|
28661
29560
|
console.log(" to ~/.codex/config.toml and the graph-first block to ./AGENTS.md.");
|
|
28662
29561
|
console.log(" Plan by default; --apply to write.");
|
|
@@ -28733,6 +29632,10 @@ function usage4() {
|
|
|
28733
29632
|
console.log(" run time; the config file is written owner-only (0600).");
|
|
28734
29633
|
console.log("");
|
|
28735
29634
|
console.log("query commands (mirror the MCP tools, ADR-050):");
|
|
29635
|
+
console.log(" ask <question> Plain-language door: resolves the question to");
|
|
29636
|
+
console.log(" nodes and routes it to the right traversal, with");
|
|
29637
|
+
console.log(" a compact provenance-tagged answer. Reach here first.");
|
|
29638
|
+
console.log(` example: ${neat} ask "why is checkout failing?"`);
|
|
28736
29639
|
console.log(" root-cause <node-id> Walk inbound edges to find what broke first.");
|
|
28737
29640
|
console.log(` example: ${neat} root-cause service:<name>`);
|
|
28738
29641
|
console.log(" blast-radius <node-id> BFS inbound \u2014 the dependents that break if this dies.");
|
|
@@ -28952,7 +29855,7 @@ async function buildPatchSections(services, project) {
|
|
|
28952
29855
|
}
|
|
28953
29856
|
async function runInit(opts) {
|
|
28954
29857
|
const written = [];
|
|
28955
|
-
const stat = await
|
|
29858
|
+
const stat = await import_node_fs56.promises.stat(opts.scanPath).catch(() => null);
|
|
28956
29859
|
if (!stat || !stat.isDirectory()) {
|
|
28957
29860
|
console.error(`neat init: ${opts.scanPath} is not a directory`);
|
|
28958
29861
|
return { exitCode: 2, writtenFiles: written };
|
|
@@ -28961,13 +29864,13 @@ async function runInit(opts) {
|
|
|
28961
29864
|
printDiscoveryReport(opts, services);
|
|
28962
29865
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
28963
29866
|
const patch = renderPatch(sections);
|
|
28964
|
-
const patchPath =
|
|
29867
|
+
const patchPath = import_node_path92.default.join(opts.scanPath, "neat.patch");
|
|
28965
29868
|
if (opts.dryRun) {
|
|
28966
|
-
await
|
|
29869
|
+
await import_node_fs56.promises.writeFile(patchPath, patch, "utf8");
|
|
28967
29870
|
written.push(patchPath);
|
|
28968
29871
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
28969
|
-
const gitignorePath =
|
|
28970
|
-
const gitignoreExists = await
|
|
29872
|
+
const gitignorePath = import_node_path92.default.join(opts.scanPath, ".gitignore");
|
|
29873
|
+
const gitignoreExists = await import_node_fs56.promises.stat(gitignorePath).then(() => true).catch(() => false);
|
|
28971
29874
|
const verb = gitignoreExists ? "append" : "create";
|
|
28972
29875
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
28973
29876
|
console.log("rerun without --dry-run to register and snapshot.");
|
|
@@ -28978,9 +29881,9 @@ async function runInit(opts) {
|
|
|
28978
29881
|
const graph = getGraph(graphKey);
|
|
28979
29882
|
const projectPaths = pathsForProject(
|
|
28980
29883
|
graphKey,
|
|
28981
|
-
|
|
29884
|
+
import_node_path92.default.join(opts.scanPath, "neat-out")
|
|
28982
29885
|
);
|
|
28983
|
-
const errorsPath =
|
|
29886
|
+
const errorsPath = import_node_path92.default.join(import_node_path92.default.dirname(opts.outPath), import_node_path92.default.basename(projectPaths.errorsPath));
|
|
28984
29887
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
28985
29888
|
await saveGraphToDisk(graph, opts.outPath);
|
|
28986
29889
|
written.push(opts.outPath);
|
|
@@ -29059,7 +29962,7 @@ async function runInit(opts) {
|
|
|
29059
29962
|
console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
29060
29963
|
}
|
|
29061
29964
|
} else {
|
|
29062
|
-
await
|
|
29965
|
+
await import_node_fs56.promises.writeFile(patchPath, patch, "utf8");
|
|
29063
29966
|
written.push(patchPath);
|
|
29064
29967
|
}
|
|
29065
29968
|
}
|
|
@@ -29099,9 +30002,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
29099
30002
|
};
|
|
29100
30003
|
function claudeConfigPath() {
|
|
29101
30004
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
29102
|
-
if (override && override.length > 0) return
|
|
30005
|
+
if (override && override.length > 0) return import_node_path92.default.resolve(override);
|
|
29103
30006
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
29104
|
-
return
|
|
30007
|
+
return import_node_path92.default.join(home, ".claude.json");
|
|
29105
30008
|
}
|
|
29106
30009
|
async function runSkill(opts) {
|
|
29107
30010
|
const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -29113,7 +30016,7 @@ async function runSkill(opts) {
|
|
|
29113
30016
|
const target = claudeConfigPath();
|
|
29114
30017
|
let existing = {};
|
|
29115
30018
|
try {
|
|
29116
|
-
existing = JSON.parse(await
|
|
30019
|
+
existing = JSON.parse(await import_node_fs56.promises.readFile(target, "utf8"));
|
|
29117
30020
|
} catch (err) {
|
|
29118
30021
|
if (err.code !== "ENOENT") {
|
|
29119
30022
|
console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
|
|
@@ -29125,8 +30028,8 @@ async function runSkill(opts) {
|
|
|
29125
30028
|
...existing,
|
|
29126
30029
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
29127
30030
|
};
|
|
29128
|
-
await
|
|
29129
|
-
await
|
|
30031
|
+
await import_node_fs56.promises.mkdir(import_node_path92.default.dirname(target), { recursive: true });
|
|
30032
|
+
await import_node_fs56.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
29130
30033
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
29131
30034
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
29132
30035
|
console.log("");
|
|
@@ -29153,7 +30056,7 @@ async function main() {
|
|
|
29153
30056
|
const argv = process.argv.slice(2);
|
|
29154
30057
|
const cmd0 = argv[0];
|
|
29155
30058
|
if (cmd0 === "-h" || cmd0 === "--help") {
|
|
29156
|
-
|
|
30059
|
+
usage5();
|
|
29157
30060
|
process.exit(0);
|
|
29158
30061
|
}
|
|
29159
30062
|
if (cmd0 === "--version" || cmd0 === "-v" || cmd0 === "version") {
|
|
@@ -29175,6 +30078,11 @@ async function main() {
|
|
|
29175
30078
|
if (code !== 0) process.exit(code);
|
|
29176
30079
|
return;
|
|
29177
30080
|
}
|
|
30081
|
+
if (cmd0 === "claude") {
|
|
30082
|
+
const code = await runClaudeCommand(argv.slice(1));
|
|
30083
|
+
if (code !== 0) process.exit(code);
|
|
30084
|
+
return;
|
|
30085
|
+
}
|
|
29178
30086
|
const EDITOR_VERBS = [
|
|
29179
30087
|
"cursor",
|
|
29180
30088
|
"devin",
|
|
@@ -29205,19 +30113,19 @@ async function main() {
|
|
|
29205
30113
|
const target = positional[0];
|
|
29206
30114
|
if (!target) {
|
|
29207
30115
|
console.error("neat init: missing <path>");
|
|
29208
|
-
|
|
30116
|
+
usage5();
|
|
29209
30117
|
process.exit(2);
|
|
29210
30118
|
}
|
|
29211
30119
|
if (apply6 && dryRun) {
|
|
29212
30120
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
29213
30121
|
process.exit(2);
|
|
29214
30122
|
}
|
|
29215
|
-
const scanPath =
|
|
30123
|
+
const scanPath = import_node_path92.default.resolve(target);
|
|
29216
30124
|
const projectExplicit = parsed.project !== null;
|
|
29217
|
-
const projectName = projectExplicit ? project :
|
|
30125
|
+
const projectName = projectExplicit ? project : import_node_path92.default.basename(scanPath);
|
|
29218
30126
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
29219
|
-
const fallback = pathsForProject(projectKey,
|
|
29220
|
-
const outPath =
|
|
30127
|
+
const fallback = pathsForProject(projectKey, import_node_path92.default.join(scanPath, "neat-out")).snapshotPath;
|
|
30128
|
+
const outPath = import_node_path92.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
|
|
29221
30129
|
const result = await runInit({
|
|
29222
30130
|
scanPath,
|
|
29223
30131
|
outPath,
|
|
@@ -29235,24 +30143,24 @@ async function main() {
|
|
|
29235
30143
|
const target = positional[0];
|
|
29236
30144
|
if (!target) {
|
|
29237
30145
|
console.error("neat watch: missing <path>");
|
|
29238
|
-
|
|
30146
|
+
usage5();
|
|
29239
30147
|
process.exit(2);
|
|
29240
30148
|
}
|
|
29241
|
-
const scanPath =
|
|
29242
|
-
const stat = await
|
|
30149
|
+
const scanPath = import_node_path92.default.resolve(target);
|
|
30150
|
+
const stat = await import_node_fs56.promises.stat(scanPath).catch(() => null);
|
|
29243
30151
|
if (!stat || !stat.isDirectory()) {
|
|
29244
30152
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
29245
30153
|
process.exit(2);
|
|
29246
30154
|
}
|
|
29247
|
-
const projectPaths = pathsForProject(project,
|
|
29248
|
-
const outPath =
|
|
29249
|
-
const errorsPath =
|
|
29250
|
-
process.env.NEAT_ERRORS_PATH ??
|
|
30155
|
+
const projectPaths = pathsForProject(project, import_node_path92.default.join(scanPath, "neat-out"));
|
|
30156
|
+
const outPath = import_node_path92.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
|
|
30157
|
+
const errorsPath = import_node_path92.default.resolve(
|
|
30158
|
+
process.env.NEAT_ERRORS_PATH ?? import_node_path92.default.join(import_node_path92.default.dirname(outPath), import_node_path92.default.basename(projectPaths.errorsPath))
|
|
29251
30159
|
);
|
|
29252
|
-
const staleEventsPath =
|
|
29253
|
-
process.env.NEAT_STALE_EVENTS_PATH ??
|
|
30160
|
+
const staleEventsPath = import_node_path92.default.resolve(
|
|
30161
|
+
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
30162
|
);
|
|
29255
|
-
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ?
|
|
30163
|
+
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path92.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
|
|
29256
30164
|
const handle = await startWatch(getGraph(project), {
|
|
29257
30165
|
scanPath,
|
|
29258
30166
|
outPath,
|
|
@@ -29261,7 +30169,7 @@ async function main() {
|
|
|
29261
30169
|
project,
|
|
29262
30170
|
// Resolve NEAT_HOME so a `neat watch` picks up connectors added to
|
|
29263
30171
|
// ~/.neat/connectors.json (#871). Same resolution the rest of the CLI uses.
|
|
29264
|
-
neatHome: process.env.NEAT_HOME ?
|
|
30172
|
+
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
30173
|
...embeddingsCachePath ? { embeddingsCachePath } : {},
|
|
29266
30174
|
host: process.env.HOST ?? "0.0.0.0",
|
|
29267
30175
|
port: Number(process.env.PORT ?? 8080),
|
|
@@ -29297,7 +30205,7 @@ async function main() {
|
|
|
29297
30205
|
const name = positional[0];
|
|
29298
30206
|
if (!name) {
|
|
29299
30207
|
console.error("neat pause: missing <name>");
|
|
29300
|
-
|
|
30208
|
+
usage5();
|
|
29301
30209
|
process.exit(2);
|
|
29302
30210
|
}
|
|
29303
30211
|
const daemon = await findDaemonByProject(name);
|
|
@@ -29322,7 +30230,7 @@ async function main() {
|
|
|
29322
30230
|
const name = positional[0];
|
|
29323
30231
|
if (!name) {
|
|
29324
30232
|
console.error("neat resume: missing <name>");
|
|
29325
|
-
|
|
30233
|
+
usage5();
|
|
29326
30234
|
process.exit(2);
|
|
29327
30235
|
}
|
|
29328
30236
|
const daemon = await findDaemonByProject(name);
|
|
@@ -29352,7 +30260,7 @@ async function main() {
|
|
|
29352
30260
|
const name = positional[0];
|
|
29353
30261
|
if (!name) {
|
|
29354
30262
|
console.error("neat uninstall: missing <name>");
|
|
29355
|
-
|
|
30263
|
+
usage5();
|
|
29356
30264
|
process.exit(2);
|
|
29357
30265
|
}
|
|
29358
30266
|
const daemon = await findDaemonByProject(name);
|
|
@@ -29439,15 +30347,15 @@ async function main() {
|
|
|
29439
30347
|
return;
|
|
29440
30348
|
}
|
|
29441
30349
|
console.error(`neat: unknown command "${cmd}"`);
|
|
29442
|
-
|
|
30350
|
+
usage5();
|
|
29443
30351
|
process.exit(1);
|
|
29444
30352
|
}
|
|
29445
30353
|
async function tryOrchestrator(cmd, parsed) {
|
|
29446
|
-
const scanPath =
|
|
29447
|
-
const stat = await
|
|
30354
|
+
const scanPath = import_node_path92.default.resolve(cmd);
|
|
30355
|
+
const stat = await import_node_fs56.promises.stat(scanPath).catch(() => null);
|
|
29448
30356
|
if (!stat || !stat.isDirectory()) return null;
|
|
29449
30357
|
const projectExplicit = parsed.project !== null;
|
|
29450
|
-
const projectName = projectExplicit ? parsed.project :
|
|
30358
|
+
const projectName = projectExplicit ? parsed.project : import_node_path92.default.basename(scanPath);
|
|
29451
30359
|
const result = await runOrchestrator({
|
|
29452
30360
|
scanPath,
|
|
29453
30361
|
project: projectName,
|
|
@@ -29469,7 +30377,10 @@ var QUERY_VERBS = /* @__PURE__ */ new Set([
|
|
|
29469
30377
|
"stale-edges",
|
|
29470
30378
|
"policies",
|
|
29471
30379
|
// Tenth verb (ADR-060) — amends ADR-050's locked allowlist of nine.
|
|
29472
|
-
"divergences"
|
|
30380
|
+
"divergences",
|
|
30381
|
+
// Twelfth verb (ADR-198) — the plain-language door over the whole tool
|
|
30382
|
+
// surface. Amends ADR-050's locked allowlist the same way `divergences` did.
|
|
30383
|
+
"ask"
|
|
29473
30384
|
]);
|
|
29474
30385
|
function resolveProjectFlag(parsed) {
|
|
29475
30386
|
if (parsed.project) return parsed.project;
|
|
@@ -29591,6 +30502,15 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
29591
30502
|
makeWork = (project) => runSearch(client, { query: q, ...project ? { project } : {} });
|
|
29592
30503
|
break;
|
|
29593
30504
|
}
|
|
30505
|
+
case "ask": {
|
|
30506
|
+
const question = positional.join(" ").trim();
|
|
30507
|
+
if (!question) {
|
|
30508
|
+
console.error("neat ask: missing <question>");
|
|
30509
|
+
return 2;
|
|
30510
|
+
}
|
|
30511
|
+
makeWork = (project) => runAsk(client, { question, ...project ? { project } : {} });
|
|
30512
|
+
break;
|
|
30513
|
+
}
|
|
29594
30514
|
case "diff": {
|
|
29595
30515
|
const against = parsed.against ?? parsed.since;
|
|
29596
30516
|
if (!against) {
|
|
@@ -29636,10 +30556,10 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
29636
30556
|
const parts = parsed.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
29637
30557
|
const out = [];
|
|
29638
30558
|
for (const p of parts) {
|
|
29639
|
-
const r =
|
|
30559
|
+
const r = import_types98.DivergenceTypeSchema.safeParse(p);
|
|
29640
30560
|
if (!r.success) {
|
|
29641
30561
|
console.error(
|
|
29642
|
-
`neat divergences: unknown --type "${p}". allowed: ${
|
|
30562
|
+
`neat divergences: unknown --type "${p}". allowed: ${import_types98.DivergenceTypeSchema.options.join(", ")}`
|
|
29643
30563
|
);
|
|
29644
30564
|
return 2;
|
|
29645
30565
|
}
|