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