@pratik7368patil/anchor-core 0.1.32 → 0.1.33

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.js CHANGED
@@ -617,8 +617,12 @@ CREATE TABLE IF NOT EXISTS org_cross_repo_edges (
617
617
  source_path TEXT NOT NULL,
618
618
  target_repo TEXT NOT NULL,
619
619
  target_path TEXT,
620
+ layer TEXT NOT NULL DEFAULT 'file',
620
621
  relationship TEXT NOT NULL,
621
622
  evidence_json TEXT NOT NULL,
623
+ match_reasons_json TEXT NOT NULL DEFAULT '[]',
624
+ evidence_count INTEGER NOT NULL DEFAULT 0,
625
+ is_weak INTEGER NOT NULL DEFAULT 0,
622
626
  confidence REAL NOT NULL,
623
627
  created_at TEXT NOT NULL
624
628
  );
@@ -643,6 +647,9 @@ CREATE TABLE IF NOT EXISTS org_api_consumers (
643
647
  consumer_path TEXT NOT NULL,
644
648
  contract TEXT NOT NULL,
645
649
  evidence_json TEXT NOT NULL,
650
+ match_reasons_json TEXT NOT NULL DEFAULT '[]',
651
+ evidence_count INTEGER NOT NULL DEFAULT 0,
652
+ is_weak INTEGER NOT NULL DEFAULT 0,
646
653
  confidence REAL NOT NULL,
647
654
  created_at TEXT NOT NULL
648
655
  );
