@neat.is/core 0.3.3 → 0.3.4
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-33ZZ2ZID.js} +181 -66
- package/dist/chunk-33ZZ2ZID.js.map +1 -0
- package/dist/{chunk-ICFH326H.js → chunk-T3A2GK3X.js} +2 -2
- package/dist/cli.cjs +215 -124
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +3 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +160 -74
- 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 +167 -81
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +156 -70
- 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-T3A2GK3X.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
|
|
@@ -887,19 +887,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
887
887
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
888
888
|
let best = { path: [start], edges: [] };
|
|
889
889
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
890
|
-
function step(node,
|
|
891
|
-
if (
|
|
892
|
-
best = { path: [...
|
|
890
|
+
function step(node, path41, edges) {
|
|
891
|
+
if (path41.length > best.path.length) {
|
|
892
|
+
best = { path: [...path41], edges: [...edges] };
|
|
893
893
|
}
|
|
894
|
-
if (
|
|
894
|
+
if (path41.length - 1 >= maxDepth) return;
|
|
895
895
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
896
896
|
for (const [srcId, edge] of incoming) {
|
|
897
897
|
if (visited.has(srcId)) continue;
|
|
898
898
|
visited.add(srcId);
|
|
899
|
-
|
|
899
|
+
path41.push(srcId);
|
|
900
900
|
edges.push(edge);
|
|
901
|
-
step(srcId,
|
|
902
|
-
|
|
901
|
+
step(srcId, path41, edges);
|
|
902
|
+
path41.pop();
|
|
903
903
|
edges.pop();
|
|
904
904
|
visited.delete(srcId);
|
|
905
905
|
}
|
|
@@ -1588,35 +1588,37 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
|
1588
1588
|
const existing = graph.getEdgeAttributes(id);
|
|
1589
1589
|
const newSpanCount = (existing.signal?.spanCount ?? existing.callCount ?? 0) + 1;
|
|
1590
1590
|
const newErrorCount = (existing.signal?.errorCount ?? 0) + (isError ? 1 : 0);
|
|
1591
|
+
const newSignal = {
|
|
1592
|
+
spanCount: newSpanCount,
|
|
1593
|
+
errorCount: newErrorCount,
|
|
1594
|
+
lastObservedAgeMs: 0
|
|
1595
|
+
};
|
|
1591
1596
|
const updated = {
|
|
1592
1597
|
...existing,
|
|
1593
1598
|
provenance: import_types3.Provenance.OBSERVED,
|
|
1594
1599
|
lastObserved: ts,
|
|
1595
1600
|
callCount: newSpanCount,
|
|
1596
|
-
signal:
|
|
1597
|
-
|
|
1598
|
-
errorCount: newErrorCount,
|
|
1599
|
-
lastObservedAgeMs: 0
|
|
1600
|
-
},
|
|
1601
|
-
confidence: 1
|
|
1601
|
+
signal: newSignal,
|
|
1602
|
+
confidence: (0, import_types3.confidenceForObservedSignal)(newSignal)
|
|
1602
1603
|
};
|
|
1603
1604
|
graph.replaceEdgeAttributes(id, updated);
|
|
1604
1605
|
return { edge: updated, created: false };
|
|
1605
1606
|
}
|
|
1607
|
+
const signal = {
|
|
1608
|
+
spanCount: 1,
|
|
1609
|
+
errorCount: isError ? 1 : 0,
|
|
1610
|
+
lastObservedAgeMs: 0
|
|
1611
|
+
};
|
|
1606
1612
|
const edge = {
|
|
1607
1613
|
id,
|
|
1608
1614
|
source,
|
|
1609
1615
|
target,
|
|
1610
1616
|
type,
|
|
1611
1617
|
provenance: import_types3.Provenance.OBSERVED,
|
|
1612
|
-
confidence:
|
|
1618
|
+
confidence: (0, import_types3.confidenceForObservedSignal)(signal),
|
|
1613
1619
|
lastObserved: ts,
|
|
1614
1620
|
callCount: 1,
|
|
1615
|
-
signal
|
|
1616
|
-
spanCount: 1,
|
|
1617
|
-
errorCount: isError ? 1 : 0,
|
|
1618
|
-
lastObservedAgeMs: 0
|
|
1619
|
-
}
|
|
1621
|
+
signal
|
|
1620
1622
|
};
|
|
1621
1623
|
graph.addEdgeWithKey(id, source, target, edge);
|
|
1622
1624
|
return { edge, created: true };
|
|
@@ -2251,6 +2253,27 @@ function formatExtractionBanner(count) {
|
|
|
2251
2253
|
if (count === 1) return `[neat] 1 file skipped due to parse errors`;
|
|
2252
2254
|
return `[neat] ${count} files skipped due to parse errors`;
|
|
2253
2255
|
}
|
|
2256
|
+
var droppedSink = [];
|
|
2257
|
+
function noteExtractedDropped(edge) {
|
|
2258
|
+
droppedSink.push(edge);
|
|
2259
|
+
}
|
|
2260
|
+
function drainDroppedExtracted() {
|
|
2261
|
+
return droppedSink.splice(0, droppedSink.length);
|
|
2262
|
+
}
|
|
2263
|
+
function isRejectedLogEnabled() {
|
|
2264
|
+
const raw = process.env.NEAT_EXTRACTED_REJECTED_LOG;
|
|
2265
|
+
return raw === "1" || raw === "true";
|
|
2266
|
+
}
|
|
2267
|
+
async function writeRejectedExtracted(drops, rejectedPath) {
|
|
2268
|
+
if (drops.length === 0) return;
|
|
2269
|
+
await import_node_fs7.promises.mkdir(import_node_path7.default.dirname(rejectedPath), { recursive: true });
|
|
2270
|
+
const lines = drops.map((d) => JSON.stringify({ ...d, ts: (/* @__PURE__ */ new Date()).toISOString() })).join("\n") + "\n";
|
|
2271
|
+
await import_node_fs7.promises.appendFile(rejectedPath, lines, "utf8");
|
|
2272
|
+
}
|
|
2273
|
+
function formatPrecisionFloorBanner(count) {
|
|
2274
|
+
if (count === 1) return `[neat] 1 extracted edge dropped below precision floor`;
|
|
2275
|
+
return `[neat] ${count} extracted edges dropped below precision floor`;
|
|
2276
|
+
}
|
|
2254
2277
|
|
|
2255
2278
|
// src/extract/services.ts
|
|
2256
2279
|
var DEFAULT_SCAN_DEPTH = 5;
|
|
@@ -3189,6 +3212,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3189
3212
|
target: dbNode.id,
|
|
3190
3213
|
type: import_types7.EdgeType.CONNECTS_TO,
|
|
3191
3214
|
provenance: import_types7.Provenance.EXTRACTED,
|
|
3215
|
+
confidence: (0, import_types7.confidenceForExtracted)("structural"),
|
|
3192
3216
|
// ADR-032 / #140 — every EXTRACTED edge carries evidence.file.
|
|
3193
3217
|
// Ghost-edge cleanup keys retirement on this; the conditional
|
|
3194
3218
|
// sourceFile spread that used to live here was a v0.1.x leftover.
|
|
@@ -3263,6 +3287,7 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3263
3287
|
target: node.id,
|
|
3264
3288
|
type: import_types8.EdgeType.CONFIGURED_BY,
|
|
3265
3289
|
provenance: import_types8.Provenance.EXTRACTED,
|
|
3290
|
+
confidence: (0, import_types8.confidenceForExtracted)("structural"),
|
|
3266
3291
|
evidence: { file: relPath.split(import_node_path18.default.sep).join("/") }
|
|
3267
3292
|
};
|
|
3268
3293
|
if (!graph.hasEdge(edge.id)) {
|
|
@@ -3417,17 +3442,32 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3417
3442
|
for (const [targetId, evidenceFile] of seenTargets) {
|
|
3418
3443
|
const fileContent = files.find((f) => f.path === evidenceFile.file)?.content ?? "";
|
|
3419
3444
|
const line = lineOf(fileContent, `//${evidenceFile.host}`);
|
|
3445
|
+
const confidence = (0, import_types9.confidenceForExtracted)("hostname-shape-match");
|
|
3446
|
+
const ev = {
|
|
3447
|
+
file: import_node_path20.default.relative(service.dir, evidenceFile.file),
|
|
3448
|
+
line,
|
|
3449
|
+
snippet: snippet(fileContent, line)
|
|
3450
|
+
};
|
|
3451
|
+
const edgeId = (0, import_types4.extractedEdgeId)(service.node.id, targetId, import_types9.EdgeType.CALLS);
|
|
3452
|
+
if (!(0, import_types9.passesExtractedFloor)(confidence)) {
|
|
3453
|
+
noteExtractedDropped({
|
|
3454
|
+
source: service.node.id,
|
|
3455
|
+
target: targetId,
|
|
3456
|
+
type: import_types9.EdgeType.CALLS,
|
|
3457
|
+
confidence,
|
|
3458
|
+
confidenceKind: "hostname-shape-match",
|
|
3459
|
+
evidence: ev
|
|
3460
|
+
});
|
|
3461
|
+
continue;
|
|
3462
|
+
}
|
|
3420
3463
|
const edge = {
|
|
3421
|
-
id:
|
|
3464
|
+
id: edgeId,
|
|
3422
3465
|
source: service.node.id,
|
|
3423
3466
|
target: targetId,
|
|
3424
3467
|
type: import_types9.EdgeType.CALLS,
|
|
3425
3468
|
provenance: import_types9.Provenance.EXTRACTED,
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
line,
|
|
3429
|
-
snippet: snippet(fileContent, line)
|
|
3430
|
-
}
|
|
3469
|
+
confidence,
|
|
3470
|
+
evidence: ev
|
|
3431
3471
|
};
|
|
3432
3472
|
if (!graph.hasEdge(edge.id)) {
|
|
3433
3473
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3466,6 +3506,10 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3466
3506
|
name: topic,
|
|
3467
3507
|
kind: "kafka-topic",
|
|
3468
3508
|
edgeType,
|
|
3509
|
+
// `producer.send({topic: 'x'})` / `consumer.subscribe({topic: 'x'})` —
|
|
3510
|
+
// framework-aware (kafkajs / node-rdkafka shape). Verified-call-site
|
|
3511
|
+
// tier (ADR-066).
|
|
3512
|
+
confidenceKind: "verified-call-site",
|
|
3469
3513
|
evidence: {
|
|
3470
3514
|
file: import_node_path21.default.relative(serviceDir, file.path),
|
|
3471
3515
|
line,
|
|
@@ -3498,6 +3542,10 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3498
3542
|
name: host,
|
|
3499
3543
|
kind: "redis",
|
|
3500
3544
|
edgeType: "CALLS",
|
|
3545
|
+
// `redis://host` URL literal — the scheme is structural support, but no
|
|
3546
|
+
// call expression is verified to wire it through. URL-with-structural-
|
|
3547
|
+
// support tier (ADR-066).
|
|
3548
|
+
confidenceKind: "url-with-structural-support",
|
|
3501
3549
|
evidence: {
|
|
3502
3550
|
file: import_node_path22.default.relative(serviceDir, file.path),
|
|
3503
3551
|
line,
|
|
@@ -3539,6 +3587,10 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
3539
3587
|
name,
|
|
3540
3588
|
kind,
|
|
3541
3589
|
edgeType: "CALLS",
|
|
3590
|
+
// SDK marker (S3Client, GetCommand, etc.) plus a Bucket/TableName
|
|
3591
|
+
// literal — framework-aware recognizer, verified-call-site tier
|
|
3592
|
+
// (ADR-066).
|
|
3593
|
+
confidenceKind: "verified-call-site",
|
|
3542
3594
|
evidence: {
|
|
3543
3595
|
file: import_node_path23.default.relative(serviceDir, file.path),
|
|
3544
3596
|
line,
|
|
@@ -3618,6 +3670,10 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
3618
3670
|
name,
|
|
3619
3671
|
kind,
|
|
3620
3672
|
edgeType: "CALLS",
|
|
3673
|
+
// `new <Name>Client(...)` with @aws-sdk/* or @grpc/grpc-js import
|
|
3674
|
+
// context — import-aware classification per #238. Verified-call-site
|
|
3675
|
+
// tier (ADR-066).
|
|
3676
|
+
confidenceKind: "verified-call-site",
|
|
3621
3677
|
evidence: {
|
|
3622
3678
|
file: import_node_path24.default.relative(serviceDir, file.path),
|
|
3623
3679
|
line,
|
|
@@ -3678,6 +3734,18 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3678
3734
|
const edgeId = (0, import_types4.extractedEdgeId)(service.node.id, ep.infraId, edgeType);
|
|
3679
3735
|
if (seenEdges.has(edgeId)) continue;
|
|
3680
3736
|
seenEdges.add(edgeId);
|
|
3737
|
+
const confidence = (0, import_types14.confidenceForExtracted)(ep.confidenceKind);
|
|
3738
|
+
if (!(0, import_types14.passesExtractedFloor)(confidence)) {
|
|
3739
|
+
noteExtractedDropped({
|
|
3740
|
+
source: service.node.id,
|
|
3741
|
+
target: ep.infraId,
|
|
3742
|
+
type: edgeType,
|
|
3743
|
+
confidence,
|
|
3744
|
+
confidenceKind: ep.confidenceKind,
|
|
3745
|
+
evidence: ep.evidence
|
|
3746
|
+
});
|
|
3747
|
+
continue;
|
|
3748
|
+
}
|
|
3681
3749
|
if (!graph.hasEdge(edgeId)) {
|
|
3682
3750
|
const edge = {
|
|
3683
3751
|
id: edgeId,
|
|
@@ -3685,6 +3753,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3685
3753
|
target: ep.infraId,
|
|
3686
3754
|
type: edgeType,
|
|
3687
3755
|
provenance: import_types14.Provenance.EXTRACTED,
|
|
3756
|
+
confidence,
|
|
3688
3757
|
evidence: ep.evidence
|
|
3689
3758
|
};
|
|
3690
3759
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -3804,6 +3873,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3804
3873
|
target: targetId,
|
|
3805
3874
|
type: import_types16.EdgeType.DEPENDS_ON,
|
|
3806
3875
|
provenance: import_types16.Provenance.EXTRACTED,
|
|
3876
|
+
confidence: (0, import_types16.confidenceForExtracted)("structural"),
|
|
3807
3877
|
evidence: { file: evidenceFile }
|
|
3808
3878
|
};
|
|
3809
3879
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -3864,6 +3934,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
3864
3934
|
target: node.id,
|
|
3865
3935
|
type: import_types17.EdgeType.RUNS_ON,
|
|
3866
3936
|
provenance: import_types17.Provenance.EXTRACTED,
|
|
3937
|
+
confidence: (0, import_types17.confidenceForExtracted)("structural"),
|
|
3867
3938
|
evidence: {
|
|
3868
3939
|
file: import_node_path26.default.relative(scanPath, dockerfilePath).split(import_node_path26.default.sep).join("/")
|
|
3869
3940
|
}
|
|
@@ -3980,6 +4051,9 @@ async function addInfra(graph, scanPath, services) {
|
|
|
3980
4051
|
};
|
|
3981
4052
|
}
|
|
3982
4053
|
|
|
4054
|
+
// src/extract/index.ts
|
|
4055
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
4056
|
+
|
|
3983
4057
|
// src/extract/retire.ts
|
|
3984
4058
|
init_cjs_shims();
|
|
3985
4059
|
var import_node_fs17 = require("fs");
|
|
@@ -4044,13 +4118,26 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4044
4118
|
);
|
|
4045
4119
|
}
|
|
4046
4120
|
}
|
|
4121
|
+
const droppedEntries = drainDroppedExtracted();
|
|
4122
|
+
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
4123
|
+
const rejectedPath = import_node_path30.default.join(import_node_path30.default.dirname(opts.errorsPath), "rejected.ndjson");
|
|
4124
|
+
try {
|
|
4125
|
+
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
4126
|
+
} catch (err) {
|
|
4127
|
+
console.warn(
|
|
4128
|
+
`[neat] failed to write rejected extracted edges to ${rejectedPath}: ${err.message}`
|
|
4129
|
+
);
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4047
4132
|
const result = {
|
|
4048
4133
|
nodesAdded: phase1Nodes + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
4049
4134
|
edgesAdded: phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
4050
4135
|
frontiersPromoted,
|
|
4051
4136
|
extractionErrors: errorEntries.length,
|
|
4052
4137
|
errorEntries,
|
|
4053
|
-
ghostsRetired
|
|
4138
|
+
ghostsRetired,
|
|
4139
|
+
extractedDropped: droppedEntries.length,
|
|
4140
|
+
droppedEntries
|
|
4054
4141
|
};
|
|
4055
4142
|
emitNeatEvent({
|
|
4056
4143
|
type: "extraction-complete",
|
|
@@ -4068,7 +4155,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4068
4155
|
// src/persist.ts
|
|
4069
4156
|
init_cjs_shims();
|
|
4070
4157
|
var import_node_fs18 = require("fs");
|
|
4071
|
-
var
|
|
4158
|
+
var import_node_path31 = __toESM(require("path"), 1);
|
|
4072
4159
|
var SCHEMA_VERSION = 2;
|
|
4073
4160
|
function migrateV1ToV2(payload) {
|
|
4074
4161
|
const nodes = payload.graph.nodes;
|
|
@@ -4082,7 +4169,7 @@ function migrateV1ToV2(payload) {
|
|
|
4082
4169
|
return { ...payload, schemaVersion: 2 };
|
|
4083
4170
|
}
|
|
4084
4171
|
async function ensureDir(filePath) {
|
|
4085
|
-
await import_node_fs18.promises.mkdir(
|
|
4172
|
+
await import_node_fs18.promises.mkdir(import_node_path31.default.dirname(filePath), { recursive: true });
|
|
4086
4173
|
}
|
|
4087
4174
|
async function saveGraphToDisk(graph, outPath) {
|
|
4088
4175
|
await ensureDir(outPath);
|
|
@@ -4152,7 +4239,7 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
|
|
|
4152
4239
|
// src/watch.ts
|
|
4153
4240
|
init_cjs_shims();
|
|
4154
4241
|
var import_node_fs22 = __toESM(require("fs"), 1);
|
|
4155
|
-
var
|
|
4242
|
+
var import_node_path37 = __toESM(require("path"), 1);
|
|
4156
4243
|
var import_chokidar = __toESM(require("chokidar"), 1);
|
|
4157
4244
|
|
|
4158
4245
|
// src/api.ts
|
|
@@ -4200,14 +4287,6 @@ function nodeIsFrontier(graph, nodeId) {
|
|
|
4200
4287
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
4201
4288
|
return attrs.type === import_types19.NodeType.FrontierNode;
|
|
4202
4289
|
}
|
|
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;
|
|
4210
|
-
}
|
|
4211
4290
|
function clampConfidence(n) {
|
|
4212
4291
|
if (!Number.isFinite(n)) return 0;
|
|
4213
4292
|
return Math.max(0, Math.min(1, n));
|
|
@@ -4221,32 +4300,34 @@ function reasonForMissingExtracted(source, target, type) {
|
|
|
4221
4300
|
var RECOMMENDATION_MISSING_OBSERVED = "Verify the code path is exercised in production; check feature flags or conditional branches that might gate the call.";
|
|
4222
4301
|
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
4302
|
var RECOMMENDATION_HOST_MISMATCH = "Check environment-specific config overrides \u2014 the runtime host differs from what static configuration declares.";
|
|
4303
|
+
function gradedConfidence(edge) {
|
|
4304
|
+
if (typeof edge.confidence === "number") return clampConfidence(edge.confidence);
|
|
4305
|
+
return clampConfidence(confidenceForEdge(edge));
|
|
4306
|
+
}
|
|
4224
4307
|
function detectMissingDivergences(graph, bucket) {
|
|
4225
4308
|
const out = [];
|
|
4226
4309
|
if (bucket.extracted && !bucket.observed) {
|
|
4227
4310
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
4228
|
-
const sourceHasTraffic = hasAnyObservedFromSource(graph, bucket.source);
|
|
4229
4311
|
out.push({
|
|
4230
4312
|
type: "missing-observed",
|
|
4231
4313
|
source: bucket.source,
|
|
4232
4314
|
target: bucket.target,
|
|
4233
4315
|
edgeType: bucket.type,
|
|
4234
4316
|
extracted: bucket.extracted,
|
|
4235
|
-
confidence:
|
|
4317
|
+
confidence: gradedConfidence(bucket.extracted),
|
|
4236
4318
|
reason: reasonForMissingObserved(bucket.source, bucket.target, bucket.type),
|
|
4237
4319
|
recommendation: RECOMMENDATION_MISSING_OBSERVED
|
|
4238
4320
|
});
|
|
4239
4321
|
}
|
|
4240
4322
|
}
|
|
4241
4323
|
if (bucket.observed && !bucket.extracted) {
|
|
4242
|
-
const cascaded = clampConfidence(confidenceForEdge(bucket.observed));
|
|
4243
4324
|
out.push({
|
|
4244
4325
|
type: "missing-extracted",
|
|
4245
4326
|
source: bucket.source,
|
|
4246
4327
|
target: bucket.target,
|
|
4247
4328
|
edgeType: bucket.type,
|
|
4248
4329
|
observed: bucket.observed,
|
|
4249
|
-
confidence:
|
|
4330
|
+
confidence: gradedConfidence(bucket.observed),
|
|
4250
4331
|
reason: reasonForMissingExtracted(bucket.source, bucket.target, bucket.type),
|
|
4251
4332
|
recommendation: RECOMMENDATION_MISSING_EXTRACTED
|
|
4252
4333
|
});
|
|
@@ -4385,8 +4466,17 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4385
4466
|
const target = opts.node;
|
|
4386
4467
|
filtered = filtered.filter((d) => involvesNode(d, target));
|
|
4387
4468
|
}
|
|
4469
|
+
const TYPE_LEADERSHIP = {
|
|
4470
|
+
"missing-extracted": 0,
|
|
4471
|
+
"missing-observed": 1,
|
|
4472
|
+
"version-mismatch": 2,
|
|
4473
|
+
"host-mismatch": 3,
|
|
4474
|
+
"compat-violation": 4
|
|
4475
|
+
};
|
|
4388
4476
|
filtered.sort((a, b) => {
|
|
4389
4477
|
if (b.confidence !== a.confidence) return b.confidence - a.confidence;
|
|
4478
|
+
const lead = TYPE_LEADERSHIP[a.type] - TYPE_LEADERSHIP[b.type];
|
|
4479
|
+
if (lead !== 0) return lead;
|
|
4390
4480
|
if (a.type !== b.type) return a.type.localeCompare(b.type);
|
|
4391
4481
|
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
4392
4482
|
return a.target.localeCompare(b.target);
|
|
@@ -4477,23 +4567,23 @@ function canonicalJson(value) {
|
|
|
4477
4567
|
|
|
4478
4568
|
// src/projects.ts
|
|
4479
4569
|
init_cjs_shims();
|
|
4480
|
-
var
|
|
4570
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
4481
4571
|
function pathsForProject(project, baseDir) {
|
|
4482
4572
|
if (project === DEFAULT_PROJECT) {
|
|
4483
4573
|
return {
|
|
4484
|
-
snapshotPath:
|
|
4485
|
-
errorsPath:
|
|
4486
|
-
staleEventsPath:
|
|
4487
|
-
embeddingsCachePath:
|
|
4488
|
-
policyViolationsPath:
|
|
4574
|
+
snapshotPath: import_node_path32.default.join(baseDir, "graph.json"),
|
|
4575
|
+
errorsPath: import_node_path32.default.join(baseDir, "errors.ndjson"),
|
|
4576
|
+
staleEventsPath: import_node_path32.default.join(baseDir, "stale-events.ndjson"),
|
|
4577
|
+
embeddingsCachePath: import_node_path32.default.join(baseDir, "embeddings.json"),
|
|
4578
|
+
policyViolationsPath: import_node_path32.default.join(baseDir, "policy-violations.ndjson")
|
|
4489
4579
|
};
|
|
4490
4580
|
}
|
|
4491
4581
|
return {
|
|
4492
|
-
snapshotPath:
|
|
4493
|
-
errorsPath:
|
|
4494
|
-
staleEventsPath:
|
|
4495
|
-
embeddingsCachePath:
|
|
4496
|
-
policyViolationsPath:
|
|
4582
|
+
snapshotPath: import_node_path32.default.join(baseDir, `${project}.json`),
|
|
4583
|
+
errorsPath: import_node_path32.default.join(baseDir, `errors.${project}.ndjson`),
|
|
4584
|
+
staleEventsPath: import_node_path32.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
4585
|
+
embeddingsCachePath: import_node_path32.default.join(baseDir, `embeddings.${project}.json`),
|
|
4586
|
+
policyViolationsPath: import_node_path32.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
4497
4587
|
};
|
|
4498
4588
|
}
|
|
4499
4589
|
var Projects = class {
|
|
@@ -4531,23 +4621,23 @@ var Projects = class {
|
|
|
4531
4621
|
init_cjs_shims();
|
|
4532
4622
|
var import_node_fs20 = require("fs");
|
|
4533
4623
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
4534
|
-
var
|
|
4624
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
4535
4625
|
var import_types20 = require("@neat.is/types");
|
|
4536
4626
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
4537
4627
|
var LOCK_RETRY_MS = 50;
|
|
4538
4628
|
function neatHome() {
|
|
4539
4629
|
const override = process.env.NEAT_HOME;
|
|
4540
|
-
if (override && override.length > 0) return
|
|
4541
|
-
return
|
|
4630
|
+
if (override && override.length > 0) return import_node_path33.default.resolve(override);
|
|
4631
|
+
return import_node_path33.default.join(import_node_os2.default.homedir(), ".neat");
|
|
4542
4632
|
}
|
|
4543
4633
|
function registryPath() {
|
|
4544
|
-
return
|
|
4634
|
+
return import_node_path33.default.join(neatHome(), "projects.json");
|
|
4545
4635
|
}
|
|
4546
4636
|
function registryLockPath() {
|
|
4547
|
-
return
|
|
4637
|
+
return import_node_path33.default.join(neatHome(), "projects.json.lock");
|
|
4548
4638
|
}
|
|
4549
4639
|
async function normalizeProjectPath(input) {
|
|
4550
|
-
const resolved =
|
|
4640
|
+
const resolved = import_node_path33.default.resolve(input);
|
|
4551
4641
|
try {
|
|
4552
4642
|
return await import_node_fs20.promises.realpath(resolved);
|
|
4553
4643
|
} catch {
|
|
@@ -4555,7 +4645,7 @@ async function normalizeProjectPath(input) {
|
|
|
4555
4645
|
}
|
|
4556
4646
|
}
|
|
4557
4647
|
async function writeAtomically(target, contents) {
|
|
4558
|
-
await import_node_fs20.promises.mkdir(
|
|
4648
|
+
await import_node_fs20.promises.mkdir(import_node_path33.default.dirname(target), { recursive: true });
|
|
4559
4649
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
4560
4650
|
const fd = await import_node_fs20.promises.open(tmp, "w");
|
|
4561
4651
|
try {
|
|
@@ -4568,7 +4658,7 @@ async function writeAtomically(target, contents) {
|
|
|
4568
4658
|
}
|
|
4569
4659
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS) {
|
|
4570
4660
|
const deadline = Date.now() + timeoutMs;
|
|
4571
|
-
await import_node_fs20.promises.mkdir(
|
|
4661
|
+
await import_node_fs20.promises.mkdir(import_node_path33.default.dirname(lockPath), { recursive: true });
|
|
4572
4662
|
while (true) {
|
|
4573
4663
|
try {
|
|
4574
4664
|
const fd = await import_node_fs20.promises.open(lockPath, "wx");
|
|
@@ -5175,7 +5265,7 @@ init_otel_grpc();
|
|
|
5175
5265
|
// src/search.ts
|
|
5176
5266
|
init_cjs_shims();
|
|
5177
5267
|
var import_node_fs21 = require("fs");
|
|
5178
|
-
var
|
|
5268
|
+
var import_node_path36 = __toESM(require("path"), 1);
|
|
5179
5269
|
var import_node_crypto = require("crypto");
|
|
5180
5270
|
var DEFAULT_LIMIT = 10;
|
|
5181
5271
|
var NOMIC_DIM = 768;
|
|
@@ -5314,7 +5404,7 @@ async function readCache(cachePath) {
|
|
|
5314
5404
|
}
|
|
5315
5405
|
}
|
|
5316
5406
|
async function writeCache(cachePath, cache) {
|
|
5317
|
-
await import_node_fs21.promises.mkdir(
|
|
5407
|
+
await import_node_fs21.promises.mkdir(import_node_path36.default.dirname(cachePath), { recursive: true });
|
|
5318
5408
|
await import_node_fs21.promises.writeFile(cachePath, JSON.stringify(cache));
|
|
5319
5409
|
}
|
|
5320
5410
|
var VectorIndex = class {
|
|
@@ -5470,8 +5560,8 @@ var ALL_PHASES = [
|
|
|
5470
5560
|
];
|
|
5471
5561
|
function classifyChange(relPath) {
|
|
5472
5562
|
const phases = /* @__PURE__ */ new Set();
|
|
5473
|
-
const base =
|
|
5474
|
-
const segments = relPath.split(
|
|
5563
|
+
const base = import_node_path37.default.basename(relPath).toLowerCase();
|
|
5564
|
+
const segments = relPath.split(import_node_path37.default.sep).map((s) => s.toLowerCase());
|
|
5475
5565
|
if (base === "package.json" || base === "requirements.txt" || base === "pyproject.toml" || base === "setup.py") {
|
|
5476
5566
|
phases.add("services");
|
|
5477
5567
|
phases.add("aliases");
|
|
@@ -5573,9 +5663,9 @@ function countWatchableDirs(scanPath, limit) {
|
|
|
5573
5663
|
for (const e of entries) {
|
|
5574
5664
|
if (count >= limit) return;
|
|
5575
5665
|
if (!e.isDirectory()) continue;
|
|
5576
|
-
if (IGNORED_WATCH_PATHS.some((re) => re.test(
|
|
5666
|
+
if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path37.default.join(dir, e.name) + import_node_path37.default.sep))) continue;
|
|
5577
5667
|
count++;
|
|
5578
|
-
if (depth < 2) visit(
|
|
5668
|
+
if (depth < 2) visit(import_node_path37.default.join(dir, e.name), depth + 1);
|
|
5579
5669
|
}
|
|
5580
5670
|
};
|
|
5581
5671
|
visit(scanPath, 0);
|
|
@@ -5593,8 +5683,8 @@ async function startWatch(graph, opts) {
|
|
|
5593
5683
|
const projectName = opts.project ?? DEFAULT_PROJECT;
|
|
5594
5684
|
await loadGraphFromDisk(graph, opts.outPath);
|
|
5595
5685
|
const detachEventBus = attachGraphToEventBus(graph, { project: projectName });
|
|
5596
|
-
const policyFilePath =
|
|
5597
|
-
const policyViolationsPath =
|
|
5686
|
+
const policyFilePath = import_node_path37.default.join(opts.scanPath, "policy.json");
|
|
5687
|
+
const policyViolationsPath = import_node_path37.default.join(import_node_path37.default.dirname(opts.outPath), "policy-violations.ndjson");
|
|
5598
5688
|
let policies = [];
|
|
5599
5689
|
try {
|
|
5600
5690
|
policies = await loadPolicyFile(policyFilePath);
|
|
@@ -5643,7 +5733,7 @@ async function startWatch(graph, opts) {
|
|
|
5643
5733
|
const host = opts.host ?? "0.0.0.0";
|
|
5644
5734
|
const port = opts.port ?? 8080;
|
|
5645
5735
|
const otelPort = opts.otelPort ?? 4318;
|
|
5646
|
-
const cachePath = opts.embeddingsCachePath ??
|
|
5736
|
+
const cachePath = opts.embeddingsCachePath ?? import_node_path37.default.join(import_node_path37.default.dirname(opts.outPath), "embeddings.json");
|
|
5647
5737
|
let searchIndex;
|
|
5648
5738
|
try {
|
|
5649
5739
|
searchIndex = await buildSearchIndex(graph, { cachePath });
|
|
@@ -5661,7 +5751,7 @@ async function startWatch(graph, opts) {
|
|
|
5661
5751
|
// Paths are derived from the explicit options the watch caller passes
|
|
5662
5752
|
// — pathsForProject is only used to fill in the embeddings/snapshot
|
|
5663
5753
|
// fields so the registry shape is complete.
|
|
5664
|
-
...pathsForProject(projectName,
|
|
5754
|
+
...pathsForProject(projectName, import_node_path37.default.dirname(opts.outPath)),
|
|
5665
5755
|
snapshotPath: opts.outPath,
|
|
5666
5756
|
errorsPath: opts.errorsPath,
|
|
5667
5757
|
staleEventsPath: opts.staleEventsPath
|
|
@@ -5746,9 +5836,9 @@ async function startWatch(graph, opts) {
|
|
|
5746
5836
|
};
|
|
5747
5837
|
const onPath = (absPath) => {
|
|
5748
5838
|
if (shouldIgnore(absPath)) return;
|
|
5749
|
-
const rel =
|
|
5839
|
+
const rel = import_node_path37.default.relative(opts.scanPath, absPath);
|
|
5750
5840
|
if (!rel || rel.startsWith("..")) return;
|
|
5751
|
-
pendingPaths.add(rel.split(
|
|
5841
|
+
pendingPaths.add(rel.split(import_node_path37.default.sep).join("/"));
|
|
5752
5842
|
const phases = classifyChange(rel);
|
|
5753
5843
|
if (phases.size === 0) {
|
|
5754
5844
|
for (const p of ALL_PHASES) pending.add(p);
|
|
@@ -5806,7 +5896,7 @@ init_cjs_shims();
|
|
|
5806
5896
|
// src/installers/javascript.ts
|
|
5807
5897
|
init_cjs_shims();
|
|
5808
5898
|
var import_node_fs23 = require("fs");
|
|
5809
|
-
var
|
|
5899
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
5810
5900
|
var SDK_PACKAGES = [
|
|
5811
5901
|
{ name: "@opentelemetry/api", version: "^1.9.0" },
|
|
5812
5902
|
{ name: "@opentelemetry/sdk-node", version: "^0.57.0" },
|
|
@@ -5822,7 +5912,7 @@ var OTEL_ENV = {
|
|
|
5822
5912
|
};
|
|
5823
5913
|
async function readPackageJson(serviceDir) {
|
|
5824
5914
|
try {
|
|
5825
|
-
const raw = await import_node_fs23.promises.readFile(
|
|
5915
|
+
const raw = await import_node_fs23.promises.readFile(import_node_path38.default.join(serviceDir, "package.json"), "utf8");
|
|
5826
5916
|
return JSON.parse(raw);
|
|
5827
5917
|
} catch {
|
|
5828
5918
|
return null;
|
|
@@ -5841,7 +5931,7 @@ function rewriteStartScript(start) {
|
|
|
5841
5931
|
}
|
|
5842
5932
|
async function plan(serviceDir) {
|
|
5843
5933
|
const pkg = await readPackageJson(serviceDir);
|
|
5844
|
-
const manifestPath =
|
|
5934
|
+
const manifestPath = import_node_path38.default.join(serviceDir, "package.json");
|
|
5845
5935
|
const empty = {
|
|
5846
5936
|
language: "javascript",
|
|
5847
5937
|
serviceDir,
|
|
@@ -5940,7 +6030,7 @@ async function rollback(installPlan, originals) {
|
|
|
5940
6030
|
...restored.map((f) => `restored: ${f}`),
|
|
5941
6031
|
""
|
|
5942
6032
|
];
|
|
5943
|
-
const rollbackPath =
|
|
6033
|
+
const rollbackPath = import_node_path38.default.join(installPlan.serviceDir, "neat-rollback.patch");
|
|
5944
6034
|
await import_node_fs23.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
|
|
5945
6035
|
}
|
|
5946
6036
|
var javascriptInstaller = {
|
|
@@ -5953,7 +6043,7 @@ var javascriptInstaller = {
|
|
|
5953
6043
|
// src/installers/python.ts
|
|
5954
6044
|
init_cjs_shims();
|
|
5955
6045
|
var import_node_fs24 = require("fs");
|
|
5956
|
-
var
|
|
6046
|
+
var import_node_path39 = __toESM(require("path"), 1);
|
|
5957
6047
|
var SDK_PACKAGES2 = [
|
|
5958
6048
|
{ name: "opentelemetry-distro", version: ">=0.49b0" },
|
|
5959
6049
|
{ name: "opentelemetry-exporter-otlp", version: ">=1.28.0" }
|
|
@@ -5974,7 +6064,7 @@ async function exists2(p) {
|
|
|
5974
6064
|
async function detect2(serviceDir) {
|
|
5975
6065
|
const markers = ["requirements.txt", "pyproject.toml", "setup.py"];
|
|
5976
6066
|
for (const m of markers) {
|
|
5977
|
-
if (await exists2(
|
|
6067
|
+
if (await exists2(import_node_path39.default.join(serviceDir, m))) return true;
|
|
5978
6068
|
}
|
|
5979
6069
|
return false;
|
|
5980
6070
|
}
|
|
@@ -5984,7 +6074,7 @@ function reqPackageName(line) {
|
|
|
5984
6074
|
return head.replace(/[<>=!~].*$/, "").toLowerCase();
|
|
5985
6075
|
}
|
|
5986
6076
|
async function planRequirementsTxtEdits(serviceDir) {
|
|
5987
|
-
const file =
|
|
6077
|
+
const file = import_node_path39.default.join(serviceDir, "requirements.txt");
|
|
5988
6078
|
if (!await exists2(file)) return null;
|
|
5989
6079
|
const raw = await import_node_fs24.promises.readFile(file, "utf8");
|
|
5990
6080
|
const presentNames = new Set(
|
|
@@ -5994,7 +6084,7 @@ async function planRequirementsTxtEdits(serviceDir) {
|
|
|
5994
6084
|
return { manifest: file, missing: [...missing] };
|
|
5995
6085
|
}
|
|
5996
6086
|
async function planProcfileEdits(serviceDir) {
|
|
5997
|
-
const procfile =
|
|
6087
|
+
const procfile = import_node_path39.default.join(serviceDir, "Procfile");
|
|
5998
6088
|
if (!await exists2(procfile)) return [];
|
|
5999
6089
|
const raw = await import_node_fs24.promises.readFile(procfile, "utf8");
|
|
6000
6090
|
const edits = [];
|
|
@@ -6079,7 +6169,7 @@ async function apply2(installPlan) {
|
|
|
6079
6169
|
if (raw === void 0) {
|
|
6080
6170
|
throw new Error(`python installer: cannot read ${file} during apply`);
|
|
6081
6171
|
}
|
|
6082
|
-
const base =
|
|
6172
|
+
const base = import_node_path39.default.basename(file);
|
|
6083
6173
|
if (base === "requirements.txt") {
|
|
6084
6174
|
const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
|
|
6085
6175
|
if (edits.length > 0) await applyRequirementsTxt(file, edits, raw);
|
|
@@ -6111,7 +6201,7 @@ async function rollback2(installPlan, originals) {
|
|
|
6111
6201
|
...restored.map((f) => `restored: ${f}`),
|
|
6112
6202
|
""
|
|
6113
6203
|
];
|
|
6114
|
-
const rollbackPath =
|
|
6204
|
+
const rollbackPath = import_node_path39.default.join(installPlan.serviceDir, "neat-rollback.patch");
|
|
6115
6205
|
await import_node_fs24.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
|
|
6116
6206
|
}
|
|
6117
6207
|
var pythonInstaller = {
|
|
@@ -6225,10 +6315,10 @@ var TransportError = class extends Error {
|
|
|
6225
6315
|
function createHttpClient(baseUrl) {
|
|
6226
6316
|
const root = baseUrl.replace(/\/$/, "");
|
|
6227
6317
|
return {
|
|
6228
|
-
async get(
|
|
6318
|
+
async get(path41) {
|
|
6229
6319
|
let res;
|
|
6230
6320
|
try {
|
|
6231
|
-
res = await fetch(`${root}${
|
|
6321
|
+
res = await fetch(`${root}${path41}`);
|
|
6232
6322
|
} catch (err) {
|
|
6233
6323
|
throw new TransportError(
|
|
6234
6324
|
`cannot reach neat-core at ${root}: ${err.message}`
|
|
@@ -6238,16 +6328,16 @@ function createHttpClient(baseUrl) {
|
|
|
6238
6328
|
const body = await res.text().catch(() => "");
|
|
6239
6329
|
throw new HttpError(
|
|
6240
6330
|
res.status,
|
|
6241
|
-
`${res.status} ${res.statusText} on GET ${
|
|
6331
|
+
`${res.status} ${res.statusText} on GET ${path41}: ${body}`,
|
|
6242
6332
|
body
|
|
6243
6333
|
);
|
|
6244
6334
|
}
|
|
6245
6335
|
return await res.json();
|
|
6246
6336
|
},
|
|
6247
|
-
async post(
|
|
6337
|
+
async post(path41, body) {
|
|
6248
6338
|
let res;
|
|
6249
6339
|
try {
|
|
6250
|
-
res = await fetch(`${root}${
|
|
6340
|
+
res = await fetch(`${root}${path41}`, {
|
|
6251
6341
|
method: "POST",
|
|
6252
6342
|
headers: { "content-type": "application/json" },
|
|
6253
6343
|
body: JSON.stringify(body)
|
|
@@ -6261,7 +6351,7 @@ function createHttpClient(baseUrl) {
|
|
|
6261
6351
|
const text = await res.text().catch(() => "");
|
|
6262
6352
|
throw new HttpError(
|
|
6263
6353
|
res.status,
|
|
6264
|
-
`${res.status} ${res.statusText} on POST ${
|
|
6354
|
+
`${res.status} ${res.statusText} on POST ${path41}: ${text}`,
|
|
6265
6355
|
text
|
|
6266
6356
|
);
|
|
6267
6357
|
}
|
|
@@ -6275,12 +6365,12 @@ function projectPath(project, suffix) {
|
|
|
6275
6365
|
}
|
|
6276
6366
|
async function runRootCause(client, input) {
|
|
6277
6367
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
6278
|
-
const
|
|
6368
|
+
const path41 = projectPath(
|
|
6279
6369
|
input.project,
|
|
6280
6370
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
6281
6371
|
);
|
|
6282
6372
|
try {
|
|
6283
|
-
const result = await client.get(
|
|
6373
|
+
const result = await client.get(path41);
|
|
6284
6374
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
6285
6375
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
6286
6376
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -6306,12 +6396,12 @@ async function runRootCause(client, input) {
|
|
|
6306
6396
|
}
|
|
6307
6397
|
async function runBlastRadius(client, input) {
|
|
6308
6398
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
6309
|
-
const
|
|
6399
|
+
const path41 = projectPath(
|
|
6310
6400
|
input.project,
|
|
6311
6401
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
6312
6402
|
);
|
|
6313
6403
|
try {
|
|
6314
|
-
const result = await client.get(
|
|
6404
|
+
const result = await client.get(path41);
|
|
6315
6405
|
if (result.totalAffected === 0) {
|
|
6316
6406
|
return {
|
|
6317
6407
|
summary: `${result.origin} has no downstream dependencies. Nothing else would break if it failed.`
|
|
@@ -6345,12 +6435,12 @@ function formatBlastEntry(n) {
|
|
|
6345
6435
|
}
|
|
6346
6436
|
async function runDependencies(client, input) {
|
|
6347
6437
|
const depth = input.depth ?? 3;
|
|
6348
|
-
const
|
|
6438
|
+
const path41 = projectPath(
|
|
6349
6439
|
input.project,
|
|
6350
6440
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
6351
6441
|
);
|
|
6352
6442
|
try {
|
|
6353
|
-
const result = await client.get(
|
|
6443
|
+
const result = await client.get(path41);
|
|
6354
6444
|
if (result.total === 0) {
|
|
6355
6445
|
return {
|
|
6356
6446
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -6431,9 +6521,9 @@ function formatDuration(ms) {
|
|
|
6431
6521
|
return `${Math.round(h / 24)}d`;
|
|
6432
6522
|
}
|
|
6433
6523
|
async function runIncidents(client, input) {
|
|
6434
|
-
const
|
|
6524
|
+
const path41 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
6435
6525
|
try {
|
|
6436
|
-
const body = await client.get(
|
|
6526
|
+
const body = await client.get(path41);
|
|
6437
6527
|
const events = body.events;
|
|
6438
6528
|
if (events.length === 0) {
|
|
6439
6529
|
return {
|
|
@@ -6957,7 +7047,7 @@ async function runInit(opts) {
|
|
|
6957
7047
|
printDiscoveryReport(opts, services);
|
|
6958
7048
|
const sections = opts.noInstall ? [] : await buildPatchSections(services);
|
|
6959
7049
|
const patch = renderPatch(sections);
|
|
6960
|
-
const patchPath =
|
|
7050
|
+
const patchPath = import_node_path40.default.join(opts.scanPath, "neat.patch");
|
|
6961
7051
|
if (opts.dryRun) {
|
|
6962
7052
|
await import_node_fs25.promises.writeFile(patchPath, patch, "utf8");
|
|
6963
7053
|
written.push(patchPath);
|
|
@@ -6970,9 +7060,9 @@ async function runInit(opts) {
|
|
|
6970
7060
|
const graph = getGraph(graphKey);
|
|
6971
7061
|
const projectPaths = pathsForProject(
|
|
6972
7062
|
graphKey,
|
|
6973
|
-
|
|
7063
|
+
import_node_path40.default.join(opts.scanPath, "neat-out")
|
|
6974
7064
|
);
|
|
6975
|
-
const errorsPath =
|
|
7065
|
+
const errorsPath = import_node_path40.default.join(import_node_path40.default.dirname(opts.outPath), import_node_path40.default.basename(projectPaths.errorsPath));
|
|
6976
7066
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
6977
7067
|
await saveGraphToDisk(graph, opts.outPath);
|
|
6978
7068
|
written.push(opts.outPath);
|
|
@@ -7022,6 +7112,7 @@ async function runInit(opts) {
|
|
|
7022
7112
|
if (result.extractionErrors > 0) {
|
|
7023
7113
|
console.log(`errors: ${errorsPath}`);
|
|
7024
7114
|
}
|
|
7115
|
+
console.log(formatPrecisionFloorBanner(result.extractedDropped));
|
|
7025
7116
|
const incompatibilities = findIncompatibilities(nodes);
|
|
7026
7117
|
if (incompatibilities.length > 0) {
|
|
7027
7118
|
console.log("");
|
|
@@ -7051,9 +7142,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
7051
7142
|
};
|
|
7052
7143
|
function claudeConfigPath() {
|
|
7053
7144
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
7054
|
-
if (override && override.length > 0) return
|
|
7145
|
+
if (override && override.length > 0) return import_node_path40.default.resolve(override);
|
|
7055
7146
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
7056
|
-
return
|
|
7147
|
+
return import_node_path40.default.join(home, ".claude.json");
|
|
7057
7148
|
}
|
|
7058
7149
|
async function runSkill(opts) {
|
|
7059
7150
|
const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -7077,7 +7168,7 @@ async function runSkill(opts) {
|
|
|
7077
7168
|
...existing,
|
|
7078
7169
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
7079
7170
|
};
|
|
7080
|
-
await import_node_fs25.promises.mkdir(
|
|
7171
|
+
await import_node_fs25.promises.mkdir(import_node_path40.default.dirname(target), { recursive: true });
|
|
7081
7172
|
await import_node_fs25.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
7082
7173
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
7083
7174
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
@@ -7112,12 +7203,12 @@ async function main() {
|
|
|
7112
7203
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
7113
7204
|
process.exit(2);
|
|
7114
7205
|
}
|
|
7115
|
-
const scanPath =
|
|
7206
|
+
const scanPath = import_node_path40.default.resolve(target);
|
|
7116
7207
|
const projectExplicit = parsed.project !== null;
|
|
7117
|
-
const projectName = projectExplicit ? project :
|
|
7208
|
+
const projectName = projectExplicit ? project : import_node_path40.default.basename(scanPath);
|
|
7118
7209
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
7119
|
-
const fallback = pathsForProject(projectKey,
|
|
7120
|
-
const outPath =
|
|
7210
|
+
const fallback = pathsForProject(projectKey, import_node_path40.default.join(scanPath, "neat-out")).snapshotPath;
|
|
7211
|
+
const outPath = import_node_path40.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
|
|
7121
7212
|
const result = await runInit({
|
|
7122
7213
|
scanPath,
|
|
7123
7214
|
outPath,
|
|
@@ -7137,21 +7228,21 @@ async function main() {
|
|
|
7137
7228
|
usage();
|
|
7138
7229
|
process.exit(2);
|
|
7139
7230
|
}
|
|
7140
|
-
const scanPath =
|
|
7231
|
+
const scanPath = import_node_path40.default.resolve(target);
|
|
7141
7232
|
const stat = await import_node_fs25.promises.stat(scanPath).catch(() => null);
|
|
7142
7233
|
if (!stat || !stat.isDirectory()) {
|
|
7143
7234
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
7144
7235
|
process.exit(2);
|
|
7145
7236
|
}
|
|
7146
|
-
const projectPaths = pathsForProject(project,
|
|
7147
|
-
const outPath =
|
|
7148
|
-
const errorsPath =
|
|
7149
|
-
process.env.NEAT_ERRORS_PATH ??
|
|
7237
|
+
const projectPaths = pathsForProject(project, import_node_path40.default.join(scanPath, "neat-out"));
|
|
7238
|
+
const outPath = import_node_path40.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
|
|
7239
|
+
const errorsPath = import_node_path40.default.resolve(
|
|
7240
|
+
process.env.NEAT_ERRORS_PATH ?? import_node_path40.default.join(import_node_path40.default.dirname(outPath), import_node_path40.default.basename(projectPaths.errorsPath))
|
|
7150
7241
|
);
|
|
7151
|
-
const staleEventsPath =
|
|
7152
|
-
process.env.NEAT_STALE_EVENTS_PATH ??
|
|
7242
|
+
const staleEventsPath = import_node_path40.default.resolve(
|
|
7243
|
+
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
7244
|
);
|
|
7154
|
-
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ?
|
|
7245
|
+
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path40.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
|
|
7155
7246
|
const handle = await startWatch(getGraph(project), {
|
|
7156
7247
|
scanPath,
|
|
7157
7248
|
outPath,
|