@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.
@@ -427,19 +427,19 @@ function confidenceFromMix(edges, now = Date.now()) {
427
427
  function longestIncomingWalk(graph, start, maxDepth) {
428
428
  let best = { path: [start], edges: [] };
429
429
  const visited = /* @__PURE__ */ new Set([start]);
430
- function step(node, path33, edges) {
431
- if (path33.length > best.path.length) {
432
- best = { path: [...path33], edges: [...edges] };
430
+ function step(node, path34, edges) {
431
+ if (path34.length > best.path.length) {
432
+ best = { path: [...path34], edges: [...edges] };
433
433
  }
434
- if (path33.length - 1 >= maxDepth) return;
434
+ if (path34.length - 1 >= maxDepth) return;
435
435
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
436
436
  for (const [srcId, edge] of incoming) {
437
437
  if (visited.has(srcId)) continue;
438
438
  visited.add(srcId);
439
- path33.push(srcId);
439
+ path34.push(srcId);
440
440
  edges.push(edge);
441
- step(srcId, path33, edges);
442
- path33.pop();
441
+ step(srcId, path34, edges);
442
+ path34.pop();
443
443
  edges.pop();
444
444
  visited.delete(srcId);
445
445
  }
@@ -1018,6 +1018,7 @@ import {
1018
1018
  EdgeType as EdgeType2,
1019
1019
  NodeType as NodeType3,
1020
1020
  Provenance as Provenance3,
1021
+ confidenceForObservedSignal,
1021
1022
  databaseId,
1022
1023
  extractedEdgeId,
1023
1024
  frontierEdgeId,
@@ -1214,35 +1215,37 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
1214
1215
  const existing = graph.getEdgeAttributes(id);
1215
1216
  const newSpanCount = (existing.signal?.spanCount ?? existing.callCount ?? 0) + 1;
1216
1217
  const newErrorCount = (existing.signal?.errorCount ?? 0) + (isError ? 1 : 0);
1218
+ const newSignal = {
1219
+ spanCount: newSpanCount,
1220
+ errorCount: newErrorCount,
1221
+ lastObservedAgeMs: 0
1222
+ };
1217
1223
  const updated = {
1218
1224
  ...existing,
1219
1225
  provenance: Provenance3.OBSERVED,
1220
1226
  lastObserved: ts,
1221
1227
  callCount: newSpanCount,
1222
- signal: {
1223
- spanCount: newSpanCount,
1224
- errorCount: newErrorCount,
1225
- lastObservedAgeMs: 0
1226
- },
1227
- confidence: 1
1228
+ signal: newSignal,
1229
+ confidence: confidenceForObservedSignal(newSignal)
1228
1230
  };
1229
1231
  graph.replaceEdgeAttributes(id, updated);
1230
1232
  return { edge: updated, created: false };
1231
1233
  }
1234
+ const signal = {
1235
+ spanCount: 1,
1236
+ errorCount: isError ? 1 : 0,
1237
+ lastObservedAgeMs: 0
1238
+ };
1232
1239
  const edge = {
1233
1240
  id,
1234
1241
  source,
1235
1242
  target,
1236
1243
  type,
1237
1244
  provenance: Provenance3.OBSERVED,
1238
- confidence: 1,
1245
+ confidence: confidenceForObservedSignal(signal),
1239
1246
  lastObserved: ts,
1240
1247
  callCount: 1,
1241
- signal: {
1242
- spanCount: 1,
1243
- errorCount: isError ? 1 : 0,
1244
- lastObservedAgeMs: 0
1245
- }
1248
+ signal
1246
1249
  };
1247
1250
  graph.addEdgeWithKey(id, source, target, edge);
1248
1251
  return { edge, created: true };
@@ -1593,6 +1596,27 @@ function formatExtractionBanner(count) {
1593
1596
  if (count === 1) return `[neat] 1 file skipped due to parse errors`;
1594
1597
  return `[neat] ${count} files skipped due to parse errors`;
1595
1598
  }
1599
+ var droppedSink = [];
1600
+ function noteExtractedDropped(edge) {
1601
+ droppedSink.push(edge);
1602
+ }
1603
+ function drainDroppedExtracted() {
1604
+ return droppedSink.splice(0, droppedSink.length);
1605
+ }
1606
+ function isRejectedLogEnabled() {
1607
+ const raw = process.env.NEAT_EXTRACTED_REJECTED_LOG;
1608
+ return raw === "1" || raw === "true";
1609
+ }
1610
+ async function writeRejectedExtracted(drops, rejectedPath) {
1611
+ if (drops.length === 0) return;
1612
+ await fs4.mkdir(path4.dirname(rejectedPath), { recursive: true });
1613
+ const lines = drops.map((d) => JSON.stringify({ ...d, ts: (/* @__PURE__ */ new Date()).toISOString() })).join("\n") + "\n";
1614
+ await fs4.appendFile(rejectedPath, lines, "utf8");
1615
+ }
1616
+ function formatPrecisionFloorBanner(count) {
1617
+ if (count === 1) return `[neat] 1 extracted edge dropped below precision floor`;
1618
+ return `[neat] ${count} extracted edges dropped below precision floor`;
1619
+ }
1596
1620
 
1597
1621
  // src/extract/services.ts
1598
1622
  import { promises as fs8 } from "fs";
@@ -2223,7 +2247,13 @@ async function addServiceAliases(graph, scanPath, services) {
2223
2247
 
2224
2248
  // src/extract/databases/index.ts
2225
2249
  import path17 from "path";
2226
- import { EdgeType as EdgeType3, NodeType as NodeType6, Provenance as Provenance4, databaseId as databaseId2 } from "@neat.is/types";
2250
+ import {
2251
+ EdgeType as EdgeType3,
2252
+ NodeType as NodeType6,
2253
+ Provenance as Provenance4,
2254
+ databaseId as databaseId2,
2255
+ confidenceForExtracted
2256
+ } from "@neat.is/types";
2227
2257
 
2228
2258
  // src/extract/databases/db-config-yaml.ts
2229
2259
  import path10 from "path";
@@ -2798,6 +2828,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
2798
2828
  target: dbNode.id,
2799
2829
  type: EdgeType3.CONNECTS_TO,
2800
2830
  provenance: Provenance4.EXTRACTED,
2831
+ confidence: confidenceForExtracted("structural"),
2801
2832
  // ADR-032 / #140 — every EXTRACTED edge carries evidence.file.
2802
2833
  // Ghost-edge cleanup keys retirement on this; the conditional
2803
2834
  // sourceFile spread that used to live here was a v0.1.x leftover.
@@ -2830,7 +2861,13 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
2830
2861
  // src/extract/configs.ts
2831
2862
  import { promises as fs12 } from "fs";
2832
2863
  import path18 from "path";
2833
- import { EdgeType as EdgeType4, NodeType as NodeType7, Provenance as Provenance5, configId } from "@neat.is/types";
2864
+ import {
2865
+ EdgeType as EdgeType4,
2866
+ NodeType as NodeType7,
2867
+ Provenance as Provenance5,
2868
+ configId,
2869
+ confidenceForExtracted as confidenceForExtracted2
2870
+ } from "@neat.is/types";
2834
2871
  async function walkConfigFiles(dir) {
2835
2872
  const out = [];
2836
2873
  async function walk(current) {
@@ -2871,6 +2908,7 @@ async function addConfigNodes(graph, services, scanPath) {
2871
2908
  target: node.id,
2872
2909
  type: EdgeType4.CONFIGURED_BY,
2873
2910
  provenance: Provenance5.EXTRACTED,
2911
+ confidence: confidenceForExtracted2("structural"),
2874
2912
  evidence: { file: relPath.split(path18.sep).join("/") }
2875
2913
  };
2876
2914
  if (!graph.hasEdge(edge.id)) {
@@ -2883,14 +2921,25 @@ async function addConfigNodes(graph, services, scanPath) {
2883
2921
  }
2884
2922
 
2885
2923
  // src/extract/calls/index.ts
2886
- import { EdgeType as EdgeType6, NodeType as NodeType8, Provenance as Provenance7 } from "@neat.is/types";
2924
+ import {
2925
+ EdgeType as EdgeType6,
2926
+ NodeType as NodeType8,
2927
+ Provenance as Provenance7,
2928
+ confidenceForExtracted as confidenceForExtracted4,
2929
+ passesExtractedFloor as passesExtractedFloor2
2930
+ } from "@neat.is/types";
2887
2931
 
2888
2932
  // src/extract/calls/http.ts
2889
2933
  import path20 from "path";
2890
2934
  import Parser from "tree-sitter";
2891
2935
  import JavaScript from "tree-sitter-javascript";
2892
2936
  import Python from "tree-sitter-python";
2893
- import { EdgeType as EdgeType5, Provenance as Provenance6 } from "@neat.is/types";
2937
+ import {
2938
+ EdgeType as EdgeType5,
2939
+ Provenance as Provenance6,
2940
+ confidenceForExtracted as confidenceForExtracted3,
2941
+ passesExtractedFloor
2942
+ } from "@neat.is/types";
2894
2943
 
2895
2944
  // src/extract/calls/shared.ts
2896
2945
  import { promises as fs13 } from "fs";
@@ -3022,17 +3071,32 @@ async function addHttpCallEdges(graph, services) {
3022
3071
  for (const [targetId, evidenceFile] of seenTargets) {
3023
3072
  const fileContent = files.find((f) => f.path === evidenceFile.file)?.content ?? "";
3024
3073
  const line = lineOf(fileContent, `//${evidenceFile.host}`);
3074
+ const confidence = confidenceForExtracted3("hostname-shape-match");
3075
+ const ev = {
3076
+ file: path20.relative(service.dir, evidenceFile.file),
3077
+ line,
3078
+ snippet: snippet(fileContent, line)
3079
+ };
3080
+ const edgeId = extractedEdgeId2(service.node.id, targetId, EdgeType5.CALLS);
3081
+ if (!passesExtractedFloor(confidence)) {
3082
+ noteExtractedDropped({
3083
+ source: service.node.id,
3084
+ target: targetId,
3085
+ type: EdgeType5.CALLS,
3086
+ confidence,
3087
+ confidenceKind: "hostname-shape-match",
3088
+ evidence: ev
3089
+ });
3090
+ continue;
3091
+ }
3025
3092
  const edge = {
3026
- id: extractedEdgeId2(service.node.id, targetId, EdgeType5.CALLS),
3093
+ id: edgeId,
3027
3094
  source: service.node.id,
3028
3095
  target: targetId,
3029
3096
  type: EdgeType5.CALLS,
3030
3097
  provenance: Provenance6.EXTRACTED,
3031
- evidence: {
3032
- file: path20.relative(service.dir, evidenceFile.file),
3033
- line,
3034
- snippet: snippet(fileContent, line)
3035
- }
3098
+ confidence,
3099
+ evidence: ev
3036
3100
  };
3037
3101
  if (!graph.hasEdge(edge.id)) {
3038
3102
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -3070,6 +3134,10 @@ function kafkaEndpointsFromFile(file, serviceDir) {
3070
3134
  name: topic,
3071
3135
  kind: "kafka-topic",
3072
3136
  edgeType,
3137
+ // `producer.send({topic: 'x'})` / `consumer.subscribe({topic: 'x'})` —
3138
+ // framework-aware (kafkajs / node-rdkafka shape). Verified-call-site
3139
+ // tier (ADR-066).
3140
+ confidenceKind: "verified-call-site",
3073
3141
  evidence: {
3074
3142
  file: path21.relative(serviceDir, file.path),
3075
3143
  line,
@@ -3101,6 +3169,10 @@ function redisEndpointsFromFile(file, serviceDir) {
3101
3169
  name: host,
3102
3170
  kind: "redis",
3103
3171
  edgeType: "CALLS",
3172
+ // `redis://host` URL literal — the scheme is structural support, but no
3173
+ // call expression is verified to wire it through. URL-with-structural-
3174
+ // support tier (ADR-066).
3175
+ confidenceKind: "url-with-structural-support",
3104
3176
  evidence: {
3105
3177
  file: path22.relative(serviceDir, file.path),
3106
3178
  line,
@@ -3141,6 +3213,10 @@ function awsEndpointsFromFile(file, serviceDir) {
3141
3213
  name,
3142
3214
  kind,
3143
3215
  edgeType: "CALLS",
3216
+ // SDK marker (S3Client, GetCommand, etc.) plus a Bucket/TableName
3217
+ // literal — framework-aware recognizer, verified-call-site tier
3218
+ // (ADR-066).
3219
+ confidenceKind: "verified-call-site",
3144
3220
  evidence: {
3145
3221
  file: path23.relative(serviceDir, file.path),
3146
3222
  line,
@@ -3219,6 +3295,10 @@ function grpcEndpointsFromFile(file, serviceDir) {
3219
3295
  name,
3220
3296
  kind,
3221
3297
  edgeType: "CALLS",
3298
+ // `new <Name>Client(...)` with @aws-sdk/* or @grpc/grpc-js import
3299
+ // context — import-aware classification per #238. Verified-call-site
3300
+ // tier (ADR-066).
3301
+ confidenceKind: "verified-call-site",
3222
3302
  evidence: {
3223
3303
  file: path24.relative(serviceDir, file.path),
3224
3304
  line,
@@ -3279,6 +3359,18 @@ async function addExternalEndpointEdges(graph, services) {
3279
3359
  const edgeId = extractedEdgeId2(service.node.id, ep.infraId, edgeType);
3280
3360
  if (seenEdges.has(edgeId)) continue;
3281
3361
  seenEdges.add(edgeId);
3362
+ const confidence = confidenceForExtracted4(ep.confidenceKind);
3363
+ if (!passesExtractedFloor2(confidence)) {
3364
+ noteExtractedDropped({
3365
+ source: service.node.id,
3366
+ target: ep.infraId,
3367
+ type: edgeType,
3368
+ confidence,
3369
+ confidenceKind: ep.confidenceKind,
3370
+ evidence: ep.evidence
3371
+ });
3372
+ continue;
3373
+ }
3282
3374
  if (!graph.hasEdge(edgeId)) {
3283
3375
  const edge = {
3284
3376
  id: edgeId,
@@ -3286,6 +3378,7 @@ async function addExternalEndpointEdges(graph, services) {
3286
3378
  target: ep.infraId,
3287
3379
  type: edgeType,
3288
3380
  provenance: Provenance7.EXTRACTED,
3381
+ confidence,
3289
3382
  evidence: ep.evidence
3290
3383
  };
3291
3384
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -3306,7 +3399,7 @@ async function addCallEdges(graph, services) {
3306
3399
 
3307
3400
  // src/extract/infra/docker-compose.ts
3308
3401
  import path25 from "path";
3309
- import { EdgeType as EdgeType7, Provenance as Provenance8 } from "@neat.is/types";
3402
+ import { EdgeType as EdgeType7, Provenance as Provenance8, confidenceForExtracted as confidenceForExtracted5 } from "@neat.is/types";
3310
3403
 
3311
3404
  // src/extract/infra/shared.ts
3312
3405
  import { NodeType as NodeType9, infraId as infraId5 } from "@neat.is/types";
@@ -3400,6 +3493,7 @@ async function addComposeInfra(graph, scanPath, services) {
3400
3493
  target: targetId,
3401
3494
  type: EdgeType7.DEPENDS_ON,
3402
3495
  provenance: Provenance8.EXTRACTED,
3496
+ confidence: confidenceForExtracted5("structural"),
3403
3497
  evidence: { file: evidenceFile }
3404
3498
  };
3405
3499
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -3412,7 +3506,7 @@ async function addComposeInfra(graph, scanPath, services) {
3412
3506
  // src/extract/infra/dockerfile.ts
3413
3507
  import path26 from "path";
3414
3508
  import { promises as fs14 } from "fs";
3415
- import { EdgeType as EdgeType8, Provenance as Provenance9 } from "@neat.is/types";
3509
+ import { EdgeType as EdgeType8, Provenance as Provenance9, confidenceForExtracted as confidenceForExtracted6 } from "@neat.is/types";
3416
3510
  function runtimeImage(content) {
3417
3511
  const lines = content.split("\n");
3418
3512
  let last = null;
@@ -3459,6 +3553,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
3459
3553
  target: node.id,
3460
3554
  type: EdgeType8.RUNS_ON,
3461
3555
  provenance: Provenance9.EXTRACTED,
3556
+ confidence: confidenceForExtracted6("structural"),
3462
3557
  evidence: {
3463
3558
  file: path26.relative(scanPath, dockerfilePath).split(path26.sep).join("/")
3464
3559
  }
@@ -3573,6 +3668,9 @@ async function addInfra(graph, scanPath, services) {
3573
3668
  };
3574
3669
  }
3575
3670
 
3671
+ // src/extract/index.ts
3672
+ import path30 from "path";
3673
+
3576
3674
  // src/extract/retire.ts
3577
3675
  import { existsSync } from "fs";
3578
3676
  import path29 from "path";
@@ -3636,13 +3734,26 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
3636
3734
  );
3637
3735
  }
3638
3736
  }
3737
+ const droppedEntries = drainDroppedExtracted();
3738
+ if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
3739
+ const rejectedPath = path30.join(path30.dirname(opts.errorsPath), "rejected.ndjson");
3740
+ try {
3741
+ await writeRejectedExtracted(droppedEntries, rejectedPath);
3742
+ } catch (err) {
3743
+ console.warn(
3744
+ `[neat] failed to write rejected extracted edges to ${rejectedPath}: ${err.message}`
3745
+ );
3746
+ }
3747
+ }
3639
3748
  const result = {
3640
3749
  nodesAdded: phase1Nodes + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
3641
3750
  edgesAdded: phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
3642
3751
  frontiersPromoted,
3643
3752
  extractionErrors: errorEntries.length,
3644
3753
  errorEntries,
3645
- ghostsRetired
3754
+ ghostsRetired,
3755
+ extractedDropped: droppedEntries.length,
3756
+ droppedEntries
3646
3757
  };
3647
3758
  emitNeatEvent({
3648
3759
  type: "extraction-complete",
@@ -3659,7 +3770,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
3659
3770
 
3660
3771
  // src/persist.ts
3661
3772
  import { promises as fs17 } from "fs";
3662
- import path30 from "path";
3773
+ import path31 from "path";
3663
3774
  var SCHEMA_VERSION = 2;
3664
3775
  function migrateV1ToV2(payload) {
3665
3776
  const nodes = payload.graph.nodes;
@@ -3673,7 +3784,7 @@ function migrateV1ToV2(payload) {
3673
3784
  return { ...payload, schemaVersion: 2 };
3674
3785
  }
3675
3786
  async function ensureDir(filePath) {
3676
- await fs17.mkdir(path30.dirname(filePath), { recursive: true });
3787
+ await fs17.mkdir(path31.dirname(filePath), { recursive: true });
3677
3788
  }
3678
3789
  async function saveGraphToDisk(graph, outPath) {
3679
3790
  await ensureDir(outPath);
@@ -3817,23 +3928,23 @@ function canonicalJson(value) {
3817
3928
  }
3818
3929
 
3819
3930
  // src/projects.ts
3820
- import path31 from "path";
3931
+ import path32 from "path";
3821
3932
  function pathsForProject(project, baseDir) {
3822
3933
  if (project === DEFAULT_PROJECT) {
3823
3934
  return {
3824
- snapshotPath: path31.join(baseDir, "graph.json"),
3825
- errorsPath: path31.join(baseDir, "errors.ndjson"),
3826
- staleEventsPath: path31.join(baseDir, "stale-events.ndjson"),
3827
- embeddingsCachePath: path31.join(baseDir, "embeddings.json"),
3828
- policyViolationsPath: path31.join(baseDir, "policy-violations.ndjson")
3935
+ snapshotPath: path32.join(baseDir, "graph.json"),
3936
+ errorsPath: path32.join(baseDir, "errors.ndjson"),
3937
+ staleEventsPath: path32.join(baseDir, "stale-events.ndjson"),
3938
+ embeddingsCachePath: path32.join(baseDir, "embeddings.json"),
3939
+ policyViolationsPath: path32.join(baseDir, "policy-violations.ndjson")
3829
3940
  };
3830
3941
  }
3831
3942
  return {
3832
- snapshotPath: path31.join(baseDir, `${project}.json`),
3833
- errorsPath: path31.join(baseDir, `errors.${project}.ndjson`),
3834
- staleEventsPath: path31.join(baseDir, `stale-events.${project}.ndjson`),
3835
- embeddingsCachePath: path31.join(baseDir, `embeddings.${project}.json`),
3836
- policyViolationsPath: path31.join(baseDir, `policy-violations.${project}.ndjson`)
3943
+ snapshotPath: path32.join(baseDir, `${project}.json`),
3944
+ errorsPath: path32.join(baseDir, `errors.${project}.ndjson`),
3945
+ staleEventsPath: path32.join(baseDir, `stale-events.${project}.ndjson`),
3946
+ embeddingsCachePath: path32.join(baseDir, `embeddings.${project}.json`),
3947
+ policyViolationsPath: path32.join(baseDir, `policy-violations.${project}.ndjson`)
3837
3948
  };
3838
3949
  }
3839
3950
  var Projects = class {
@@ -3874,7 +3985,7 @@ function parseExtraProjects(raw) {
3874
3985
  // src/registry.ts
3875
3986
  import { promises as fs19 } from "fs";
3876
3987
  import os2 from "os";
3877
- import path32 from "path";
3988
+ import path33 from "path";
3878
3989
  import {
3879
3990
  RegistryFileSchema
3880
3991
  } from "@neat.is/types";
@@ -3882,17 +3993,17 @@ var LOCK_TIMEOUT_MS = 5e3;
3882
3993
  var LOCK_RETRY_MS = 50;
3883
3994
  function neatHome() {
3884
3995
  const override = process.env.NEAT_HOME;
3885
- if (override && override.length > 0) return path32.resolve(override);
3886
- return path32.join(os2.homedir(), ".neat");
3996
+ if (override && override.length > 0) return path33.resolve(override);
3997
+ return path33.join(os2.homedir(), ".neat");
3887
3998
  }
3888
3999
  function registryPath() {
3889
- return path32.join(neatHome(), "projects.json");
4000
+ return path33.join(neatHome(), "projects.json");
3890
4001
  }
3891
4002
  function registryLockPath() {
3892
- return path32.join(neatHome(), "projects.json.lock");
4003
+ return path33.join(neatHome(), "projects.json.lock");
3893
4004
  }
3894
4005
  async function normalizeProjectPath(input) {
3895
- const resolved = path32.resolve(input);
4006
+ const resolved = path33.resolve(input);
3896
4007
  try {
3897
4008
  return await fs19.realpath(resolved);
3898
4009
  } catch {
@@ -3900,7 +4011,7 @@ async function normalizeProjectPath(input) {
3900
4011
  }
3901
4012
  }
3902
4013
  async function writeAtomically(target, contents) {
3903
- await fs19.mkdir(path32.dirname(target), { recursive: true });
4014
+ await fs19.mkdir(path33.dirname(target), { recursive: true });
3904
4015
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
3905
4016
  const fd = await fs19.open(tmp, "w");
3906
4017
  try {
@@ -3913,7 +4024,7 @@ async function writeAtomically(target, contents) {
3913
4024
  }
3914
4025
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS) {
3915
4026
  const deadline = Date.now() + timeoutMs;
3916
- await fs19.mkdir(path32.dirname(lockPath), { recursive: true });
4027
+ await fs19.mkdir(path33.dirname(lockPath), { recursive: true });
3917
4028
  while (true) {
3918
4029
  try {
3919
4030
  const fd = await fs19.open(lockPath, "wx");
@@ -4089,14 +4200,6 @@ function nodeIsFrontier(graph, nodeId) {
4089
4200
  const attrs = graph.getNodeAttributes(nodeId);
4090
4201
  return attrs.type === NodeType10.FrontierNode;
4091
4202
  }
4092
- function hasAnyObservedFromSource(graph, sourceId) {
4093
- if (!graph.hasNode(sourceId)) return false;
4094
- for (const edgeId of graph.outboundEdges(sourceId)) {
4095
- const e = graph.getEdgeAttributes(edgeId);
4096
- if (e.provenance === Provenance11.OBSERVED) return true;
4097
- }
4098
- return false;
4099
- }
4100
4203
  function clampConfidence(n) {
4101
4204
  if (!Number.isFinite(n)) return 0;
4102
4205
  return Math.max(0, Math.min(1, n));
@@ -4110,32 +4213,34 @@ function reasonForMissingExtracted(source, target, type) {
4110
4213
  var RECOMMENDATION_MISSING_OBSERVED = "Verify the code path is exercised in production; check feature flags or conditional branches that might gate the call.";
4111
4214
  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.";
4112
4215
  var RECOMMENDATION_HOST_MISMATCH = "Check environment-specific config overrides \u2014 the runtime host differs from what static configuration declares.";
4216
+ function gradedConfidence(edge) {
4217
+ if (typeof edge.confidence === "number") return clampConfidence(edge.confidence);
4218
+ return clampConfidence(confidenceForEdge(edge));
4219
+ }
4113
4220
  function detectMissingDivergences(graph, bucket) {
4114
4221
  const out = [];
4115
4222
  if (bucket.extracted && !bucket.observed) {
4116
4223
  if (!nodeIsFrontier(graph, bucket.target)) {
4117
- const sourceHasTraffic = hasAnyObservedFromSource(graph, bucket.source);
4118
4224
  out.push({
4119
4225
  type: "missing-observed",
4120
4226
  source: bucket.source,
4121
4227
  target: bucket.target,
4122
4228
  edgeType: bucket.type,
4123
4229
  extracted: bucket.extracted,
4124
- confidence: sourceHasTraffic ? 1 : 0.5,
4230
+ confidence: gradedConfidence(bucket.extracted),
4125
4231
  reason: reasonForMissingObserved(bucket.source, bucket.target, bucket.type),
4126
4232
  recommendation: RECOMMENDATION_MISSING_OBSERVED
4127
4233
  });
4128
4234
  }
4129
4235
  }
4130
4236
  if (bucket.observed && !bucket.extracted) {
4131
- const cascaded = clampConfidence(confidenceForEdge(bucket.observed));
4132
4237
  out.push({
4133
4238
  type: "missing-extracted",
4134
4239
  source: bucket.source,
4135
4240
  target: bucket.target,
4136
4241
  edgeType: bucket.type,
4137
4242
  observed: bucket.observed,
4138
- confidence: cascaded,
4243
+ confidence: gradedConfidence(bucket.observed),
4139
4244
  reason: reasonForMissingExtracted(bucket.source, bucket.target, bucket.type),
4140
4245
  recommendation: RECOMMENDATION_MISSING_EXTRACTED
4141
4246
  });
@@ -4274,8 +4379,17 @@ function computeDivergences(graph, opts = {}) {
4274
4379
  const target = opts.node;
4275
4380
  filtered = filtered.filter((d) => involvesNode(d, target));
4276
4381
  }
4382
+ const TYPE_LEADERSHIP = {
4383
+ "missing-extracted": 0,
4384
+ "missing-observed": 1,
4385
+ "version-mismatch": 2,
4386
+ "host-mismatch": 3,
4387
+ "compat-violation": 4
4388
+ };
4277
4389
  filtered.sort((a, b) => {
4278
4390
  if (b.confidence !== a.confidence) return b.confidence - a.confidence;
4391
+ const lead = TYPE_LEADERSHIP[a.type] - TYPE_LEADERSHIP[b.type];
4392
+ if (lead !== 0) return lead;
4279
4393
  if (a.type !== b.type) return a.type.localeCompare(b.type);
4280
4394
  if (a.source !== b.source) return a.source.localeCompare(b.source);
4281
4395
  return a.target.localeCompare(b.target);
@@ -4795,6 +4909,7 @@ export {
4795
4909
  readErrorEvents,
4796
4910
  isStrictExtractionEnabled,
4797
4911
  formatExtractionBanner,
4912
+ formatPrecisionFloorBanner,
4798
4913
  discoverServices,
4799
4914
  addServiceNodes,
4800
4915
  addServiceAliases,
@@ -4826,4 +4941,4 @@ export {
4826
4941
  removeProject,
4827
4942
  buildApi
4828
4943
  };
4829
- //# sourceMappingURL=chunk-VABXPLDT.js.map
4944
+ //# sourceMappingURL=chunk-33ZZ2ZID.js.map