@@ -667,6 +674,10 @@ CREATE TABLE IF NOT EXISTS org_graph_state (
667
674
  last_status TEXT NOT NULL DEFAULT 'unknown',
668
675
  last_duration_ms INTEGER,
669
676
  edge_count INTEGER NOT NULL DEFAULT 0,
677
+ visible_edge_count INTEGER NOT NULL DEFAULT 0,
678
+ weak_edge_count INTEGER NOT NULL DEFAULT 0,
679
+ edge_confidence_json TEXT NOT NULL DEFAULT '{"strong":0,"moderate":0,"weak":0}',
680
+ last_render_prep_ms INTEGER,
670
681
  api_contract_count INTEGER NOT NULL DEFAULT 0,
671
682
  api_consumer_count INTEGER NOT NULL DEFAULT 0,
672
683
  last_error TEXT,
@@ -707,8 +718,11 @@ CREATE INDEX IF NOT EXISTS idx_org_repositories_org ON org_repositories(org);
707
718
  CREATE INDEX IF NOT EXISTS idx_org_repo_state_org ON org_repo_state(org);
708
719
  CREATE INDEX IF NOT EXISTS idx_org_edges_source ON org_cross_repo_edges(org, source_repo);
709
720
  CREATE INDEX IF NOT EXISTS idx_org_edges_target ON org_cross_repo_edges(org, target_repo);
721
+ CREATE INDEX IF NOT EXISTS idx_org_edges_layer ON org_cross_repo_edges(org, layer, confidence);
722
+ CREATE INDEX IF NOT EXISTS idx_org_edges_repo_pair ON org_cross_repo_edges(org, layer, source_repo, target_repo, relationship);
710
723
  CREATE INDEX IF NOT EXISTS idx_org_consumers_provider ON org_api_consumers(org, provider_repo);
711
724
  CREATE INDEX IF NOT EXISTS idx_org_consumers_consumer ON org_api_consumers(org, consumer_repo);
725
+ CREATE INDEX IF NOT EXISTS idx_org_consumers_contract ON org_api_consumers(org, contract);
712
726
  CREATE INDEX IF NOT EXISTS idx_org_anomalies_org ON org_anomaly_events(org, severity);
713
727
  CREATE INDEX IF NOT EXISTS idx_org_graph_state_status ON org_graph_state(org, last_status);
714
728
 
@@ -1783,6 +1797,41 @@ function initializeSchema(db) {
1783
1797
  ensureColumn(db, "sync_state", "graphql_cursor_reason", "TEXT");
1784
1798
  ensureColumn(db, "sync_state", "graphql_cursor_updated_at", "TEXT");
1785
1799
  ensureColumn(db, "code_index_state", "last_indexed_commit", "TEXT");
1800
+ ensureColumn(db, "org_cross_repo_edges", "layer", "TEXT NOT NULL DEFAULT 'file'");
1801
+ ensureColumn(
1802
+ db,
1803
+ "org_cross_repo_edges",
1804
+ "match_reasons_json",
1805
+ "TEXT NOT NULL DEFAULT '[]'"
1806
+ );
1807
+ ensureColumn(db, "org_cross_repo_edges", "evidence_count", "INTEGER NOT NULL DEFAULT 0");
1808
+ ensureColumn(db, "org_cross_repo_edges", "is_weak", "INTEGER NOT NULL DEFAULT 0");
1809
+ ensureColumn(
1810
+ db,
1811
+ "org_api_consumers",
1812
+ "match_reasons_json",
1813
+ "TEXT NOT NULL DEFAULT '[]'"
1814
+ );
1815
+ ensureColumn(db, "org_api_consumers", "evidence_count", "INTEGER NOT NULL DEFAULT 0");
1816
+ ensureColumn(db, "org_api_consumers", "is_weak", "INTEGER NOT NULL DEFAULT 0");
1817
+ ensureColumn(db, "org_graph_state", "visible_edge_count", "INTEGER NOT NULL DEFAULT 0");
1818
+ ensureColumn(db, "org_graph_state", "weak_edge_count", "INTEGER NOT NULL DEFAULT 0");
1819
+ ensureColumn(
1820
+ db,
1821
+ "org_graph_state",
1822
+ "edge_confidence_json",
1823
+ `TEXT NOT NULL DEFAULT '{"strong":0,"moderate":0,"weak":0}'`
1824
+ );
1825
+ ensureColumn(db, "org_graph_state", "last_render_prep_ms", "INTEGER");
1826
+ db.exec(
1827
+ "CREATE INDEX IF NOT EXISTS idx_org_edges_layer ON org_cross_repo_edges(org, layer, confidence)"
1828
+ );
1829
+ db.exec(
1830
+ "CREATE INDEX IF NOT EXISTS idx_org_edges_repo_pair ON org_cross_repo_edges(org, layer, source_repo, target_repo, relationship)"
1831
+ );
1832
+ db.exec(
1833
+ "CREATE INDEX IF NOT EXISTS idx_org_consumers_contract ON org_api_consumers(org, contract)"
1834
+ );
1786
1835
  }
1787
1836
  function ensureColumn(db, tableName, columnName, definition) {
1788
1837
  const columns = db.prepare(`PRAGMA table_info(${tableName})`).all();
@@ -8706,6 +8755,24 @@ function resolveOrgForTool(org, baseDir = defaultOrgBaseDir()) {
8706
8755
 
8707
8756
  // src/org/database.ts
8708
8757
  import fs10 from "fs";
8758
+ var DEFAULT_EDGE_DISTRIBUTION = {
8759
+ strong: 0,
8760
+ moderate: 0,
8761
+ weak: 0
8762
+ };
8763
+ function parseEdgeDistribution(value) {
8764
+ if (!value) return { ...DEFAULT_EDGE_DISTRIBUTION };
8765
+ try {
8766
+ const parsed = JSON.parse(value);
8767
+ return {
8768
+ strong: Number(parsed.strong ?? 0),
8769
+ moderate: Number(parsed.moderate ?? 0),
8770
+ weak: Number(parsed.weak ?? 0)
8771
+ };
8772
+ } catch {
8773
+ return { ...DEFAULT_EDGE_DISTRIBUTION };
8774
+ }
8775
+ }
8709
8776
  function openOrgDatabase(org, baseDir) {
8710
8777
  const root = orgRoot(org, baseDir);
8711
8778
  const db = openAnchorDatabase(root, orgDatabasePath(org, baseDir));
@@ -8835,8 +8902,9 @@ function recordOrgGraphState(db, input) {
8835
8902
  db.prepare(
8836
8903
  `INSERT INTO org_graph_state
8837
8904
  (org, last_built_at, last_status, last_duration_ms, edge_count, api_contract_count,
8838
- api_consumer_count, last_error, updated_at)
8839
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
8905
+ api_consumer_count, visible_edge_count, weak_edge_count, edge_confidence_json,
8906
+ last_render_prep_ms, last_error, updated_at)
8907
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
8840
8908
  ON CONFLICT(org) DO UPDATE SET
8841
8909
  last_built_at = COALESCE(excluded.last_built_at, org_graph_state.last_built_at),
8842
8910
  last_status = excluded.last_status,
@@ -8844,6 +8912,10 @@ function recordOrgGraphState(db, input) {
8844
8912
  edge_count = excluded.edge_count,
8845
8913
  api_contract_count = excluded.api_contract_count,
8846
8914
  api_consumer_count = excluded.api_consumer_count,
8915
+ visible_edge_count = excluded.visible_edge_count,
8916
+ weak_edge_count = excluded.weak_edge_count,
8917
+ edge_confidence_json = excluded.edge_confidence_json,
8918
+ last_render_prep_ms = excluded.last_render_prep_ms,
8847
8919
  last_error = excluded.last_error,
8848
8920
  updated_at = excluded.updated_at`
8849
8921
  ).run(
@@ -8854,6 +8926,10 @@ function recordOrgGraphState(db, input) {
8854
8926
  input.edgeCount ?? 0,
8855
8927
  input.apiContractCount ?? 0,
8856
8928
  input.apiConsumerCount ?? 0,
8929
+ input.visibleEdgeCount ?? input.edgeCount ?? 0,
8930
+ input.weakEdgeCount ?? 0,
8931
+ JSON.stringify(input.edgeConfidenceDistribution ?? DEFAULT_EDGE_DISTRIBUTION),
8932
+ input.lastRenderPrepMs ?? null,
8857
8933
  input.error ?? null,
8858
8934
  now
8859
8935
  );
@@ -8868,6 +8944,10 @@ function getOrgGraphState(db, org) {
8868
8944
  lastStatus: row.last_status ?? void 0,
8869
8945
  lastDurationMs: row.last_duration_ms ?? void 0,
8870
8946
  edgeCount: row.edge_count ?? void 0,
8947
+ visibleEdgeCount: row.visible_edge_count ?? void 0,
8948
+ weakEdgeCount: row.weak_edge_count ?? void 0,
8949
+ edgeConfidenceDistribution: parseEdgeDistribution(row.edge_confidence_json),
8950
+ lastRenderPrepMs: row.last_render_prep_ms ?? void 0,
8871
8951
  apiContractCount: row.api_contract_count ?? void 0,
8872
8952
  apiConsumerCount: row.api_consumer_count ?? void 0,
8873
8953
  lastError: row.last_error ?? void 0
@@ -8880,7 +8960,19 @@ function count(db, table, where = "", params = []) {
8880
8960
  function getOrgGraphCounts(db, org) {
8881
8961
  initializeSchema(db);
8882
8962
  return {
8883
- edges: count(db, "org_cross_repo_edges", "WHERE org = ?", [org]),
8963
+ edges: count(db, "org_cross_repo_edges", "WHERE org = ? AND layer = 'repo'", [org]),
8964
+ visibleEdges: count(
8965
+ db,
8966
+ "org_cross_repo_edges",
8967
+ "WHERE org = ? AND layer = 'repo' AND is_weak = 0",
8968
+ [org]
8969
+ ),
8970
+ weakEdges: count(
8971
+ db,
8972
+ "org_cross_repo_edges",
8973
+ "WHERE org = ? AND layer = 'repo' AND is_weak = 1",
8974
+ [org]
8975
+ ),
8884
8976
  apiContracts: count(db, "org_api_contracts", "WHERE org = ?", [org]),
8885
8977
  apiConsumers: count(db, "org_api_consumers", "WHERE org = ?", [org])
8886
8978
  };
@@ -8907,11 +8999,22 @@ function getOrgStatus(db, config, baseDir, options = {}) {
8907
8999
  const codeFileCount = count(db, "code_files");
8908
9000
  const codeChunkCount = count(db, "code_chunks");
8909
9001
  const wisdomUnitCount = count(db, "wisdom_units");
8910
- const crossRepoEdgeCount = count(db, "org_cross_repo_edges", "WHERE org = ?", [config.org]);
9002
+ const crossRepoEdgeCount = count(
9003
+ db,
9004
+ "org_cross_repo_edges",
9005
+ "WHERE org = ? AND layer = 'repo' AND is_weak = 0",
9006
+ [config.org]
9007
+ );
9008
+ const graphWeakEdgeCount = count(
9009
+ db,
9010
+ "org_cross_repo_edges",
9011
+ "WHERE org = ? AND layer = 'repo' AND is_weak = 1",
9012
+ [config.org]
9013
+ );
8911
9014
  const apiContractCount = count(db, "org_api_contracts", "WHERE org = ?", [config.org]);
8912
9015
  const apiConsumerCount = count(db, "org_api_consumers", "WHERE org = ?", [config.org]);
8913
9016
  const anomalyCount = count(db, "org_anomaly_events", "WHERE org = ?", [config.org]);
8914
- const graphState = db.prepare("SELECT * FROM org_graph_state WHERE org = ?").get(config.org);
9017
+ const graphState = getOrgGraphState(db, config.org);
8915
9018
  let score = 0;
8916
9019
  const reasons = [];
8917
9020
  if (enabledRepos.length > 0) {
@@ -8955,10 +9058,14 @@ function getOrgStatus(db, config, baseDir, options = {}) {
8955
9058
  apiContractCount,
8956
9059
  apiConsumerCount,
8957
9060
  anomalyCount,
8958
- graphLastBuiltAt: graphState?.last_built_at ?? void 0,
8959
- graphLastStatus: graphState?.last_status ?? void 0,
8960
- graphLastDurationMs: graphState?.last_duration_ms ?? void 0,
8961
- graphLastError: graphState?.last_error ?? void 0,
9061
+ graphLastBuiltAt: graphState?.lastBuiltAt,
9062
+ graphLastStatus: graphState?.lastStatus,
9063
+ graphLastDurationMs: graphState?.lastDurationMs,
9064
+ graphLastError: graphState?.lastError,
9065
+ graphVisibleEdgeCount: graphState?.visibleEdgeCount ?? crossRepoEdgeCount,
9066
+ graphWeakEdgeCount: graphState?.weakEdgeCount ?? graphWeakEdgeCount,
9067
+ graphRenderPrepMs: graphState?.lastRenderPrepMs,
9068
+ graphEdgeConfidenceDistribution: graphState?.edgeConfidenceDistribution ?? { ...DEFAULT_EDGE_DISTRIBUTION },
8962
9069
  coverageScore: score,
8963
9070
  coverageGrade: grade(score),
8964
9071
  coverageReasons: reasons,
@@ -9262,6 +9369,34 @@ function orgCloneStateFromResult(org, repo, result) {
9262
9369
  import crypto9 from "crypto";
9263
9370
  import fs13 from "fs";
9264
9371
  import path23 from "path";
9372
+ var MIN_FILE_EDGE_CONFIDENCE = 0.62;
9373
+ var MIN_REPO_EDGE_CONFIDENCE = 0.7;
9374
+ var MIN_VISIBLE_EVIDENCE = 2;
9375
+ var MIN_API_CONSUMER_CONFIDENCE = 0.68;
9376
+ var MAX_EDGE_EVIDENCE = 8;
9377
+ var MAX_EDGE_REASONS = 6;
9378
+ var MAX_CONTRACTS_PER_CHUNK = 24;
9379
+ var CONTRACT_IGNORE = /* @__PURE__ */ new Set([
9380
+ "api",
9381
+ "v1",
9382
+ "v2",
9383
+ "v3",
9384
+ "graphql",
9385
+ "query",
9386
+ "mutation",
9387
+ "subscription",
9388
+ "schema",
9389
+ "route",
9390
+ "routes",
9391
+ "controller",
9392
+ "client",
9393
+ "request"
9394
+ ]);
9395
+ var DEFAULT_EDGE_DISTRIBUTION2 = {
9396
+ strong: 0,
9397
+ moderate: 0,
9398
+ weak: 0
9399
+ };
9265
9400
  function stableId(parts) {
9266
9401
  return crypto9.createHash("sha256").update(parts.join("\0")).digest("hex").slice(0, 32);
9267
9402
  }
@@ -9274,6 +9409,9 @@ function fileEvidence(repo, filePath, note) {
9274
9409
  note
9275
9410
  };
9276
9411
  }
9412
+ function evidenceJson(evidence) {
9413
+ return JSON.stringify(evidence);
9414
+ }
9277
9415
  function readPackageManifest(repoPath) {
9278
9416
  const packagePath = path23.join(repoPath, "package.json");
9279
9417
  if (!fs13.existsSync(packagePath)) return void 0;
@@ -9319,19 +9457,53 @@ function parseJsonArray10(value) {
9319
9457
  return [];
9320
9458
  }
9321
9459
  }
9322
- function extractContracts(text) {
9323
- const contracts = [];
9324
- const routeMatches = text.matchAll(/["'`]((?:\/api)?\/[A-Za-z0-9_./:{}-]{2,})["'`]/g);
9325
- for (const match of routeMatches) {
9326
- const route = match[1];
9327
- if (route && route.length <= 120 && !route.includes(" ")) contracts.push(route);
9328
- }
9329
- const gqlMatches = text.matchAll(/\b(query|mutation)\s+([A-Za-z0-9_]+)/g);
9330
- for (const match of gqlMatches) {
9331
- const operation = match[2];
9332
- if (operation) contracts.push(operation);
9460
+ function normalizeToken(value) {
9461
+ return value.toLowerCase().replace(/[{}()[\],:"'`]/g, "").replace(/\/+/g, "/").replace(/\/$/g, "").replace(/^-+/g, "").trim();
9462
+ }
9463
+ function splitTokenSymbols(value) {
9464
+ return value.split(/[^A-Za-z0-9_/-]+/).map((item) => normalizeToken(item)).filter((item) => item.length >= 3 && !CONTRACT_IGNORE.has(item)).slice(0, 12);
9465
+ }
9466
+ function normalizeContract(contract, kind) {
9467
+ if (kind === "route") {
9468
+ const normalized = normalizeToken(contract);
9469
+ return normalized.startsWith("/") ? normalized : `/${normalized}`;
9333
9470
  }
9334
- return uniqueStrings(contracts).slice(0, 20);
9471
+ return normalizeToken(contract);
9472
+ }
9473
+ function isGenericRoute(route) {
9474
+ const normalized = normalizeToken(route);
9475
+ if (!normalized.startsWith("/")) return true;
9476
+ const segments = normalized.split("/").filter((segment) => segment && !segment.startsWith(":") && segment !== "*");
9477
+ if (segments.length === 0) return true;
9478
+ const informative = segments.filter((segment) => !CONTRACT_IGNORE.has(segment));
9479
+ return informative.length < 1;
9480
+ }
9481
+ function extractContracts(text) {
9482
+ const tokens = [];
9483
+ const seen = /* @__PURE__ */ new Set();
9484
+ const pushToken = (rawValue, kind) => {
9485
+ const sanitized = sanitizeHistoricalText(rawValue).slice(0, 180);
9486
+ if (!sanitized) return;
9487
+ const normalized = normalizeContract(sanitized, kind);
9488
+ if (!normalized || CONTRACT_IGNORE.has(normalized)) return;
9489
+ if (kind === "route" && isGenericRoute(normalized)) return;
9490
+ const key = `${kind}\0${normalized}`;
9491
+ if (seen.has(key)) return;
9492
+ seen.add(key);
9493
+ tokens.push({
9494
+ raw: sanitized,
9495
+ normalized,
9496
+ kind,
9497
+ symbols: splitTokenSymbols(sanitized)
9498
+ });
9499
+ };
9500
+ const routeMatches = text.matchAll(/["'`]((?:\/api)?\/[A-Za-z0-9_./:{}-]{3,})["'`]/g);
9501
+ for (const match of routeMatches) pushToken(match[1] ?? "", "route");
9502
+ const gqlMatches = text.matchAll(/\b(query|mutation|subscription)\s+([A-Za-z][A-Za-z0-9_]{2,})/g);
9503
+ for (const match of gqlMatches) pushToken(match[2] ?? "", "graphql");
9504
+ const schemaMatches = text.matchAll(/\b(?:type|interface|enum|input)\s+([A-Z][A-Za-z0-9_]{2,})\b/g);
9505
+ for (const match of schemaMatches) pushToken(match[1] ?? "", "schema");
9506
+ return tokens.slice(0, MAX_CONTRACTS_PER_CHUNK);
9335
9507
  }
9336
9508
  function isApiProviderPath(filePath) {
9337
9509
  const normalized = filePath.toLowerCase();
@@ -9342,28 +9514,216 @@ function isApiProviderPath(filePath) {
9342
9514
  function isApiConsumerText(text) {
9343
9515
  return /\b(fetch|axios|ky|graphql|gql|client|sdk|request)\b/i.test(text);
9344
9516
  }
9345
- function evidenceJson(evidence) {
9346
- return JSON.stringify(evidence);
9347
- }
9348
9517
  function shouldEmitProgress3(current, total, interval = 100) {
9349
9518
  return current === 1 || current === total || current % interval === 0;
9350
9519
  }
9351
9520
  function resolveOptions(baseDirOrOptions) {
9352
9521
  return typeof baseDirOrOptions === "string" ? { baseDir: baseDirOrOptions } : baseDirOrOptions ?? {};
9353
9522
  }
9523
+ function clampConfidence(value) {
9524
+ if (Number.isNaN(value)) return 0;
9525
+ return Math.max(0, Math.min(0.99, Number(value.toFixed(3))));
9526
+ }
9527
+ function confidenceBucket(confidence) {
9528
+ if (confidence >= 0.82) return "strong";
9529
+ if (confidence >= 0.68) return "moderate";
9530
+ return "weak";
9531
+ }
9532
+ function uniqueEvidenceRefs(evidence) {
9533
+ const map = /* @__PURE__ */ new Map();
9534
+ for (const item of evidence) {
9535
+ const key = `${item.prNumber}|${item.prUrl}|${item.sourceType}|${item.filePath ?? ""}|${item.note ?? ""}`;
9536
+ if (!map.has(key)) map.set(key, item);
9537
+ }
9538
+ return [...map.values()].slice(0, MAX_EDGE_EVIDENCE);
9539
+ }
9540
+ function mergeReasons(a, b) {
9541
+ return uniqueStrings([...a, ...b]).slice(0, MAX_EDGE_REASONS);
9542
+ }
9543
+ function updateWeakFlag(edge, minConfidence2, minEvidence) {
9544
+ edge.evidence = uniqueEvidenceRefs(edge.evidence);
9545
+ edge.evidenceCount = edge.evidence.length;
9546
+ edge.confidence = clampConfidence(edge.confidence);
9547
+ edge.weak = edge.confidence < minConfidence2 || edge.evidenceCount < minEvidence;
9548
+ }
9549
+ function fileEdgeKey(edge) {
9550
+ return [
9551
+ edge.layer,
9552
+ edge.sourceRepo,
9553
+ edge.sourcePath,
9554
+ edge.targetRepo,
9555
+ edge.targetPath ?? "",
9556
+ edge.relationship
9557
+ ].join("\0");
9558
+ }
9559
+ function repoEdgeKey(edge) {
9560
+ return [edge.layer, edge.sourceRepo, edge.targetRepo, edge.relationship].join("\0");
9561
+ }
9562
+ function upsertEdge(map, edge, minConfidence2, minEvidence) {
9563
+ const layer = edge.layer ?? "file";
9564
+ if (edge.sourceRepo === edge.targetRepo) return { inserted: false, updated: false };
9565
+ const key = layer === "repo" ? repoEdgeKey({ ...edge, layer }) : fileEdgeKey({ ...edge, layer });
9566
+ const existing = map.get(key);
9567
+ if (!existing) {
9568
+ const created = {
9569
+ ...edge,
9570
+ layer,
9571
+ evidence: uniqueEvidenceRefs(edge.evidence),
9572
+ matchReasons: mergeReasons([], edge.matchReasons),
9573
+ evidenceCount: 0,
9574
+ weak: false
9575
+ };
9576
+ updateWeakFlag(created, minConfidence2, minEvidence);
9577
+ map.set(key, created);
9578
+ return { inserted: true, updated: false };
9579
+ }
9580
+ const merged = {
9581
+ ...existing,
9582
+ sourcePath: existing.layer === "repo" ? "*" : existing.sourcePath,
9583
+ targetPath: existing.layer === "repo" ? void 0 : existing.targetPath ?? edge.targetPath,
9584
+ evidence: uniqueEvidenceRefs([...existing.evidence, ...edge.evidence]),
9585
+ matchReasons: mergeReasons(existing.matchReasons, edge.matchReasons),
9586
+ confidence: Math.max(existing.confidence, edge.confidence),
9587
+ evidenceCount: 0,
9588
+ weak: false
9589
+ };
9590
+ updateWeakFlag(merged, minConfidence2, minEvidence);
9591
+ map.set(key, merged);
9592
+ return { inserted: false, updated: true };
9593
+ }
9594
+ function scorePackageDependency(dependency) {
9595
+ const normalized = sanitizeHistoricalText(dependency);
9596
+ const reasons = ["exact_package_dependency"];
9597
+ const confidence = normalized.startsWith("@") ? 0.93 : 0.9;
9598
+ return { confidence, reasons };
9599
+ }
9600
+ function scoreImportEdge(input) {
9601
+ let score = 0.58;
9602
+ const reasons = ["cross_repo_import"];
9603
+ if (input.specifier.includes("/")) {
9604
+ score += 0.12;
9605
+ reasons.push("qualified_specifier");
9606
+ }
9607
+ if (input.importedPath) {
9608
+ score += 0.16;
9609
+ reasons.push("resolved_import_path");
9610
+ }
9611
+ if (input.importedSymbols.length > 0) {
9612
+ score += 0.11;
9613
+ reasons.push("explicit_import_symbols");
9614
+ }
9615
+ return { confidence: clampConfidence(score), reasons };
9616
+ }
9617
+ function scoreContract(input) {
9618
+ let score = 0.66;
9619
+ if (input.token.kind === "route") score += 0.08;
9620
+ if (input.token.kind === "graphql") score += 0.06;
9621
+ if (input.token.kind === "schema") score += 0.03;
9622
+ if (/\b(route|controller|api|schema|client)\b/i.test(input.filePath)) score += 0.05;
9623
+ if (input.token.symbols.length > 0) score += 0.04;
9624
+ return clampConfidence(score);
9625
+ }
9626
+ function overlapScore(a, b) {
9627
+ if (a.length === 0 || b.length === 0) return 0;
9628
+ const set = new Set(a);
9629
+ let overlaps = 0;
9630
+ for (const value of b) {
9631
+ if (set.has(value)) overlaps += 1;
9632
+ }
9633
+ return overlaps / Math.max(1, Math.min(a.length, b.length));
9634
+ }
9635
+ function scoreConsumerMatch(input) {
9636
+ let score = 0.58;
9637
+ const reasons = ["matched_contract_token"];
9638
+ if (input.consumerToken.kind === input.contract.kind) {
9639
+ score += 0.12;
9640
+ reasons.push("matching_contract_kind");
9641
+ }
9642
+ const symbolOverlap = overlapScore(
9643
+ uniqueStrings([...input.contract.symbols, ...splitTokenSymbols(input.contract.contract)]),
9644
+ uniqueStrings([...input.chunkSymbols, ...input.consumerToken.symbols])
9645
+ );
9646
+ if (symbolOverlap > 0) {
9647
+ score += Math.min(0.2, symbolOverlap * 0.22);
9648
+ reasons.push("symbol_overlap");
9649
+ }
9650
+ if (input.contract.kind === "route" && input.consumerToken.raw.includes("/")) {
9651
+ score += 0.08;
9652
+ reasons.push("route_literal_match");
9653
+ }
9654
+ return { confidence: clampConfidence(score), reasons };
9655
+ }
9656
+ function aggregateRepoEdges(fileEdges) {
9657
+ const grouped = /* @__PURE__ */ new Map();
9658
+ for (const edge of fileEdges) {
9659
+ const key = repoEdgeKey({
9660
+ layer: "repo",
9661
+ sourceRepo: edge.sourceRepo,
9662
+ targetRepo: edge.targetRepo,
9663
+ relationship: edge.relationship
9664
+ });
9665
+ const bucket = grouped.get(key) ?? [];
9666
+ bucket.push(edge);
9667
+ grouped.set(key, bucket);
9668
+ }
9669
+ const repoEdges = [];
9670
+ for (const [key, group] of grouped.entries()) {
9671
+ const [layerValue = "repo", sourceRepo = "", targetRepo = ""] = key.split("\0");
9672
+ const relationship = group[0]?.relationship ?? "imports";
9673
+ const confidences = group.map((edge) => edge.confidence);
9674
+ const maxConfidence = Math.max(...confidences);
9675
+ const avgConfidence = confidences.reduce((sum, value) => sum + value, 0) / confidences.length;
9676
+ const repetitionBoost = Math.min(0.18, Math.log2(group.length + 1) * 0.06);
9677
+ const confidence = clampConfidence(maxConfidence * 0.6 + avgConfidence * 0.25 + repetitionBoost);
9678
+ const evidence = uniqueEvidenceRefs(group.flatMap((edge) => edge.evidence));
9679
+ const matchReasons6 = mergeReasons([], group.flatMap((edge) => edge.matchReasons));
9680
+ const repoEdge = {
9681
+ org: group[0]?.org ?? "",
9682
+ sourceRepo,
9683
+ sourcePath: "*",
9684
+ targetRepo,
9685
+ targetPath: void 0,
9686
+ layer: layerValue,
9687
+ relationship,
9688
+ evidence,
9689
+ matchReasons: matchReasons6,
9690
+ evidenceCount: 0,
9691
+ weak: false,
9692
+ confidence
9693
+ };
9694
+ updateWeakFlag(repoEdge, MIN_REPO_EDGE_CONFIDENCE, MIN_VISIBLE_EVIDENCE);
9695
+ repoEdges.push(repoEdge);
9696
+ }
9697
+ return repoEdges.sort((a, b) => b.confidence - a.confidence);
9698
+ }
9699
+ function buildQuality(repoEdges, hiddenRepoEdges) {
9700
+ const distribution = { ...DEFAULT_EDGE_DISTRIBUTION2 };
9701
+ for (const edge of repoEdges) {
9702
+ distribution[confidenceBucket(edge.confidence)] += 1;
9703
+ }
9704
+ return {
9705
+ edgeConfidenceDistribution: distribution,
9706
+ weakEdgesFiltered: hiddenRepoEdges.length,
9707
+ minVisibleConfidence: MIN_REPO_EDGE_CONFIDENCE,
9708
+ minVisibleEvidence: MIN_VISIBLE_EVIDENCE
9709
+ };
9710
+ }
9711
+ function isVisibleRepoEdge(edge) {
9712
+ return !edge.weak && edge.confidence >= MIN_REPO_EDGE_CONFIDENCE && edge.evidenceCount >= MIN_VISIBLE_EVIDENCE;
9713
+ }
9354
9714
  function rebuildOrgGraph(db, config, baseDirOrOptions) {
9355
9715
  initializeSchema(db);
9356
9716
  const options = resolveOptions(baseDirOrOptions);
9357
9717
  const startedAt = Date.now();
9358
9718
  try {
9719
+ const enabledRepos = config.repos.filter((repo) => repo.enabled);
9720
+ const repoByName = new Map(enabledRepos.map((repo) => [repo.fullName, repo]));
9359
9721
  options.onProgress?.({
9360
9722
  stage: "loading_package_manifests",
9361
9723
  org: config.org,
9362
- totalRepos: config.repos.filter((repo) => repo.enabled).length
9724
+ totalRepos: enabledRepos.length
9363
9725
  });
9364
9726
  const packageNames = repoPackageNames(config, options.baseDir);
9365
- const enabledRepos = config.repos.filter((repo) => repo.enabled);
9366
- const repoByName = new Map(enabledRepos.map((repo) => [repo.fullName, repo]));
9367
9727
  const packageToRepo = /* @__PURE__ */ new Map();
9368
9728
  for (const [repo, names] of packageNames.entries()) {
9369
9729
  for (const name of names) packageToRepo.set(name, repo);
@@ -9374,31 +9734,22 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9374
9734
  repos: enabledRepos.length,
9375
9735
  packageNames: packageToRepo.size
9376
9736
  });
9377
- const edges = [];
9378
- const edgeKeys = /* @__PURE__ */ new Set();
9379
- const addEdge = (edge) => {
9380
- if (edge.sourceRepo === edge.targetRepo) return;
9381
- const key = [
9382
- edge.sourceRepo,
9383
- edge.sourcePath,
9384
- edge.targetRepo,
9385
- edge.targetPath ?? "",
9386
- edge.relationship
9387
- ].join("\0");
9388
- if (edgeKeys.has(key)) return;
9389
- edgeKeys.add(key);
9390
- edges.push(edge);
9737
+ const fileEdgeMap = /* @__PURE__ */ new Map();
9738
+ const addFileEdge = (edge) => {
9739
+ upsertEdge(fileEdgeMap, { ...edge, layer: "file" }, MIN_FILE_EDGE_CONFIDENCE, 1);
9391
9740
  };
9392
9741
  enabledRepos.forEach((repo, index) => {
9393
9742
  const manifest = readPackageManifest(orgRepoLocalPath(config.org, repo, options.baseDir));
9394
9743
  for (const dependency of dependenciesFor(manifest)) {
9395
9744
  const targetRepo = packageToRepo.get(dependency);
9396
9745
  if (!targetRepo || targetRepo === repo.fullName) continue;
9397
- addEdge({
9746
+ const score = scorePackageDependency(dependency);
9747
+ addFileEdge({
9398
9748
  org: config.org,
9399
9749
  sourceRepo: repo.fullName,
9400
9750
  sourcePath: "package.json",
9401
9751
  targetRepo,
9752
+ targetPath: "package.json",
9402
9753
  relationship: "depends_on_package",
9403
9754
  evidence: [
9404
9755
  fileEvidence(
@@ -9407,7 +9758,8 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9407
9758
  `depends on ${sanitizeHistoricalText(dependency)}`
9408
9759
  )
9409
9760
  ],
9410
- confidence: 0.9
9761
+ matchReasons: score.reasons,
9762
+ confidence: score.confidence
9411
9763
  });
9412
9764
  }
9413
9765
  options.onProgress?.({
@@ -9416,7 +9768,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9416
9768
  current: index + 1,
9417
9769
  total: enabledRepos.length,
9418
9770
  repo: repo.fullName,
9419
- edges: edges.length
9771
+ edges: fileEdgeMap.size
9420
9772
  });
9421
9773
  });
9422
9774
  options.onProgress?.({ stage: "loading_imports", org: config.org });
@@ -9431,7 +9783,13 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9431
9783
  const rootSpecifier = packageRootForSpecifier(item.specifier);
9432
9784
  const targetRepo = packageToRepo.get(rootSpecifier) ?? packageToRepo.get(item.specifier);
9433
9785
  if (targetRepo && targetRepo !== item.repo) {
9434
- addEdge({
9786
+ const importedSymbols = parseJsonArray10(item.imported_symbols_json);
9787
+ const score = scoreImportEdge({
9788
+ specifier: item.specifier,
9789
+ importedPath: item.imported_path,
9790
+ importedSymbols
9791
+ });
9792
+ addFileEdge({
9435
9793
  org: config.org,
9436
9794
  sourceRepo: item.repo,
9437
9795
  sourcePath: item.source_path,
@@ -9445,7 +9803,8 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9445
9803
  `imports ${sanitizeHistoricalText(rootSpecifier || item.specifier)}`
9446
9804
  )
9447
9805
  ],
9448
- confidence: parseJsonArray10(item.imported_symbols_json).length > 0 ? 0.88 : 0.76
9806
+ matchReasons: score.reasons,
9807
+ confidence: score.confidence
9449
9808
  });
9450
9809
  }
9451
9810
  if (shouldEmitProgress3(index + 1, imports.length)) {
@@ -9455,7 +9814,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9455
9814
  current: index + 1,
9456
9815
  total: imports.length,
9457
9816
  sourcePath: item.source_path,
9458
- edges: edges.length
9817
+ edges: fileEdgeMap.size
9459
9818
  });
9460
9819
  }
9461
9820
  });
@@ -9469,25 +9828,27 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9469
9828
  (chunk) => repoByName.has(chunk.repo) && isApiProviderPath(chunk.file_path)
9470
9829
  );
9471
9830
  const apiContracts = [];
9472
- const contractKeys = /* @__PURE__ */ new Set();
9831
+ const contractByKey = /* @__PURE__ */ new Map();
9473
9832
  const contractsByToken = /* @__PURE__ */ new Map();
9474
9833
  providerChunks.forEach((chunk, index) => {
9475
- for (const contract of extractContracts(chunk.sanitized_text)) {
9476
- const sanitizedContract = sanitizeHistoricalText(contract);
9477
- const key = [chunk.repo, chunk.file_path, sanitizedContract].join("\0");
9478
- if (contractKeys.has(key)) continue;
9479
- contractKeys.add(key);
9480
- const apiContract = {
9834
+ for (const token of extractContracts(chunk.sanitized_text)) {
9835
+ const key = [chunk.repo, chunk.file_path, token.kind, token.normalized].join("\0");
9836
+ if (contractByKey.has(key)) continue;
9837
+ const contract = {
9481
9838
  repo: chunk.repo,
9482
9839
  filePath: chunk.file_path,
9483
- contract: sanitizedContract,
9484
- evidence: [fileEvidence(chunk.repo, chunk.file_path, `defines ${sanitizedContract}`)],
9485
- confidence: 0.74
9840
+ contract: token.raw,
9841
+ normalizedContract: token.normalized,
9842
+ kind: token.kind,
9843
+ symbols: token.symbols,
9844
+ evidence: [fileEvidence(chunk.repo, chunk.file_path, `defines ${token.raw}`)],
9845
+ confidence: scoreContract({ token, filePath: chunk.file_path })
9486
9846
  };
9487
- apiContracts.push(apiContract);
9488
- const bucket = contractsByToken.get(sanitizedContract) ?? [];
9489
- bucket.push(apiContract);
9490
- contractsByToken.set(sanitizedContract, bucket);
9847
+ contractByKey.set(key, contract);
9848
+ apiContracts.push(contract);
9849
+ const bucket = contractsByToken.get(contract.normalizedContract) ?? [];
9850
+ bucket.push(contract);
9851
+ contractsByToken.set(contract.normalizedContract, bucket);
9491
9852
  }
9492
9853
  if (shouldEmitProgress3(index + 1, providerChunks.length)) {
9493
9854
  options.onProgress?.({
@@ -9500,28 +9861,39 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9500
9861
  });
9501
9862
  }
9502
9863
  });
9503
- const apiConsumers = [];
9504
- const consumerKeys = /* @__PURE__ */ new Set();
9505
9864
  const consumerChunks = chunks.filter(
9506
9865
  (chunk) => repoByName.has(chunk.repo) && isApiConsumerText(chunk.sanitized_text)
9507
9866
  );
9867
+ const apiConsumers = [];
9868
+ const consumerKeySet = /* @__PURE__ */ new Set();
9508
9869
  consumerChunks.forEach((chunk, index) => {
9509
- const consumerTokens = extractContracts(chunk.sanitized_text);
9510
- let chunkMatches = 0;
9511
- for (const token of consumerTokens) {
9512
- const contracts = contractsByToken.get(sanitizeHistoricalText(token));
9513
- if (!contracts) continue;
9870
+ const chunkTokens = extractContracts(chunk.sanitized_text);
9871
+ const chunkSymbols = parseJsonArray10(chunk.symbols_json);
9872
+ let matchesForChunk = 0;
9873
+ for (const consumerToken of chunkTokens) {
9874
+ const contracts = contractsByToken.get(consumerToken.normalized);
9875
+ if (!contracts?.length) continue;
9514
9876
  for (const contract of contracts) {
9515
9877
  if (chunk.repo === contract.repo) continue;
9878
+ const score = scoreConsumerMatch({
9879
+ consumerToken,
9880
+ contract,
9881
+ chunkSymbols
9882
+ });
9883
+ if (score.confidence < MIN_API_CONSUMER_CONFIDENCE) continue;
9516
9884
  const consumerKey = [
9517
9885
  contract.repo,
9518
9886
  contract.filePath,
9519
9887
  chunk.repo,
9520
9888
  chunk.file_path,
9521
- contract.contract
9889
+ contract.normalizedContract
9522
9890
  ].join("\0");
9523
- if (consumerKeys.has(consumerKey)) continue;
9524
- consumerKeys.add(consumerKey);
9891
+ if (consumerKeySet.has(consumerKey)) continue;
9892
+ consumerKeySet.add(consumerKey);
9893
+ const evidence = uniqueEvidenceRefs([
9894
+ ...contract.evidence,
9895
+ fileEvidence(chunk.repo, chunk.file_path, `consumes ${contract.contract}`)
9896
+ ]);
9525
9897
  const consumer = {
9526
9898
  org: config.org,
9527
9899
  providerRepo: contract.repo,
@@ -9529,23 +9901,24 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9529
9901
  consumerRepo: chunk.repo,
9530
9902
  consumerPath: chunk.file_path,
9531
9903
  contract: contract.contract,
9532
- evidence: [
9533
- ...contract.evidence,
9534
- fileEvidence(chunk.repo, chunk.file_path, `consumes ${contract.contract}`)
9535
- ],
9536
- confidence: 0.86
9904
+ evidence,
9905
+ matchReasons: mergeReasons([], score.reasons),
9906
+ evidenceCount: evidence.length,
9907
+ weak: score.confidence < MIN_API_CONSUMER_CONFIDENCE || evidence.length < MIN_VISIBLE_EVIDENCE,
9908
+ confidence: score.confidence
9537
9909
  };
9538
- chunkMatches += 1;
9539
9910
  apiConsumers.push(consumer);
9540
- addEdge({
9911
+ matchesForChunk += 1;
9912
+ addFileEdge({
9541
9913
  org: config.org,
9542
9914
  sourceRepo: chunk.repo,
9543
9915
  sourcePath: chunk.file_path,
9544
9916
  targetRepo: contract.repo,
9545
9917
  targetPath: contract.filePath,
9546
9918
  relationship: "api_consumer",
9547
- evidence: consumer.evidence,
9548
- confidence: consumer.confidence
9919
+ evidence,
9920
+ matchReasons: score.reasons,
9921
+ confidence: score.confidence
9549
9922
  });
9550
9923
  }
9551
9924
  }
@@ -9556,46 +9929,79 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9556
9929
  current: index + 1,
9557
9930
  total: consumerChunks.length,
9558
9931
  filePath: chunk.file_path,
9559
- matches: chunkMatches
9932
+ matches: matchesForChunk
9560
9933
  });
9561
9934
  }
9562
9935
  });
9936
+ const allFileEdges = [...fileEdgeMap.values()].sort((a, b) => b.confidence - a.confidence);
9937
+ const repoEdges = aggregateRepoEdges(allFileEdges);
9938
+ const visibleRepoEdges = repoEdges.filter(isVisibleRepoEdge);
9939
+ const hiddenRepoEdges = repoEdges.filter((edge) => !isVisibleRepoEdge(edge));
9940
+ const hiddenFileEdges = allFileEdges.filter(
9941
+ (edge) => edge.confidence < MIN_FILE_EDGE_CONFIDENCE || edge.evidenceCount < 1
9942
+ );
9943
+ const visibleFileEdges = allFileEdges.filter((edge) => !hiddenFileEdges.includes(edge));
9944
+ const quality = buildQuality(visibleRepoEdges, hiddenRepoEdges);
9563
9945
  options.onProgress?.({
9564
9946
  stage: "writing_org_graph",
9565
9947
  org: config.org,
9566
- edges: edges.length,
9948
+ edges: repoEdges.length,
9567
9949
  apiContracts: apiContracts.length,
9568
9950
  apiConsumers: apiConsumers.length
9569
9951
  });
9570
9952
  const now = (/* @__PURE__ */ new Date()).toISOString();
9953
+ const renderPrepStartedAt = Date.now();
9571
9954
  const transaction = db.transaction(() => {
9572
9955
  db.prepare("DELETE FROM org_cross_repo_edges WHERE org = ?").run(config.org);
9573
9956
  db.prepare("DELETE FROM org_api_contracts WHERE org = ?").run(config.org);
9574
9957
  db.prepare("DELETE FROM org_api_consumers WHERE org = ?").run(config.org);
9575
9958
  const insertEdge = db.prepare(
9576
9959
  `INSERT INTO org_cross_repo_edges
9577
- (id, org, source_repo, source_path, target_repo, target_path, relationship, evidence_json, confidence, created_at)
9578
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
9960
+ (id, org, source_repo, source_path, target_repo, target_path, layer, relationship,
9961
+ evidence_json, match_reasons_json, evidence_count, is_weak, confidence, created_at)
9962
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
9579
9963
  ON CONFLICT(id) DO UPDATE SET
9964
+ source_path = excluded.source_path,
9965
+ target_path = excluded.target_path,
9966
+ layer = excluded.layer,
9580
9967
  evidence_json = excluded.evidence_json,
9968
+ match_reasons_json = excluded.match_reasons_json,
9969
+ evidence_count = excluded.evidence_count,
9970
+ is_weak = excluded.is_weak,
9581
9971
  confidence = excluded.confidence,
9582
9972
  created_at = excluded.created_at`
9583
9973
  );
9584
- for (const [index, edge] of edges.entries()) {
9974
+ const persistEdge = (edge) => {
9585
9975
  insertEdge.run(
9586
- `oge_${stableId([edge.org, edge.sourceRepo, edge.sourcePath, edge.targetRepo, edge.targetPath ?? "", edge.relationship])}`,
9976
+ `oge_${stableId([
9977
+ edge.org,
9978
+ edge.layer,
9979
+ edge.sourceRepo,
9980
+ edge.sourcePath,
9981
+ edge.targetRepo,
9982
+ edge.targetPath ?? "",
9983
+ edge.relationship
9984
+ ])}`,
9587
9985
  edge.org,
9588
9986
  edge.sourceRepo,
9589
9987
  edge.sourcePath,
9590
9988
  edge.targetRepo,
9591
9989
  edge.targetPath ?? null,
9990
+ edge.layer,
9592
9991
  edge.relationship,
9593
9992
  evidenceJson(edge.evidence),
9993
+ JSON.stringify(edge.matchReasons),
9994
+ edge.evidenceCount,
9995
+ edge.weak ? 1 : 0,
9594
9996
  edge.confidence,
9595
9997
  now
9596
9998
  );
9999
+ };
10000
+ const persistedEdges = [...repoEdges, ...allFileEdges];
10001
+ for (const [index, edge] of persistedEdges.entries()) {
10002
+ persistEdge(edge);
9597
10003
  const current = index + 1;
9598
- if (shouldEmitProgress3(current, edges.length, 500)) {
10004
+ if (shouldEmitProgress3(current, persistedEdges.length, 500)) {
9599
10005
  options.onProgress?.({
9600
10006
  stage: "writing_org_graph",
9601
10007
  org: config.org,
@@ -9603,7 +10009,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9603
10009
  apiContracts: apiContracts.length,
9604
10010
  apiConsumers: apiConsumers.length,
9605
10011
  current,
9606
- total: edges.length,
10012
+ total: persistedEdges.length,
9607
10013
  kind: "edges"
9608
10014
  });
9609
10015
  }
@@ -9620,7 +10026,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9620
10026
  );
9621
10027
  for (const [index, contract] of apiContracts.entries()) {
9622
10028
  insertContract.run(
9623
- `oac_${stableId([config.org, contract.repo, contract.filePath, contract.contract])}`,
10029
+ `oac_${stableId([config.org, contract.repo, contract.filePath, contract.normalizedContract])}`,
9624
10030
  config.org,
9625
10031
  contract.repo,
9626
10032
  contract.filePath,
@@ -9634,7 +10040,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9634
10040
  options.onProgress?.({
9635
10041
  stage: "writing_org_graph",
9636
10042
  org: config.org,
9637
- edges: edges.length,
10043
+ edges: persistedEdges.length,
9638
10044
  apiContracts: current,
9639
10045
  apiConsumers: apiConsumers.length,
9640
10046
  current,
@@ -9645,11 +10051,15 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9645
10051
  }
9646
10052
  const insertConsumer = db.prepare(
9647
10053
  `INSERT INTO org_api_consumers
9648
- (id, org, provider_repo, provider_path, consumer_repo, consumer_path, contract, evidence_json, confidence, created_at)
9649
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
10054
+ (id, org, provider_repo, provider_path, consumer_repo, consumer_path, contract, evidence_json,
10055
+ match_reasons_json, evidence_count, is_weak, confidence, created_at)
10056
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
9650
10057
  ON CONFLICT(id) DO UPDATE SET
9651
10058
  contract = excluded.contract,
9652
10059
  evidence_json = excluded.evidence_json,
10060
+ match_reasons_json = excluded.match_reasons_json,
10061
+ evidence_count = excluded.evidence_count,
10062
+ is_weak = excluded.is_weak,
9653
10063
  confidence = excluded.confidence,
9654
10064
  created_at = excluded.created_at`
9655
10065
  );
@@ -9661,7 +10071,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9661
10071
  consumer.providerPath ?? "",
9662
10072
  consumer.consumerRepo,
9663
10073
  consumer.consumerPath,
9664
- consumer.contract
10074
+ normalizeToken(consumer.contract)
9665
10075
  ])}`,
9666
10076
  consumer.org,
9667
10077
  consumer.providerRepo,
@@ -9670,6 +10080,9 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9670
10080
  consumer.consumerPath,
9671
10081
  sanitizeHistoricalText(consumer.contract),
9672
10082
  evidenceJson(consumer.evidence),
10083
+ JSON.stringify(consumer.matchReasons),
10084
+ consumer.evidenceCount,
10085
+ consumer.weak ? 1 : 0,
9673
10086
  consumer.confidence,
9674
10087
  now
9675
10088
  );
@@ -9678,7 +10091,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9678
10091
  options.onProgress?.({
9679
10092
  stage: "writing_org_graph",
9680
10093
  org: config.org,
9681
- edges: edges.length,
10094
+ edges: persistedEdges.length,
9682
10095
  apiContracts: apiContracts.length,
9683
10096
  apiConsumers: current,
9684
10097
  current,
@@ -9689,6 +10102,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9689
10102
  }
9690
10103
  });
9691
10104
  transaction();
10105
+ const renderPrepMs = Date.now() - renderPrepStartedAt;
9692
10106
  const durationMs = Date.now() - startedAt;
9693
10107
  const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
9694
10108
  recordOrgGraphState(db, {
@@ -9696,22 +10110,40 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9696
10110
  status: "success",
9697
10111
  builtAt: finishedAt,
9698
10112
  durationMs,
9699
- edgeCount: edges.length,
10113
+ edgeCount: repoEdges.length,
10114
+ visibleEdgeCount: visibleRepoEdges.length,
10115
+ weakEdgeCount: hiddenRepoEdges.length,
10116
+ edgeConfidenceDistribution: quality.edgeConfidenceDistribution,
10117
+ lastRenderPrepMs: renderPrepMs,
9700
10118
  apiContractCount: apiContracts.length,
9701
10119
  apiConsumerCount: apiConsumers.length
9702
10120
  });
9703
10121
  options.onProgress?.({
9704
10122
  stage: "completed_org_graph",
9705
10123
  org: config.org,
9706
- edges: edges.length,
10124
+ edges: repoEdges.length,
9707
10125
  apiContracts: apiContracts.length,
9708
10126
  apiConsumers: apiConsumers.length,
9709
10127
  durationMs
9710
10128
  });
9711
10129
  return {
9712
- edges,
10130
+ edges: visibleRepoEdges,
10131
+ repoEdges: visibleRepoEdges,
10132
+ fileEdges: visibleFileEdges,
10133
+ hiddenFileEdges,
10134
+ hiddenRepoEdges,
9713
10135
  apiConsumers,
9714
- apiContracts,
10136
+ apiContracts: apiContracts.map((contract) => ({
10137
+ repo: contract.repo,
10138
+ filePath: contract.filePath,
10139
+ contract: contract.contract,
10140
+ evidence: contract.evidence,
10141
+ confidence: contract.confidence
10142
+ })),
10143
+ quality: {
10144
+ ...quality,
10145
+ lastRenderPrepMs: renderPrepMs
10146
+ },
9715
10147
  durationMs
9716
10148
  };
9717
10149
  } catch (error) {
@@ -10055,6 +10487,8 @@ async function indexOrgRepos(db, config, options = {}) {
10055
10487
  org: config.org,
10056
10488
  status: "skipped",
10057
10489
  edgeCount: counts.edges,
10490
+ visibleEdgeCount: counts.visibleEdges,
10491
+ weakEdgeCount: counts.weakEdges,
10058
10492
  apiContractCount: counts.apiContracts,
10059
10493
  apiConsumerCount: counts.apiConsumers
10060
10494
  });
@@ -10064,7 +10498,12 @@ async function indexOrgRepos(db, config, options = {}) {
10064
10498
  command,
10065
10499
  reason: "Graph skipped because --no-graph was passed."
10066
10500
  });
10067
- graph = { ...counts, skipped: true };
10501
+ graph = {
10502
+ edges: counts.visibleEdges,
10503
+ apiConsumers: counts.apiConsumers,
10504
+ apiContracts: counts.apiContracts,
10505
+ skipped: true
10506
+ };
10068
10507
  } else {
10069
10508
  try {
10070
10509
  const rebuiltGraph = rebuildOrgGraph(db, config, {
@@ -10079,7 +10518,12 @@ async function indexOrgRepos(db, config, options = {}) {
10079
10518
  } catch (error) {
10080
10519
  const message = error instanceof Error ? error.message : String(error);
10081
10520
  const counts = getOrgGraphCounts(db, config.org);
10082
- graph = { ...counts, error: message };
10521
+ graph = {
10522
+ edges: counts.visibleEdges,
10523
+ apiConsumers: counts.apiConsumers,
10524
+ apiContracts: counts.apiContracts,
10525
+ error: message
10526
+ };
10083
10527
  }
10084
10528
  }
10085
10529
  recordOrgIndexRun(db, {
@@ -10123,6 +10567,15 @@ function parseEvidence2(value) {
10123
10567
  return [];
10124
10568
  }
10125
10569
  }
10570
+ function parseStringArray(value) {
10571
+ if (!value) return [];
10572
+ try {
10573
+ const parsed = JSON.parse(value);
10574
+ return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
10575
+ } catch {
10576
+ return [];
10577
+ }
10578
+ }
10126
10579
  function fileEvidence2(repo, filePath, note) {
10127
10580
  return {
10128
10581
  prNumber: 0,
@@ -10154,7 +10607,7 @@ function affectedConsumers(db, org, repo, changedFiles) {
10154
10607
  const rows = db.prepare(
10155
10608
  `SELECT provider_repo, provider_path, consumer_repo, consumer_path, contract, evidence_json, confidence
10156
10609
  FROM org_api_consumers
10157
- WHERE org = ?`
10610
+ WHERE org = ? AND is_weak = 0`
10158
10611
  ).all(org);
10159
10612
  return rows.filter((row) => !repo || row.provider_repo === repo || row.consumer_repo === repo).filter((row) => {
10160
10613
  if (changedFiles.length === 0) return true;
@@ -10169,14 +10622,18 @@ function affectedConsumers(db, org, repo, changedFiles) {
10169
10622
  consumerPath: row.consumer_path,
10170
10623
  contract: sanitizeHistoricalText(row.contract),
10171
10624
  evidence: parseEvidence2(row.evidence_json),
10625
+ matchReasons: parseStringArray(row.match_reasons_json),
10626
+ evidenceCount: row.evidence_count ?? parseEvidence2(row.evidence_json).length,
10627
+ weak: (row.is_weak ?? 0) === 1,
10172
10628
  confidence: row.confidence
10173
10629
  }));
10174
10630
  }
10175
10631
  function affectedEdges(db, org, repo, changedFiles) {
10176
10632
  const rows = db.prepare(
10177
- `SELECT source_repo, source_path, target_repo, target_path, relationship, evidence_json, confidence
10633
+ `SELECT source_repo, source_path, target_repo, target_path, layer, relationship, evidence_json,
10634
+ match_reasons_json, evidence_count, is_weak, confidence
10178
10635
  FROM org_cross_repo_edges
10179
- WHERE org = ?`
10636
+ WHERE org = ? AND layer = 'file'`
10180
10637
  ).all(org);
10181
10638
  return rows.filter((row) => !repo || row.source_repo === repo || row.target_repo === repo).filter((row) => {
10182
10639
  if (changedFiles.length === 0) return true;
@@ -10187,8 +10644,12 @@ function affectedEdges(db, org, repo, changedFiles) {
10187
10644
  sourcePath: row.source_path,
10188
10645
  targetRepo: row.target_repo,
10189
10646
  targetPath: row.target_path ?? void 0,
10647
+ layer: row.layer,
10190
10648
  relationship: row.relationship,
10191
10649
  evidence: parseEvidence2(row.evidence_json),
10650
+ matchReasons: parseStringArray(row.match_reasons_json),
10651
+ evidenceCount: row.evidence_count ?? parseEvidence2(row.evidence_json).length,
10652
+ weak: (row.is_weak ?? 0) === 1,
10192
10653
  confidence: row.confidence
10193
10654
  }));
10194
10655
  }
@@ -10506,7 +10967,7 @@ function evidenceLabel(evidence) {
10506
10967
  if (first.prNumber > 0) return `PR #${first.prNumber}`;
10507
10968
  return first.filePath ? `file ${first.filePath}` : first.note ?? "local file evidence";
10508
10969
  }
10509
- function parseStringArray(value) {
10970
+ function parseStringArray2(value) {
10510
10971
  try {
10511
10972
  const parsed = JSON.parse(value);
10512
10973
  return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
@@ -10553,7 +11014,7 @@ function getWisdom(db, input, limit) {
10553
11014
  const lowerTerms = queryTerms(input).map((term) => term.toLowerCase());
10554
11015
  return rows.filter((row) => matchesRepo(row.repo, input.repos)).map((row) => ({
10555
11016
  row,
10556
- score: rowScore(input, row.sanitized_text, parseStringArray(row.file_paths_json), [], lowerTerms)
11017
+ score: rowScore(input, row.sanitized_text, parseStringArray2(row.file_paths_json), [], lowerTerms)
10557
11018
  })).filter((item) => item.score > 0 || (input.files ?? []).length === 0).sort((a, b) => b.score - a.score || b.row.confidence - a.row.confidence).slice(0, limit).map((item) => item.row);
10558
11019
  }
10559
11020
  function getCodeEvidence(db, input, limit) {
@@ -10570,7 +11031,7 @@ function getCodeEvidence(db, input, limit) {
10570
11031
  input,
10571
11032
  row.sanitized_text,
10572
11033
  [row.file_path],
10573
- parseStringArray(row.symbols_json),
11034
+ parseStringArray2(row.symbols_json),
10574
11035
  lowerTerms
10575
11036
  )
10576
11037
  })).filter((item) => item.score > 0).sort((a, b) => b.score - a.score).slice(0, limit).map((item) => item.row);
@@ -10588,7 +11049,7 @@ function getArchitecture(db, input, limit) {
10588
11049
  score: rowScore(
10589
11050
  input,
10590
11051
  row.summary_sanitized,
10591
- parseStringArray(row.source_files_json),
11052
+ parseStringArray2(row.source_files_json),
10592
11053
  [],
10593
11054
  lowerTerms
10594
11055
  )
@@ -10599,9 +11060,10 @@ function findOrgApiConsumers(db, config, input) {
10599
11060
  const repoClause = input.repo ? " AND (provider_repo = ? OR consumer_repo = ?)" : "";
10600
11061
  const repoParams = input.repo ? [input.repo, input.repo] : [];
10601
11062
  const rows = db.prepare(
10602
- `SELECT provider_repo, provider_path, consumer_repo, consumer_path, contract, evidence_json, confidence
11063
+ `SELECT provider_repo, provider_path, consumer_repo, consumer_path, contract, evidence_json,
11064
+ match_reasons_json, evidence_count, is_weak, confidence
10603
11065
  FROM org_api_consumers
10604
- WHERE org = ?${repoClause}
11066
+ WHERE org = ? AND is_weak = 0${repoClause}
10605
11067
  ORDER BY confidence DESC`
10606
11068
  ).all(config.org, ...repoParams);
10607
11069
  const limit = Math.max(1, Math.min(input.maxResults ?? 8, 25));
@@ -10617,6 +11079,9 @@ function findOrgApiConsumers(db, config, input) {
10617
11079
  consumerPath: row.consumer_path,
10618
11080
  contract: sanitizeHistoricalText(row.contract),
10619
11081
  evidence: parseEvidence3(row.evidence_json),
11082
+ matchReasons: parseStringArray2(row.match_reasons_json ?? "[]"),
11083
+ evidenceCount: row.evidence_count ?? parseEvidence3(row.evidence_json).length,
11084
+ weak: (row.is_weak ?? 0) === 1,
10620
11085
  confidence: row.confidence
10621
11086
  }));
10622
11087
  }
@@ -10625,7 +11090,7 @@ function getOrgArchitectureMap(db, config, format = "mermaid") {
10625
11090
  const rows = db.prepare(
10626
11091
  `SELECT source_repo, source_path, target_repo, target_path, relationship, confidence
10627
11092
  FROM org_cross_repo_edges
10628
- WHERE org = ?
11093
+ WHERE org = ? AND layer = 'repo' AND is_weak = 0
10629
11094
  ORDER BY confidence DESC, source_repo, target_repo`
10630
11095
  ).all(config.org);
10631
11096
  const nodes = uniqueStrings(rows.flatMap((row) => [row.source_repo, row.target_repo])).map(
@@ -10703,7 +11168,7 @@ function buildOrgContextResult(db, config, input) {
10703
11168
  if (architecture.length === 0) lines.push("- No matching architecture patterns found.");
10704
11169
  else {
10705
11170
  for (const pattern of architecture) {
10706
- const files = parseStringArray(pattern.source_files_json);
11171
+ const files = parseStringArray2(pattern.source_files_json);
10707
11172
  lines.push(
10708
11173
  `- [${pattern.repo}] [${pattern.area}] ${pattern.summary_sanitized} Evidence: ${files[0] ?? "indexed current code"}.`
10709
11174
  );