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