@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/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-ICFH326H.js";
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-VABXPLDT.js";
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 = import_node_path33.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
104
- return import_node_path33.default.resolve(here, "..", "proto");
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, import_node_path33, grpc, protoLoader;
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
- import_node_path33 = __toESM(require("path"), 1);
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 = import_node_path34.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
263
- const protoRoot = import_node_path34.default.resolve(here, "..", "proto");
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) => import_node_path34.default.resolve(protoRoot, 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 import_node_path34, import_node_url2, import_fastify2, import_protobufjs, exportTraceServiceRequestType;
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
- import_node_path34 = __toESM(require("path"), 1);
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 import_node_path37 = __toESM(require("path"), 1);
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 import_node_path35 = __toESM(require("path"), 1);
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, path38, edges) {
835
- if (path38.length > best.path.length) {
836
- best = { path: [...path38], edges: [...edges] };
834
+ function step(node, path39, edges) {
835
+ if (path39.length > best.path.length) {
836
+ best = { path: [...path39], edges: [...edges] };
837
837
  }
838
- if (path38.length - 1 >= maxDepth) return;
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
- path38.push(srcId);
843
+ path39.push(srcId);
844
844
  edges.push(edge);
845
- step(srcId, path38, edges);
846
- path38.pop();
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
- spanCount: newSpanCount,
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: 1,
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: (0, import_types4.extractedEdgeId)(service.node.id, targetId, import_types9.EdgeType.CALLS),
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
- evidence: {
3268
- file: import_node_path20.default.relative(service.dir, evidenceFile.file),
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 import_node_path30 = __toESM(require("path"), 1);
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(import_node_path30.default.dirname(filePath), { recursive: true });
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 import_node_path31 = __toESM(require("path"), 1);
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: import_node_path31.default.join(baseDir, "graph.json"),
3988
- errorsPath: import_node_path31.default.join(baseDir, "errors.ndjson"),
3989
- staleEventsPath: import_node_path31.default.join(baseDir, "stale-events.ndjson"),
3990
- embeddingsCachePath: import_node_path31.default.join(baseDir, "embeddings.json"),
3991
- policyViolationsPath: import_node_path31.default.join(baseDir, "policy-violations.ndjson")
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: import_node_path31.default.join(baseDir, `${project}.json`),
3996
- errorsPath: import_node_path31.default.join(baseDir, `errors.${project}.ndjson`),
3997
- staleEventsPath: import_node_path31.default.join(baseDir, `stale-events.${project}.ndjson`),
3998
- embeddingsCachePath: import_node_path31.default.join(baseDir, `embeddings.${project}.json`),
3999
- policyViolationsPath: import_node_path31.default.join(baseDir, `policy-violations.${project}.ndjson`)
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: sourceHasTraffic ? 1 : 0.5,
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: cascaded,
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 import_node_path32 = __toESM(require("path"), 1);
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 import_node_path32.default.resolve(override);
4364
- return import_node_path32.default.join(import_node_os2.default.homedir(), ".neat");
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 import_node_path32.default.join(neatHome(), "projects.json");
4453
+ return import_node_path33.default.join(neatHome(), "projects.json");
4368
4454
  }
4369
4455
  function registryLockPath() {
4370
- return import_node_path32.default.join(neatHome(), "projects.json.lock");
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(import_node_path32.default.dirname(target), { recursive: true });
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(import_node_path32.default.dirname(lockPath), { recursive: true });
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 import_node_path35.default.resolve(opts.neatHome);
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 import_node_path35.default.resolve(env);
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 import_node_path35.default.join(home, ".neat");
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, import_node_path35.default.join(entry2.path, "neat-out"));
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 = import_node_path35.default.join(home, "neatd.pid");
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 import_node_path36 = __toESM(require("path"), 1);
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 import_node_path36.default.dirname(pkgJsonPath);
5316
+ return import_node_path37.default.dirname(pkgJsonPath);
5231
5317
  }
5232
5318
  function resolveStandaloneServerEntry(webDir) {
5233
- return import_node_path36.default.join(webDir, ".next/standalone/packages/web/server.js");
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: import_node_path36.default.dirname(serverEntry),
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 import_node_path37.default.resolve(process.env.NEAT_HOME);
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 import_node_path37.default.join(home, ".neat");
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(import_node_path37.default.join(neatHome2(), "neatd.pid"), "utf8");
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 {