@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/index.d.cts
CHANGED
|
@@ -15,6 +15,18 @@ interface ExtractionError {
|
|
|
15
15
|
ts: string;
|
|
16
16
|
source: 'extract';
|
|
17
17
|
}
|
|
18
|
+
interface DroppedExtractedEdge {
|
|
19
|
+
source: string;
|
|
20
|
+
target: string;
|
|
21
|
+
type: string;
|
|
22
|
+
confidence: number;
|
|
23
|
+
confidenceKind: string;
|
|
24
|
+
evidence: {
|
|
25
|
+
file: string;
|
|
26
|
+
line?: number;
|
|
27
|
+
snippet?: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
18
30
|
|
|
19
31
|
interface ExtractResult {
|
|
20
32
|
nodesAdded: number;
|
|
@@ -23,6 +35,8 @@ interface ExtractResult {
|
|
|
23
35
|
extractionErrors: number;
|
|
24
36
|
errorEntries: ExtractionError[];
|
|
25
37
|
ghostsRetired: number;
|
|
38
|
+
extractedDropped: number;
|
|
39
|
+
droppedEntries: DroppedExtractedEdge[];
|
|
26
40
|
}
|
|
27
41
|
interface ExtractOptions {
|
|
28
42
|
onPolicyTrigger?: (graph: NeatGraph) => Promise<void> | void;
|
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,18 @@ interface ExtractionError {
|
|
|
15
15
|
ts: string;
|
|
16
16
|
source: 'extract';
|
|
17
17
|
}
|
|
18
|
+
interface DroppedExtractedEdge {
|
|
19
|
+
source: string;
|
|
20
|
+
target: string;
|
|
21
|
+
type: string;
|
|
22
|
+
confidence: number;
|
|
23
|
+
confidenceKind: string;
|
|
24
|
+
evidence: {
|
|
25
|
+
file: string;
|
|
26
|
+
line?: number;
|
|
27
|
+
snippet?: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
18
30
|
|
|
19
31
|
interface ExtractResult {
|
|
20
32
|
nodesAdded: number;
|
|
@@ -23,6 +35,8 @@ interface ExtractResult {
|
|
|
23
35
|
extractionErrors: number;
|
|
24
36
|
errorEntries: ExtractionError[];
|
|
25
37
|
ghostsRetired: number;
|
|
38
|
+
extractedDropped: number;
|
|
39
|
+
droppedEntries: DroppedExtractedEdge[];
|
|
26
40
|
}
|
|
27
41
|
interface ExtractOptions {
|
|
28
42
|
onPolicyTrigger?: (graph: NeatGraph) => Promise<void> | void;
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
routeSpanToProject,
|
|
3
3
|
startDaemon
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-T3A2GK3X.js";
|
|
5
5
|
import {
|
|
6
6
|
ProjectNameCollisionError,
|
|
7
7
|
addProject,
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
thresholdForEdgeType,
|
|
38
38
|
touchLastSeen,
|
|
39
39
|
writeAtomically
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-33ZZ2ZID.js";
|
|
41
41
|
import {
|
|
42
42
|
startOtelGrpcReceiver
|
|
43
43
|
} from "./chunk-G3PDTGOW.js";
|
package/dist/neatd.cjs
CHANGED
|
@@ -100,8 +100,8 @@ function reshapeGrpcRequest(req2) {
|
|
|
100
100
|
};
|
|
101
101
|
}
|
|
102
102
|
function resolveProtoRoot() {
|
|
103
|
-
const here =
|
|
104
|
-
return
|
|
103
|
+
const here = import_node_path34.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
104
|
+
return import_node_path34.default.resolve(here, "..", "proto");
|
|
105
105
|
}
|
|
106
106
|
function loadTraceService() {
|
|
107
107
|
const protoRoot = resolveProtoRoot();
|
|
@@ -156,13 +156,13 @@ async function startOtelGrpcReceiver(opts) {
|
|
|
156
156
|
})
|
|
157
157
|
};
|
|
158
158
|
}
|
|
159
|
-
var import_node_url,
|
|
159
|
+
var import_node_url, import_node_path34, grpc, protoLoader;
|
|
160
160
|
var init_otel_grpc = __esm({
|
|
161
161
|
"src/otel-grpc.ts"() {
|
|
162
162
|
"use strict";
|
|
163
163
|
init_cjs_shims();
|
|
164
164
|
import_node_url = require("url");
|
|
165
|
-
|
|
165
|
+
import_node_path34 = __toESM(require("path"), 1);
|
|
166
166
|
grpc = __toESM(require("@grpc/grpc-js"), 1);
|
|
167
167
|
protoLoader = __toESM(require("@grpc/proto-loader"), 1);
|
|
168
168
|
init_otel();
|
|
@@ -259,10 +259,10 @@ function parseOtlpRequest(body) {
|
|
|
259
259
|
}
|
|
260
260
|
function loadProtobufDecoder() {
|
|
261
261
|
if (exportTraceServiceRequestType) return exportTraceServiceRequestType;
|
|
262
|
-
const here =
|
|
263
|
-
const protoRoot =
|
|
262
|
+
const here = import_node_path35.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
263
|
+
const protoRoot = import_node_path35.default.resolve(here, "..", "proto");
|
|
264
264
|
const root = new import_protobufjs.default.Root();
|
|
265
|
-
root.resolvePath = (_origin, target) =>
|
|
265
|
+
root.resolvePath = (_origin, target) => import_node_path35.default.resolve(protoRoot, target);
|
|
266
266
|
root.loadSync(
|
|
267
267
|
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
268
268
|
{ keepCase: true }
|
|
@@ -354,12 +354,12 @@ async function buildOtelReceiver(opts) {
|
|
|
354
354
|
};
|
|
355
355
|
return decorated;
|
|
356
356
|
}
|
|
357
|
-
var
|
|
357
|
+
var import_node_path35, import_node_url2, import_fastify2, import_protobufjs, exportTraceServiceRequestType;
|
|
358
358
|
var init_otel = __esm({
|
|
359
359
|
"src/otel.ts"() {
|
|
360
360
|
"use strict";
|
|
361
361
|
init_cjs_shims();
|
|
362
|
-
|
|
362
|
+
import_node_path35 = __toESM(require("path"), 1);
|
|
363
363
|
import_node_url2 = require("url");
|
|
364
364
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
365
365
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
@@ -370,12 +370,12 @@ var init_otel = __esm({
|
|
|
370
370
|
// src/neatd.ts
|
|
371
371
|
init_cjs_shims();
|
|
372
372
|
var import_node_fs22 = require("fs");
|
|
373
|
-
var
|
|
373
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
374
374
|
|
|
375
375
|
// src/daemon.ts
|
|
376
376
|
init_cjs_shims();
|
|
377
377
|
var import_node_fs21 = require("fs");
|
|
378
|
-
var
|
|
378
|
+
var import_node_path36 = __toESM(require("path"), 1);
|
|
379
379
|
|
|
380
380
|
// src/graph.ts
|
|
381
381
|
init_cjs_shims();
|
|
@@ -831,19 +831,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
831
831
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
832
832
|
let best = { path: [start], edges: [] };
|
|
833
833
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
834
|
-
function step(node,
|
|
835
|
-
if (
|
|
836
|
-
best = { path: [...
|
|
834
|
+
function step(node, path39, edges) {
|
|
835
|
+
if (path39.length > best.path.length) {
|
|
836
|
+
best = { path: [...path39], edges: [...edges] };
|
|
837
837
|
}
|
|
838
|
-
if (
|
|
838
|
+
if (path39.length - 1 >= maxDepth) return;
|
|
839
839
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
840
840
|
for (const [srcId, edge] of incoming) {
|
|
841
841
|
if (visited.has(srcId)) continue;
|
|
842
842
|
visited.add(srcId);
|
|
843
|
-
|
|
843
|
+
path39.push(srcId);
|
|
844
844
|
edges.push(edge);
|
|
845
|
-
step(srcId,
|
|
846
|
-
|
|
845
|
+
step(srcId, path39, edges);
|
|
846
|
+
path39.pop();
|
|
847
847
|
edges.pop();
|
|
848
848
|
visited.delete(srcId);
|
|
849
849
|
}
|
|
@@ -1510,35 +1510,37 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
|
1510
1510
|
const existing = graph.getEdgeAttributes(id);
|
|
1511
1511
|
const newSpanCount = (existing.signal?.spanCount ?? existing.callCount ?? 0) + 1;
|
|
1512
1512
|
const newErrorCount = (existing.signal?.errorCount ?? 0) + (isError ? 1 : 0);
|
|
1513
|
+
const newSignal = {
|
|
1514
|
+
spanCount: newSpanCount,
|
|
1515
|
+
errorCount: newErrorCount,
|
|
1516
|
+
lastObservedAgeMs: 0
|
|
1517
|
+
};
|
|
1513
1518
|
const updated = {
|
|
1514
1519
|
...existing,
|
|
1515
1520
|
provenance: import_types3.Provenance.OBSERVED,
|
|
1516
1521
|
lastObserved: ts,
|
|
1517
1522
|
callCount: newSpanCount,
|
|
1518
|
-
signal:
|
|
1519
|
-
|
|
1520
|
-
errorCount: newErrorCount,
|
|
1521
|
-
lastObservedAgeMs: 0
|
|
1522
|
-
},
|
|
1523
|
-
confidence: 1
|
|
1523
|
+
signal: newSignal,
|
|
1524
|
+
confidence: (0, import_types3.confidenceForObservedSignal)(newSignal)
|
|
1524
1525
|
};
|
|
1525
1526
|
graph.replaceEdgeAttributes(id, updated);
|
|
1526
1527
|
return { edge: updated, created: false };
|
|
1527
1528
|
}
|
|
1529
|
+
const signal = {
|
|
1530
|
+
spanCount: 1,
|
|
1531
|
+
errorCount: isError ? 1 : 0,
|
|
1532
|
+
lastObservedAgeMs: 0
|
|
1533
|
+
};
|
|
1528
1534
|
const edge = {
|
|
1529
1535
|
id,
|
|
1530
1536
|
source,
|
|
1531
1537
|
target,
|
|
1532
1538
|
type,
|
|
1533
1539
|
provenance: import_types3.Provenance.OBSERVED,
|
|
1534
|
-
confidence:
|
|
1540
|
+
confidence: (0, import_types3.confidenceForObservedSignal)(signal),
|
|
1535
1541
|
lastObserved: ts,
|
|
1536
1542
|
callCount: 1,
|
|
1537
|
-
signal
|
|
1538
|
-
spanCount: 1,
|
|
1539
|
-
errorCount: isError ? 1 : 0,
|
|
1540
|
-
lastObservedAgeMs: 0
|
|
1541
|
-
}
|
|
1543
|
+
signal
|
|
1542
1544
|
};
|
|
1543
1545
|
graph.addEdgeWithKey(id, source, target, edge);
|
|
1544
1546
|
return { edge, created: true };
|
|
@@ -2092,6 +2094,23 @@ async function writeExtractionErrors(errors, errorsPath) {
|
|
|
2092
2094
|
const lines = errors.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
2093
2095
|
await import_node_fs7.promises.appendFile(errorsPath, lines, "utf8");
|
|
2094
2096
|
}
|
|
2097
|
+
var droppedSink = [];
|
|
2098
|
+
function noteExtractedDropped(edge) {
|
|
2099
|
+
droppedSink.push(edge);
|
|
2100
|
+
}
|
|
2101
|
+
function drainDroppedExtracted() {
|
|
2102
|
+
return droppedSink.splice(0, droppedSink.length);
|
|
2103
|
+
}
|
|
2104
|
+
function isRejectedLogEnabled() {
|
|
2105
|
+
const raw = process.env.NEAT_EXTRACTED_REJECTED_LOG;
|
|
2106
|
+
return raw === "1" || raw === "true";
|
|
2107
|
+
}
|
|
2108
|
+
async function writeRejectedExtracted(drops, rejectedPath) {
|
|
2109
|
+
if (drops.length === 0) return;
|
|
2110
|
+
await import_node_fs7.promises.mkdir(import_node_path7.default.dirname(rejectedPath), { recursive: true });
|
|
2111
|
+
const lines = drops.map((d) => JSON.stringify({ ...d, ts: (/* @__PURE__ */ new Date()).toISOString() })).join("\n") + "\n";
|
|
2112
|
+
await import_node_fs7.promises.appendFile(rejectedPath, lines, "utf8");
|
|
2113
|
+
}
|
|
2095
2114
|
|
|
2096
2115
|
// src/extract/services.ts
|
|
2097
2116
|
var DEFAULT_SCAN_DEPTH = 5;
|
|
@@ -3030,6 +3049,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3030
3049
|
target: dbNode.id,
|
|
3031
3050
|
type: import_types7.EdgeType.CONNECTS_TO,
|
|
3032
3051
|
provenance: import_types7.Provenance.EXTRACTED,
|
|
3052
|
+
confidence: (0, import_types7.confidenceForExtracted)("structural"),
|
|
3033
3053
|
// ADR-032 / #140 — every EXTRACTED edge carries evidence.file.
|
|
3034
3054
|
// Ghost-edge cleanup keys retirement on this; the conditional
|
|
3035
3055
|
// sourceFile spread that used to live here was a v0.1.x leftover.
|
|
@@ -3104,6 +3124,7 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3104
3124
|
target: node.id,
|
|
3105
3125
|
type: import_types8.EdgeType.CONFIGURED_BY,
|
|
3106
3126
|
provenance: import_types8.Provenance.EXTRACTED,
|
|
3127
|
+
confidence: (0, import_types8.confidenceForExtracted)("structural"),
|
|
3107
3128
|
evidence: { file: relPath.split(import_node_path18.default.sep).join("/") }
|
|
3108
3129
|
};
|
|
3109
3130
|
if (!graph.hasEdge(edge.id)) {
|
|
@@ -3258,17 +3279,32 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3258
3279
|
for (const [targetId, evidenceFile] of seenTargets) {
|
|
3259
3280
|
const fileContent = files.find((f) => f.path === evidenceFile.file)?.content ?? "";
|
|
3260
3281
|
const line = lineOf(fileContent, `//${evidenceFile.host}`);
|
|
3282
|
+
const confidence = (0, import_types9.confidenceForExtracted)("hostname-shape-match");
|
|
3283
|
+
const ev = {
|
|
3284
|
+
file: import_node_path20.default.relative(service.dir, evidenceFile.file),
|
|
3285
|
+
line,
|
|
3286
|
+
snippet: snippet(fileContent, line)
|
|
3287
|
+
};
|
|
3288
|
+
const edgeId = (0, import_types4.extractedEdgeId)(service.node.id, targetId, import_types9.EdgeType.CALLS);
|
|
3289
|
+
if (!(0, import_types9.passesExtractedFloor)(confidence)) {
|
|
3290
|
+
noteExtractedDropped({
|
|
3291
|
+
source: service.node.id,
|
|
3292
|
+
target: targetId,
|
|
3293
|
+
type: import_types9.EdgeType.CALLS,
|
|
3294
|
+
confidence,
|
|
3295
|
+
confidenceKind: "hostname-shape-match",
|
|
3296
|
+
evidence: ev
|
|
3297
|
+
});
|
|
3298
|
+
continue;
|
|
3299
|
+
}
|
|
3261
3300
|
const edge = {
|
|
3262
|
-
id:
|
|
3301
|
+
id: edgeId,
|
|
3263
3302
|
source: service.node.id,
|
|
3264
3303
|
target: targetId,
|
|
3265
3304
|
type: import_types9.EdgeType.CALLS,
|
|
3266
3305
|
provenance: import_types9.Provenance.EXTRACTED,
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
line,
|
|
3270
|
-
snippet: snippet(fileContent, line)
|
|
3271
|
-
}
|
|
3306
|
+
confidence,
|
|
3307
|
+
evidence: ev
|
|
3272
3308
|
};
|
|
3273
3309
|
if (!graph.hasEdge(edge.id)) {
|
|
3274
3310
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3307,6 +3343,10 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3307
3343
|
name: topic,
|
|
3308
3344
|
kind: "kafka-topic",
|
|
3309
3345
|
edgeType,
|
|
3346
|
+
// `producer.send({topic: 'x'})` / `consumer.subscribe({topic: 'x'})` —
|
|
3347
|
+
// framework-aware (kafkajs / node-rdkafka shape). Verified-call-site
|
|
3348
|
+
// tier (ADR-066).
|
|
3349
|
+
confidenceKind: "verified-call-site",
|
|
3310
3350
|
evidence: {
|
|
3311
3351
|
file: import_node_path21.default.relative(serviceDir, file.path),
|
|
3312
3352
|
line,
|
|
@@ -3339,6 +3379,10 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3339
3379
|
name: host,
|
|
3340
3380
|
kind: "redis",
|
|
3341
3381
|
edgeType: "CALLS",
|
|
3382
|
+
// `redis://host` URL literal — the scheme is structural support, but no
|
|
3383
|
+
// call expression is verified to wire it through. URL-with-structural-
|
|
3384
|
+
// support tier (ADR-066).
|
|
3385
|
+
confidenceKind: "url-with-structural-support",
|
|
3342
3386
|
evidence: {
|
|
3343
3387
|
file: import_node_path22.default.relative(serviceDir, file.path),
|
|
3344
3388
|
line,
|
|
@@ -3380,6 +3424,10 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
3380
3424
|
name,
|
|
3381
3425
|
kind,
|
|
3382
3426
|
edgeType: "CALLS",
|
|
3427
|
+
// SDK marker (S3Client, GetCommand, etc.) plus a Bucket/TableName
|
|
3428
|
+
// literal — framework-aware recognizer, verified-call-site tier
|
|
3429
|
+
// (ADR-066).
|
|
3430
|
+
confidenceKind: "verified-call-site",
|
|
3383
3431
|
evidence: {
|
|
3384
3432
|
file: import_node_path23.default.relative(serviceDir, file.path),
|
|
3385
3433
|
line,
|
|
@@ -3459,6 +3507,10 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
3459
3507
|
name,
|
|
3460
3508
|
kind,
|
|
3461
3509
|
edgeType: "CALLS",
|
|
3510
|
+
// `new <Name>Client(...)` with @aws-sdk/* or @grpc/grpc-js import
|
|
3511
|
+
// context — import-aware classification per #238. Verified-call-site
|
|
3512
|
+
// tier (ADR-066).
|
|
3513
|
+
confidenceKind: "verified-call-site",
|
|
3462
3514
|
evidence: {
|
|
3463
3515
|
file: import_node_path24.default.relative(serviceDir, file.path),
|
|
3464
3516
|
line,
|
|
@@ -3519,6 +3571,18 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3519
3571
|
const edgeId = (0, import_types4.extractedEdgeId)(service.node.id, ep.infraId, edgeType);
|
|
3520
3572
|
if (seenEdges.has(edgeId)) continue;
|
|
3521
3573
|
seenEdges.add(edgeId);
|
|
3574
|
+
const confidence = (0, import_types14.confidenceForExtracted)(ep.confidenceKind);
|
|
3575
|
+
if (!(0, import_types14.passesExtractedFloor)(confidence)) {
|
|
3576
|
+
noteExtractedDropped({
|
|
3577
|
+
source: service.node.id,
|
|
3578
|
+
target: ep.infraId,
|
|
3579
|
+
type: edgeType,
|
|
3580
|
+
confidence,
|
|
3581
|
+
confidenceKind: ep.confidenceKind,
|
|
3582
|
+
evidence: ep.evidence
|
|
3583
|
+
});
|
|
3584
|
+
continue;
|
|
3585
|
+
}
|
|
3522
3586
|
if (!graph.hasEdge(edgeId)) {
|
|
3523
3587
|
const edge = {
|
|
3524
3588
|
id: edgeId,
|
|
@@ -3526,6 +3590,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3526
3590
|
target: ep.infraId,
|
|
3527
3591
|
type: edgeType,
|
|
3528
3592
|
provenance: import_types14.Provenance.EXTRACTED,
|
|
3593
|
+
confidence,
|
|
3529
3594
|
evidence: ep.evidence
|
|
3530
3595
|
};
|
|
3531
3596
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -3645,6 +3710,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
3645
3710
|
target: targetId,
|
|
3646
3711
|
type: import_types16.EdgeType.DEPENDS_ON,
|
|
3647
3712
|
provenance: import_types16.Provenance.EXTRACTED,
|
|
3713
|
+
confidence: (0, import_types16.confidenceForExtracted)("structural"),
|
|
3648
3714
|
evidence: { file: evidenceFile }
|
|
3649
3715
|
};
|
|
3650
3716
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -3705,6 +3771,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
3705
3771
|
target: node.id,
|
|
3706
3772
|
type: import_types17.EdgeType.RUNS_ON,
|
|
3707
3773
|
provenance: import_types17.Provenance.EXTRACTED,
|
|
3774
|
+
confidence: (0, import_types17.confidenceForExtracted)("structural"),
|
|
3708
3775
|
evidence: {
|
|
3709
3776
|
file: import_node_path26.default.relative(scanPath, dockerfilePath).split(import_node_path26.default.sep).join("/")
|
|
3710
3777
|
}
|
|
@@ -3821,6 +3888,9 @@ async function addInfra(graph, scanPath, services) {
|
|
|
3821
3888
|
};
|
|
3822
3889
|
}
|
|
3823
3890
|
|
|
3891
|
+
// src/extract/index.ts
|
|
3892
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
3893
|
+
|
|
3824
3894
|
// src/extract/retire.ts
|
|
3825
3895
|
init_cjs_shims();
|
|
3826
3896
|
var import_node_fs17 = require("fs");
|
|
@@ -3873,13 +3943,26 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
3873
3943
|
);
|
|
3874
3944
|
}
|
|
3875
3945
|
}
|
|
3946
|
+
const droppedEntries = drainDroppedExtracted();
|
|
3947
|
+
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
3948
|
+
const rejectedPath = import_node_path30.default.join(import_node_path30.default.dirname(opts.errorsPath), "rejected.ndjson");
|
|
3949
|
+
try {
|
|
3950
|
+
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
3951
|
+
} catch (err) {
|
|
3952
|
+
console.warn(
|
|
3953
|
+
`[neat] failed to write rejected extracted edges to ${rejectedPath}: ${err.message}`
|
|
3954
|
+
);
|
|
3955
|
+
}
|
|
3956
|
+
}
|
|
3876
3957
|
const result = {
|
|
3877
3958
|
nodesAdded: phase1Nodes + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
3878
3959
|
edgesAdded: phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
3879
3960
|
frontiersPromoted,
|
|
3880
3961
|
extractionErrors: errorEntries.length,
|
|
3881
3962
|
errorEntries,
|
|
3882
|
-
ghostsRetired
|
|
3963
|
+
ghostsRetired,
|
|
3964
|
+
extractedDropped: droppedEntries.length,
|
|
3965
|
+
droppedEntries
|
|
3883
3966
|
};
|
|
3884
3967
|
emitNeatEvent({
|
|
3885
3968
|
type: "extraction-complete",
|
|
@@ -3897,7 +3980,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
3897
3980
|
// src/persist.ts
|
|
3898
3981
|
init_cjs_shims();
|
|
3899
3982
|
var import_node_fs18 = require("fs");
|
|
3900
|
-
var
|
|
3983
|
+
var import_node_path31 = __toESM(require("path"), 1);
|
|
3901
3984
|
var SCHEMA_VERSION = 2;
|
|
3902
3985
|
function migrateV1ToV2(payload) {
|
|
3903
3986
|
const nodes = payload.graph.nodes;
|
|
@@ -3911,7 +3994,7 @@ function migrateV1ToV2(payload) {
|
|
|
3911
3994
|
return { ...payload, schemaVersion: 2 };
|
|
3912
3995
|
}
|
|
3913
3996
|
async function ensureDir(filePath) {
|
|
3914
|
-
await import_node_fs18.promises.mkdir(
|
|
3997
|
+
await import_node_fs18.promises.mkdir(import_node_path31.default.dirname(filePath), { recursive: true });
|
|
3915
3998
|
}
|
|
3916
3999
|
async function saveGraphToDisk(graph, outPath) {
|
|
3917
4000
|
await ensureDir(outPath);
|
|
@@ -3980,23 +4063,23 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
|
|
|
3980
4063
|
|
|
3981
4064
|
// src/projects.ts
|
|
3982
4065
|
init_cjs_shims();
|
|
3983
|
-
var
|
|
4066
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
3984
4067
|
function pathsForProject(project, baseDir) {
|
|
3985
4068
|
if (project === DEFAULT_PROJECT) {
|
|
3986
4069
|
return {
|
|
3987
|
-
snapshotPath:
|
|
3988
|
-
errorsPath:
|
|
3989
|
-
staleEventsPath:
|
|
3990
|
-
embeddingsCachePath:
|
|
3991
|
-
policyViolationsPath:
|
|
4070
|
+
snapshotPath: import_node_path32.default.join(baseDir, "graph.json"),
|
|
4071
|
+
errorsPath: import_node_path32.default.join(baseDir, "errors.ndjson"),
|
|
4072
|
+
staleEventsPath: import_node_path32.default.join(baseDir, "stale-events.ndjson"),
|
|
4073
|
+
embeddingsCachePath: import_node_path32.default.join(baseDir, "embeddings.json"),
|
|
4074
|
+
policyViolationsPath: import_node_path32.default.join(baseDir, "policy-violations.ndjson")
|
|
3992
4075
|
};
|
|
3993
4076
|
}
|
|
3994
4077
|
return {
|
|
3995
|
-
snapshotPath:
|
|
3996
|
-
errorsPath:
|
|
3997
|
-
staleEventsPath:
|
|
3998
|
-
embeddingsCachePath:
|
|
3999
|
-
policyViolationsPath:
|
|
4078
|
+
snapshotPath: import_node_path32.default.join(baseDir, `${project}.json`),
|
|
4079
|
+
errorsPath: import_node_path32.default.join(baseDir, `errors.${project}.ndjson`),
|
|
4080
|
+
staleEventsPath: import_node_path32.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
4081
|
+
embeddingsCachePath: import_node_path32.default.join(baseDir, `embeddings.${project}.json`),
|
|
4082
|
+
policyViolationsPath: import_node_path32.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
4000
4083
|
};
|
|
4001
4084
|
}
|
|
4002
4085
|
var Projects = class {
|
|
@@ -4075,14 +4158,6 @@ function nodeIsFrontier(graph, nodeId) {
|
|
|
4075
4158
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
4076
4159
|
return attrs.type === import_types19.NodeType.FrontierNode;
|
|
4077
4160
|
}
|
|
4078
|
-
function hasAnyObservedFromSource(graph, sourceId) {
|
|
4079
|
-
if (!graph.hasNode(sourceId)) return false;
|
|
4080
|
-
for (const edgeId of graph.outboundEdges(sourceId)) {
|
|
4081
|
-
const e = graph.getEdgeAttributes(edgeId);
|
|
4082
|
-
if (e.provenance === import_types19.Provenance.OBSERVED) return true;
|
|
4083
|
-
}
|
|
4084
|
-
return false;
|
|
4085
|
-
}
|
|
4086
4161
|
function clampConfidence(n) {
|
|
4087
4162
|
if (!Number.isFinite(n)) return 0;
|
|
4088
4163
|
return Math.max(0, Math.min(1, n));
|
|
@@ -4096,32 +4171,34 @@ function reasonForMissingExtracted(source, target, type) {
|
|
|
4096
4171
|
var RECOMMENDATION_MISSING_OBSERVED = "Verify the code path is exercised in production; check feature flags or conditional branches that might gate the call.";
|
|
4097
4172
|
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.";
|
|
4098
4173
|
var RECOMMENDATION_HOST_MISMATCH = "Check environment-specific config overrides \u2014 the runtime host differs from what static configuration declares.";
|
|
4174
|
+
function gradedConfidence(edge) {
|
|
4175
|
+
if (typeof edge.confidence === "number") return clampConfidence(edge.confidence);
|
|
4176
|
+
return clampConfidence(confidenceForEdge(edge));
|
|
4177
|
+
}
|
|
4099
4178
|
function detectMissingDivergences(graph, bucket) {
|
|
4100
4179
|
const out = [];
|
|
4101
4180
|
if (bucket.extracted && !bucket.observed) {
|
|
4102
4181
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
4103
|
-
const sourceHasTraffic = hasAnyObservedFromSource(graph, bucket.source);
|
|
4104
4182
|
out.push({
|
|
4105
4183
|
type: "missing-observed",
|
|
4106
4184
|
source: bucket.source,
|
|
4107
4185
|
target: bucket.target,
|
|
4108
4186
|
edgeType: bucket.type,
|
|
4109
4187
|
extracted: bucket.extracted,
|
|
4110
|
-
confidence:
|
|
4188
|
+
confidence: gradedConfidence(bucket.extracted),
|
|
4111
4189
|
reason: reasonForMissingObserved(bucket.source, bucket.target, bucket.type),
|
|
4112
4190
|
recommendation: RECOMMENDATION_MISSING_OBSERVED
|
|
4113
4191
|
});
|
|
4114
4192
|
}
|
|
4115
4193
|
}
|
|
4116
4194
|
if (bucket.observed && !bucket.extracted) {
|
|
4117
|
-
const cascaded = clampConfidence(confidenceForEdge(bucket.observed));
|
|
4118
4195
|
out.push({
|
|
4119
4196
|
type: "missing-extracted",
|
|
4120
4197
|
source: bucket.source,
|
|
4121
4198
|
target: bucket.target,
|
|
4122
4199
|
edgeType: bucket.type,
|
|
4123
4200
|
observed: bucket.observed,
|
|
4124
|
-
confidence:
|
|
4201
|
+
confidence: gradedConfidence(bucket.observed),
|
|
4125
4202
|
reason: reasonForMissingExtracted(bucket.source, bucket.target, bucket.type),
|
|
4126
4203
|
recommendation: RECOMMENDATION_MISSING_EXTRACTED
|
|
4127
4204
|
});
|
|
@@ -4260,8 +4337,17 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4260
4337
|
const target = opts.node;
|
|
4261
4338
|
filtered = filtered.filter((d) => involvesNode(d, target));
|
|
4262
4339
|
}
|
|
4340
|
+
const TYPE_LEADERSHIP = {
|
|
4341
|
+
"missing-extracted": 0,
|
|
4342
|
+
"missing-observed": 1,
|
|
4343
|
+
"version-mismatch": 2,
|
|
4344
|
+
"host-mismatch": 3,
|
|
4345
|
+
"compat-violation": 4
|
|
4346
|
+
};
|
|
4263
4347
|
filtered.sort((a, b) => {
|
|
4264
4348
|
if (b.confidence !== a.confidence) return b.confidence - a.confidence;
|
|
4349
|
+
const lead = TYPE_LEADERSHIP[a.type] - TYPE_LEADERSHIP[b.type];
|
|
4350
|
+
if (lead !== 0) return lead;
|
|
4265
4351
|
if (a.type !== b.type) return a.type.localeCompare(b.type);
|
|
4266
4352
|
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
4267
4353
|
return a.target.localeCompare(b.target);
|
|
@@ -4354,23 +4440,23 @@ function canonicalJson(value) {
|
|
|
4354
4440
|
init_cjs_shims();
|
|
4355
4441
|
var import_node_fs20 = require("fs");
|
|
4356
4442
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
4357
|
-
var
|
|
4443
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
4358
4444
|
var import_types20 = require("@neat.is/types");
|
|
4359
4445
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
4360
4446
|
var LOCK_RETRY_MS = 50;
|
|
4361
4447
|
function neatHome() {
|
|
4362
4448
|
const override = process.env.NEAT_HOME;
|
|
4363
|
-
if (override && override.length > 0) return
|
|
4364
|
-
return
|
|
4449
|
+
if (override && override.length > 0) return import_node_path33.default.resolve(override);
|
|
4450
|
+
return import_node_path33.default.join(import_node_os2.default.homedir(), ".neat");
|
|
4365
4451
|
}
|
|
4366
4452
|
function registryPath() {
|
|
4367
|
-
return
|
|
4453
|
+
return import_node_path33.default.join(neatHome(), "projects.json");
|
|
4368
4454
|
}
|
|
4369
4455
|
function registryLockPath() {
|
|
4370
|
-
return
|
|
4456
|
+
return import_node_path33.default.join(neatHome(), "projects.json.lock");
|
|
4371
4457
|
}
|
|
4372
4458
|
async function writeAtomically(target, contents) {
|
|
4373
|
-
await import_node_fs20.promises.mkdir(
|
|
4459
|
+
await import_node_fs20.promises.mkdir(import_node_path33.default.dirname(target), { recursive: true });
|
|
4374
4460
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
4375
4461
|
const fd = await import_node_fs20.promises.open(tmp, "w");
|
|
4376
4462
|
try {
|
|
@@ -4383,7 +4469,7 @@ async function writeAtomically(target, contents) {
|
|
|
4383
4469
|
}
|
|
4384
4470
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS) {
|
|
4385
4471
|
const deadline = Date.now() + timeoutMs;
|
|
4386
|
-
await import_node_fs20.promises.mkdir(
|
|
4472
|
+
await import_node_fs20.promises.mkdir(import_node_path33.default.dirname(lockPath), { recursive: true });
|
|
4387
4473
|
while (true) {
|
|
4388
4474
|
try {
|
|
4389
4475
|
const fd = await import_node_fs20.promises.open(lockPath, "wx");
|
|
@@ -4945,11 +5031,11 @@ async function buildApi(opts) {
|
|
|
4945
5031
|
// src/daemon.ts
|
|
4946
5032
|
init_otel();
|
|
4947
5033
|
function neatHomeFor(opts) {
|
|
4948
|
-
if (opts.neatHome && opts.neatHome.length > 0) return
|
|
5034
|
+
if (opts.neatHome && opts.neatHome.length > 0) return import_node_path36.default.resolve(opts.neatHome);
|
|
4949
5035
|
const env = process.env.NEAT_HOME;
|
|
4950
|
-
if (env && env.length > 0) return
|
|
5036
|
+
if (env && env.length > 0) return import_node_path36.default.resolve(env);
|
|
4951
5037
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
4952
|
-
return
|
|
5038
|
+
return import_node_path36.default.join(home, ".neat");
|
|
4953
5039
|
}
|
|
4954
5040
|
function routeSpanToProject(serviceName, projects) {
|
|
4955
5041
|
if (!serviceName) return DEFAULT_PROJECT;
|
|
@@ -4962,7 +5048,7 @@ function routeSpanToProject(serviceName, projects) {
|
|
|
4962
5048
|
return DEFAULT_PROJECT;
|
|
4963
5049
|
}
|
|
4964
5050
|
async function bootstrapProject(entry2) {
|
|
4965
|
-
const paths = pathsForProject(entry2.name,
|
|
5051
|
+
const paths = pathsForProject(entry2.name, import_node_path36.default.join(entry2.path, "neat-out"));
|
|
4966
5052
|
try {
|
|
4967
5053
|
const stat = await import_node_fs21.promises.stat(entry2.path);
|
|
4968
5054
|
if (!stat.isDirectory()) {
|
|
@@ -5035,7 +5121,7 @@ async function startDaemon(opts = {}) {
|
|
|
5035
5121
|
`neatd: registry not found at ${regPath}. Run \`neat init <path>\` to register a project before starting the daemon.`
|
|
5036
5122
|
);
|
|
5037
5123
|
}
|
|
5038
|
-
const pidPath =
|
|
5124
|
+
const pidPath = import_node_path36.default.join(home, "neatd.pid");
|
|
5039
5125
|
await writeAtomically(pidPath, `${process.pid}
|
|
5040
5126
|
`);
|
|
5041
5127
|
const slots = /* @__PURE__ */ new Map();
|
|
@@ -5199,7 +5285,7 @@ async function startDaemon(opts = {}) {
|
|
|
5199
5285
|
init_cjs_shims();
|
|
5200
5286
|
var import_node_child_process = require("child_process");
|
|
5201
5287
|
var import_node_net = __toESM(require("net"), 1);
|
|
5202
|
-
var
|
|
5288
|
+
var import_node_path37 = __toESM(require("path"), 1);
|
|
5203
5289
|
var DEFAULT_WEB_PORT = 6328;
|
|
5204
5290
|
async function assertPortFree(port) {
|
|
5205
5291
|
await new Promise((resolve, reject) => {
|
|
@@ -5227,10 +5313,10 @@ function resolveWebPackageDir() {
|
|
|
5227
5313
|
eval("require")
|
|
5228
5314
|
);
|
|
5229
5315
|
const pkgJsonPath = req.resolve("@neat.is/web/package.json");
|
|
5230
|
-
return
|
|
5316
|
+
return import_node_path37.default.dirname(pkgJsonPath);
|
|
5231
5317
|
}
|
|
5232
5318
|
function resolveStandaloneServerEntry(webDir) {
|
|
5233
|
-
return
|
|
5319
|
+
return import_node_path37.default.join(webDir, ".next/standalone/packages/web/server.js");
|
|
5234
5320
|
}
|
|
5235
5321
|
async function spawnWebUI(restPort) {
|
|
5236
5322
|
const portRaw = process.env.NEAT_WEB_PORT;
|
|
@@ -5255,7 +5341,7 @@ async function spawnWebUI(restPort) {
|
|
|
5255
5341
|
NEAT_API_URL: process.env.NEAT_API_URL ?? `http://localhost:${restPort}`
|
|
5256
5342
|
};
|
|
5257
5343
|
const child = (0, import_node_child_process.spawn)(process.execPath, [serverEntry], {
|
|
5258
|
-
cwd:
|
|
5344
|
+
cwd: import_node_path37.default.dirname(serverEntry),
|
|
5259
5345
|
env,
|
|
5260
5346
|
stdio: ["ignore", "inherit", "inherit"],
|
|
5261
5347
|
detached: false
|
|
@@ -5292,14 +5378,14 @@ async function spawnWebUI(restPort) {
|
|
|
5292
5378
|
// src/neatd.ts
|
|
5293
5379
|
function neatHome2() {
|
|
5294
5380
|
if (process.env.NEAT_HOME && process.env.NEAT_HOME.length > 0) {
|
|
5295
|
-
return
|
|
5381
|
+
return import_node_path38.default.resolve(process.env.NEAT_HOME);
|
|
5296
5382
|
}
|
|
5297
5383
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
5298
|
-
return
|
|
5384
|
+
return import_node_path38.default.join(home, ".neat");
|
|
5299
5385
|
}
|
|
5300
5386
|
async function readPid() {
|
|
5301
5387
|
try {
|
|
5302
|
-
const raw = await import_node_fs22.promises.readFile(
|
|
5388
|
+
const raw = await import_node_fs22.promises.readFile(import_node_path38.default.join(neatHome2(), "neatd.pid"), "utf8");
|
|
5303
5389
|
const n = Number.parseInt(raw.trim(), 10);
|
|
5304
5390
|
return Number.isFinite(n) ? n : null;
|
|
5305
5391
|
} catch {
|