@pratik7368patil/anchor-core 0.1.32 → 0.1.34

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,
@@ -709,6 +720,7 @@ CREATE INDEX IF NOT EXISTS idx_org_edges_source ON org_cross_repo_edges(org, sou
709
720
  CREATE INDEX IF NOT EXISTS idx_org_edges_target ON org_cross_repo_edges(org, target_repo);
710
721
  CREATE INDEX IF NOT EXISTS idx_org_consumers_provider ON org_api_consumers(org, provider_repo);
711
722
  CREATE INDEX IF NOT EXISTS idx_org_consumers_consumer ON org_api_consumers(org, consumer_repo);
723
+ CREATE INDEX IF NOT EXISTS idx_org_consumers_contract ON org_api_consumers(org, contract);
712
724
  CREATE INDEX IF NOT EXISTS idx_org_anomalies_org ON org_anomaly_events(org, severity);
713
725
  CREATE INDEX IF NOT EXISTS idx_org_graph_state_status ON org_graph_state(org, last_status);
714
726
 
@@ -1783,6 +1795,41 @@ function initializeSchema(db) {
1783
1795
  ensureColumn(db, "sync_state", "graphql_cursor_reason", "TEXT");
1784
1796
  ensureColumn(db, "sync_state", "graphql_cursor_updated_at", "TEXT");
1785
1797
  ensureColumn(db, "code_index_state", "last_indexed_commit", "TEXT");
1798
+ ensureColumn(db, "org_cross_repo_edges", "layer", "TEXT NOT NULL DEFAULT 'file'");
1799
+ ensureColumn(
1800
+ db,
1801
+ "org_cross_repo_edges",
1802
+ "match_reasons_json",
1803
+ "TEXT NOT NULL DEFAULT '[]'"
1804
+ );
1805
+ ensureColumn(db, "org_cross_repo_edges", "evidence_count", "INTEGER NOT NULL DEFAULT 0");
1806
+ ensureColumn(db, "org_cross_repo_edges", "is_weak", "INTEGER NOT NULL DEFAULT 0");
1807
+ ensureColumn(
1808
+ db,
1809
+ "org_api_consumers",
1810
+ "match_reasons_json",
1811
+ "TEXT NOT NULL DEFAULT '[]'"
1812
+ );
1813
+ ensureColumn(db, "org_api_consumers", "evidence_count", "INTEGER NOT NULL DEFAULT 0");
1814
+ ensureColumn(db, "org_api_consumers", "is_weak", "INTEGER NOT NULL DEFAULT 0");
1815
+ ensureColumn(db, "org_graph_state", "visible_edge_count", "INTEGER NOT NULL DEFAULT 0");
1816
+ ensureColumn(db, "org_graph_state", "weak_edge_count", "INTEGER NOT NULL DEFAULT 0");
1817
+ ensureColumn(
1818
+ db,
1819
+ "org_graph_state",
1820
+ "edge_confidence_json",
1821
+ `TEXT NOT NULL DEFAULT '{"strong":0,"moderate":0,"weak":0}'`
1822
+ );
1823
+ ensureColumn(db, "org_graph_state", "last_render_prep_ms", "INTEGER");
1824
+ db.exec(
1825
+ "CREATE INDEX IF NOT EXISTS idx_org_edges_layer ON org_cross_repo_edges(org, layer, confidence)"
1826
+ );
1827
+ db.exec(
1828
+ "CREATE INDEX IF NOT EXISTS idx_org_edges_repo_pair ON org_cross_repo_edges(org, layer, source_repo, target_repo, relationship)"
1829
+ );
1830
+ db.exec(
1831
+ "CREATE INDEX IF NOT EXISTS idx_org_consumers_contract ON org_api_consumers(org, contract)"
1832
+ );
1786
1833
  }
1787
1834
  function ensureColumn(db, tableName, columnName, definition) {
1788
1835
  const columns = db.prepare(`PRAGMA table_info(${tableName})`).all();
@@ -8706,6 +8753,24 @@ function resolveOrgForTool(org, baseDir = defaultOrgBaseDir()) {
8706
8753
 
8707
8754
  // src/org/database.ts
8708
8755
  import fs10 from "fs";
8756
+ var DEFAULT_EDGE_DISTRIBUTION = {
8757
+ strong: 0,
8758
+ moderate: 0,
8759
+ weak: 0
8760
+ };
8761
+ function parseEdgeDistribution(value) {
8762
+ if (!value) return { ...DEFAULT_EDGE_DISTRIBUTION };
8763
+ try {
8764
+ const parsed = JSON.parse(value);
8765
+ return {
8766
+ strong: Number(parsed.strong ?? 0),
8767
+ moderate: Number(parsed.moderate ?? 0),
8768
+ weak: Number(parsed.weak ?? 0)
8769
+ };
8770
+ } catch {
8771
+ return { ...DEFAULT_EDGE_DISTRIBUTION };
8772
+ }
8773
+ }
8709
8774
  function openOrgDatabase(org, baseDir) {
8710
8775
  const root = orgRoot(org, baseDir);
8711
8776
  const db = openAnchorDatabase(root, orgDatabasePath(org, baseDir));
@@ -8835,8 +8900,9 @@ function recordOrgGraphState(db, input) {
8835
8900
  db.prepare(
8836
8901
  `INSERT INTO org_graph_state
8837
8902
  (org, last_built_at, last_status, last_duration_ms, edge_count, api_contract_count,
8838
- api_consumer_count, last_error, updated_at)
8839
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
8903
+ api_consumer_count, visible_edge_count, weak_edge_count, edge_confidence_json,
8904
+ last_render_prep_ms, last_error, updated_at)
8905
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
8840
8906
  ON CONFLICT(org) DO UPDATE SET
8841
8907
  last_built_at = COALESCE(excluded.last_built_at, org_graph_state.last_built_at),
8842
8908
  last_status = excluded.last_status,
@@ -8844,6 +8910,10 @@ function recordOrgGraphState(db, input) {
8844
8910
  edge_count = excluded.edge_count,
8845
8911
  api_contract_count = excluded.api_contract_count,
8846
8912
  api_consumer_count = excluded.api_consumer_count,
8913
+ visible_edge_count = excluded.visible_edge_count,
8914
+ weak_edge_count = excluded.weak_edge_count,
8915
+ edge_confidence_json = excluded.edge_confidence_json,
8916
+ last_render_prep_ms = excluded.last_render_prep_ms,
8847
8917
  last_error = excluded.last_error,
8848
8918
  updated_at = excluded.updated_at`
8849
8919
  ).run(
@@ -8854,6 +8924,10 @@ function recordOrgGraphState(db, input) {
8854
8924
  input.edgeCount ?? 0,
8855
8925
  input.apiContractCount ?? 0,
8856
8926
  input.apiConsumerCount ?? 0,
8927
+ input.visibleEdgeCount ?? input.edgeCount ?? 0,
8928
+ input.weakEdgeCount ?? 0,
8929
+ JSON.stringify(input.edgeConfidenceDistribution ?? DEFAULT_EDGE_DISTRIBUTION),
8930
+ input.lastRenderPrepMs ?? null,
8857
8931
  input.error ?? null,
8858
8932
  now
8859
8933
  );
@@ -8868,6 +8942,10 @@ function getOrgGraphState(db, org) {
8868
8942
  lastStatus: row.last_status ?? void 0,
8869
8943
  lastDurationMs: row.last_duration_ms ?? void 0,
8870
8944
  edgeCount: row.edge_count ?? void 0,
8945
+ visibleEdgeCount: row.visible_edge_count ?? void 0,
8946
+ weakEdgeCount: row.weak_edge_count ?? void 0,
8947
+ edgeConfidenceDistribution: parseEdgeDistribution(row.edge_confidence_json),
8948
+ lastRenderPrepMs: row.last_render_prep_ms ?? void 0,
8871
8949
  apiContractCount: row.api_contract_count ?? void 0,
8872
8950
  apiConsumerCount: row.api_consumer_count ?? void 0,
8873
8951
  lastError: row.last_error ?? void 0
@@ -8877,10 +8955,44 @@ function count(db, table, where = "", params = []) {
8877
8955
  const row = db.prepare(`SELECT COUNT(*) AS count FROM ${table} ${where}`).get(...params);
8878
8956
  return row.count;
8879
8957
  }
8958
+ function tableHasColumn(db, table, column) {
8959
+ try {
8960
+ const rows = db.prepare(`PRAGMA table_info(${table})`).all();
8961
+ return rows.some((row) => row.name === column);
8962
+ } catch {
8963
+ return false;
8964
+ }
8965
+ }
8966
+ function orgEdgeCountWhere(hasLayer, hasWeakFlag, filter) {
8967
+ const clauses = ["org = ?"];
8968
+ if (hasLayer) clauses.push("layer = 'repo'");
8969
+ if (filter === "visible" && hasWeakFlag) clauses.push("is_weak = 0");
8970
+ if (filter === "weak") {
8971
+ if (hasWeakFlag) clauses.push("is_weak = 1");
8972
+ else clauses.push("1 = 0");
8973
+ }
8974
+ return `WHERE ${clauses.join(" AND ")}`;
8975
+ }
8880
8976
  function getOrgGraphCounts(db, org) {
8881
8977
  initializeSchema(db);
8978
+ const hasLayer = tableHasColumn(db, "org_cross_repo_edges", "layer");
8979
+ const hasWeakFlag = tableHasColumn(db, "org_cross_repo_edges", "is_weak");
8882
8980
  return {
8883
- edges: count(db, "org_cross_repo_edges", "WHERE org = ?", [org]),
8981
+ edges: count(db, "org_cross_repo_edges", orgEdgeCountWhere(hasLayer, hasWeakFlag, "all"), [
8982
+ org
8983
+ ]),
8984
+ visibleEdges: count(
8985
+ db,
8986
+ "org_cross_repo_edges",
8987
+ orgEdgeCountWhere(hasLayer, hasWeakFlag, "visible"),
8988
+ [org]
8989
+ ),
8990
+ weakEdges: count(
8991
+ db,
8992
+ "org_cross_repo_edges",
8993
+ orgEdgeCountWhere(hasLayer, hasWeakFlag, "weak"),
8994
+ [org]
8995
+ ),
8884
8996
  apiContracts: count(db, "org_api_contracts", "WHERE org = ?", [org]),
8885
8997
  apiConsumers: count(db, "org_api_consumers", "WHERE org = ?", [org])
8886
8998
  };
@@ -8907,11 +9019,24 @@ function getOrgStatus(db, config, baseDir, options = {}) {
8907
9019
  const codeFileCount = count(db, "code_files");
8908
9020
  const codeChunkCount = count(db, "code_chunks");
8909
9021
  const wisdomUnitCount = count(db, "wisdom_units");
8910
- const crossRepoEdgeCount = count(db, "org_cross_repo_edges", "WHERE org = ?", [config.org]);
9022
+ const hasLayer = tableHasColumn(db, "org_cross_repo_edges", "layer");
9023
+ const hasWeakFlag = tableHasColumn(db, "org_cross_repo_edges", "is_weak");
9024
+ const crossRepoEdgeCount = count(
9025
+ db,
9026
+ "org_cross_repo_edges",
9027
+ orgEdgeCountWhere(hasLayer, hasWeakFlag, "visible"),
9028
+ [config.org]
9029
+ );
9030
+ const graphWeakEdgeCount = count(
9031
+ db,
9032
+ "org_cross_repo_edges",
9033
+ orgEdgeCountWhere(hasLayer, hasWeakFlag, "weak"),
9034
+ [config.org]
9035
+ );
8911
9036
  const apiContractCount = count(db, "org_api_contracts", "WHERE org = ?", [config.org]);
8912
9037
  const apiConsumerCount = count(db, "org_api_consumers", "WHERE org = ?", [config.org]);
8913
9038
  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);
9039
+ const graphState = getOrgGraphState(db, config.org);
8915
9040
  let score = 0;
8916
9041
  const reasons = [];
8917
9042
  if (enabledRepos.length > 0) {
@@ -8955,10 +9080,14 @@ function getOrgStatus(db, config, baseDir, options = {}) {
8955
9080
  apiContractCount,
8956
9081
  apiConsumerCount,
8957
9082
  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,
9083
+ graphLastBuiltAt: graphState?.lastBuiltAt,
9084
+ graphLastStatus: graphState?.lastStatus,
9085
+ graphLastDurationMs: graphState?.lastDurationMs,
9086
+ graphLastError: graphState?.lastError,
9087
+ graphVisibleEdgeCount: graphState?.visibleEdgeCount ?? crossRepoEdgeCount,
9088
+ graphWeakEdgeCount: graphState?.weakEdgeCount ?? graphWeakEdgeCount,
9089
+ graphRenderPrepMs: graphState?.lastRenderPrepMs,
9090
+ graphEdgeConfidenceDistribution: graphState?.edgeConfidenceDistribution ?? { ...DEFAULT_EDGE_DISTRIBUTION },
8962
9091
  coverageScore: score,
8963
9092
  coverageGrade: grade(score),
8964
9093
  coverageReasons: reasons,
@@ -9262,6 +9391,34 @@ function orgCloneStateFromResult(org, repo, result) {
9262
9391
  import crypto9 from "crypto";
9263
9392
  import fs13 from "fs";
9264
9393
  import path23 from "path";
9394
+ var MIN_FILE_EDGE_CONFIDENCE = 0.62;
9395
+ var MIN_REPO_EDGE_CONFIDENCE = 0.7;
9396
+ var MIN_VISIBLE_EVIDENCE = 2;
9397
+ var MIN_API_CONSUMER_CONFIDENCE = 0.68;
9398
+ var MAX_EDGE_EVIDENCE = 8;
9399
+ var MAX_EDGE_REASONS = 6;
9400
+ var MAX_CONTRACTS_PER_CHUNK = 24;
9401
+ var CONTRACT_IGNORE = /* @__PURE__ */ new Set([
9402
+ "api",
9403
+ "v1",
9404
+ "v2",
9405
+ "v3",
9406
+ "graphql",
9407
+ "query",
9408
+ "mutation",
9409
+ "subscription",
9410
+ "schema",
9411
+ "route",
9412
+ "routes",
9413
+ "controller",
9414
+ "client",
9415
+ "request"
9416
+ ]);
9417
+ var DEFAULT_EDGE_DISTRIBUTION2 = {
9418
+ strong: 0,
9419
+ moderate: 0,
9420
+ weak: 0
9421
+ };
9265
9422
  function stableId(parts) {
9266
9423
  return crypto9.createHash("sha256").update(parts.join("\0")).digest("hex").slice(0, 32);
9267
9424
  }
@@ -9274,6 +9431,9 @@ function fileEvidence(repo, filePath, note) {
9274
9431
  note
9275
9432
  };
9276
9433
  }
9434
+ function evidenceJson(evidence) {
9435
+ return JSON.stringify(evidence);
9436
+ }
9277
9437
  function readPackageManifest(repoPath) {
9278
9438
  const packagePath = path23.join(repoPath, "package.json");
9279
9439
  if (!fs13.existsSync(packagePath)) return void 0;
@@ -9319,19 +9479,53 @@ function parseJsonArray10(value) {
9319
9479
  return [];
9320
9480
  }
9321
9481
  }
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);
9482
+ function normalizeToken(value) {
9483
+ return value.toLowerCase().replace(/[{}()[\],:"'`]/g, "").replace(/\/+/g, "/").replace(/\/$/g, "").replace(/^-+/g, "").trim();
9484
+ }
9485
+ function splitTokenSymbols(value) {
9486
+ return value.split(/[^A-Za-z0-9_/-]+/).map((item) => normalizeToken(item)).filter((item) => item.length >= 3 && !CONTRACT_IGNORE.has(item)).slice(0, 12);
9487
+ }
9488
+ function normalizeContract(contract, kind) {
9489
+ if (kind === "route") {
9490
+ const normalized = normalizeToken(contract);
9491
+ return normalized.startsWith("/") ? normalized : `/${normalized}`;
9333
9492
  }
9334
- return uniqueStrings(contracts).slice(0, 20);
9493
+ return normalizeToken(contract);
9494
+ }
9495
+ function isGenericRoute(route) {
9496
+ const normalized = normalizeToken(route);
9497
+ if (!normalized.startsWith("/")) return true;
9498
+ const segments = normalized.split("/").filter((segment) => segment && !segment.startsWith(":") && segment !== "*");
9499
+ if (segments.length === 0) return true;
9500
+ const informative = segments.filter((segment) => !CONTRACT_IGNORE.has(segment));
9501
+ return informative.length < 1;
9502
+ }
9503
+ function extractContracts(text) {
9504
+ const tokens = [];
9505
+ const seen = /* @__PURE__ */ new Set();
9506
+ const pushToken = (rawValue, kind) => {
9507
+ const sanitized = sanitizeHistoricalText(rawValue).slice(0, 180);
9508
+ if (!sanitized) return;
9509
+ const normalized = normalizeContract(sanitized, kind);
9510
+ if (!normalized || CONTRACT_IGNORE.has(normalized)) return;
9511
+ if (kind === "route" && isGenericRoute(normalized)) return;
9512
+ const key = `${kind}\0${normalized}`;
9513
+ if (seen.has(key)) return;
9514
+ seen.add(key);
9515
+ tokens.push({
9516
+ raw: sanitized,
9517
+ normalized,
9518
+ kind,
9519
+ symbols: splitTokenSymbols(sanitized)
9520
+ });
9521
+ };
9522
+ const routeMatches = text.matchAll(/["'`]((?:\/api)?\/[A-Za-z0-9_./:{}-]{3,})["'`]/g);
9523
+ for (const match of routeMatches) pushToken(match[1] ?? "", "route");
9524
+ const gqlMatches = text.matchAll(/\b(query|mutation|subscription)\s+([A-Za-z][A-Za-z0-9_]{2,})/g);
9525
+ for (const match of gqlMatches) pushToken(match[2] ?? "", "graphql");
9526
+ const schemaMatches = text.matchAll(/\b(?:type|interface|enum|input)\s+([A-Z][A-Za-z0-9_]{2,})\b/g);
9527
+ for (const match of schemaMatches) pushToken(match[1] ?? "", "schema");
9528
+ return tokens.slice(0, MAX_CONTRACTS_PER_CHUNK);
9335
9529
  }
9336
9530
  function isApiProviderPath(filePath) {
9337
9531
  const normalized = filePath.toLowerCase();
@@ -9342,28 +9536,216 @@ function isApiProviderPath(filePath) {
9342
9536
  function isApiConsumerText(text) {
9343
9537
  return /\b(fetch|axios|ky|graphql|gql|client|sdk|request)\b/i.test(text);
9344
9538
  }
9345
- function evidenceJson(evidence) {
9346
- return JSON.stringify(evidence);
9347
- }
9348
9539
  function shouldEmitProgress3(current, total, interval = 100) {
9349
9540
  return current === 1 || current === total || current % interval === 0;
9350
9541
  }
9351
9542
  function resolveOptions(baseDirOrOptions) {
9352
9543
  return typeof baseDirOrOptions === "string" ? { baseDir: baseDirOrOptions } : baseDirOrOptions ?? {};
9353
9544
  }
9545
+ function clampConfidence(value) {
9546
+ if (Number.isNaN(value)) return 0;
9547
+ return Math.max(0, Math.min(0.99, Number(value.toFixed(3))));
9548
+ }
9549
+ function confidenceBucket(confidence) {
9550
+ if (confidence >= 0.82) return "strong";
9551
+ if (confidence >= 0.68) return "moderate";
9552
+ return "weak";
9553
+ }
9554
+ function uniqueEvidenceRefs(evidence) {
9555
+ const map = /* @__PURE__ */ new Map();
9556
+ for (const item of evidence) {
9557
+ const key = `${item.prNumber}|${item.prUrl}|${item.sourceType}|${item.filePath ?? ""}|${item.note ?? ""}`;
9558
+ if (!map.has(key)) map.set(key, item);
9559
+ }
9560
+ return [...map.values()].slice(0, MAX_EDGE_EVIDENCE);
9561
+ }
9562
+ function mergeReasons(a, b) {
9563
+ return uniqueStrings([...a, ...b]).slice(0, MAX_EDGE_REASONS);
9564
+ }
9565
+ function updateWeakFlag(edge, minConfidence2, minEvidence) {
9566
+ edge.evidence = uniqueEvidenceRefs(edge.evidence);
9567
+ edge.evidenceCount = edge.evidence.length;
9568
+ edge.confidence = clampConfidence(edge.confidence);
9569
+ edge.weak = edge.confidence < minConfidence2 || edge.evidenceCount < minEvidence;
9570
+ }
9571
+ function fileEdgeKey(edge) {
9572
+ return [
9573
+ edge.layer,
9574
+ edge.sourceRepo,
9575
+ edge.sourcePath,
9576
+ edge.targetRepo,
9577
+ edge.targetPath ?? "",
9578
+ edge.relationship
9579
+ ].join("\0");
9580
+ }
9581
+ function repoEdgeKey(edge) {
9582
+ return [edge.layer, edge.sourceRepo, edge.targetRepo, edge.relationship].join("\0");
9583
+ }
9584
+ function upsertEdge(map, edge, minConfidence2, minEvidence) {
9585
+ const layer = edge.layer ?? "file";
9586
+ if (edge.sourceRepo === edge.targetRepo) return { inserted: false, updated: false };
9587
+ const key = layer === "repo" ? repoEdgeKey({ ...edge, layer }) : fileEdgeKey({ ...edge, layer });
9588
+ const existing = map.get(key);
9589
+ if (!existing) {
9590
+ const created = {
9591
+ ...edge,
9592
+ layer,
9593
+ evidence: uniqueEvidenceRefs(edge.evidence),
9594
+ matchReasons: mergeReasons([], edge.matchReasons),
9595
+ evidenceCount: 0,
9596
+ weak: false
9597
+ };
9598
+ updateWeakFlag(created, minConfidence2, minEvidence);
9599
+ map.set(key, created);
9600
+ return { inserted: true, updated: false };
9601
+ }
9602
+ const merged = {
9603
+ ...existing,
9604
+ sourcePath: existing.layer === "repo" ? "*" : existing.sourcePath,
9605
+ targetPath: existing.layer === "repo" ? void 0 : existing.targetPath ?? edge.targetPath,
9606
+ evidence: uniqueEvidenceRefs([...existing.evidence, ...edge.evidence]),
9607
+ matchReasons: mergeReasons(existing.matchReasons, edge.matchReasons),
9608
+ confidence: Math.max(existing.confidence, edge.confidence),
9609
+ evidenceCount: 0,
9610
+ weak: false
9611
+ };
9612
+ updateWeakFlag(merged, minConfidence2, minEvidence);
9613
+ map.set(key, merged);
9614
+ return { inserted: false, updated: true };
9615
+ }
9616
+ function scorePackageDependency(dependency) {
9617
+ const normalized = sanitizeHistoricalText(dependency);
9618
+ const reasons = ["exact_package_dependency"];
9619
+ const confidence = normalized.startsWith("@") ? 0.93 : 0.9;
9620
+ return { confidence, reasons };
9621
+ }
9622
+ function scoreImportEdge(input) {
9623
+ let score = 0.58;
9624
+ const reasons = ["cross_repo_import"];
9625
+ if (input.specifier.includes("/")) {
9626
+ score += 0.12;
9627
+ reasons.push("qualified_specifier");
9628
+ }
9629
+ if (input.importedPath) {
9630
+ score += 0.16;
9631
+ reasons.push("resolved_import_path");
9632
+ }
9633
+ if (input.importedSymbols.length > 0) {
9634
+ score += 0.11;
9635
+ reasons.push("explicit_import_symbols");
9636
+ }
9637
+ return { confidence: clampConfidence(score), reasons };
9638
+ }
9639
+ function scoreContract(input) {
9640
+ let score = 0.66;
9641
+ if (input.token.kind === "route") score += 0.08;
9642
+ if (input.token.kind === "graphql") score += 0.06;
9643
+ if (input.token.kind === "schema") score += 0.03;
9644
+ if (/\b(route|controller|api|schema|client)\b/i.test(input.filePath)) score += 0.05;
9645
+ if (input.token.symbols.length > 0) score += 0.04;
9646
+ return clampConfidence(score);
9647
+ }
9648
+ function overlapScore(a, b) {
9649
+ if (a.length === 0 || b.length === 0) return 0;
9650
+ const set = new Set(a);
9651
+ let overlaps = 0;
9652
+ for (const value of b) {
9653
+ if (set.has(value)) overlaps += 1;
9654
+ }
9655
+ return overlaps / Math.max(1, Math.min(a.length, b.length));
9656
+ }
9657
+ function scoreConsumerMatch(input) {
9658
+ let score = 0.58;
9659
+ const reasons = ["matched_contract_token"];
9660
+ if (input.consumerToken.kind === input.contract.kind) {
9661
+ score += 0.12;
9662
+ reasons.push("matching_contract_kind");
9663
+ }
9664
+ const symbolOverlap = overlapScore(
9665
+ uniqueStrings([...input.contract.symbols, ...splitTokenSymbols(input.contract.contract)]),
9666
+ uniqueStrings([...input.chunkSymbols, ...input.consumerToken.symbols])
9667
+ );
9668
+ if (symbolOverlap > 0) {
9669
+ score += Math.min(0.2, symbolOverlap * 0.22);
9670
+ reasons.push("symbol_overlap");
9671
+ }
9672
+ if (input.contract.kind === "route" && input.consumerToken.raw.includes("/")) {
9673
+ score += 0.08;
9674
+ reasons.push("route_literal_match");
9675
+ }
9676
+ return { confidence: clampConfidence(score), reasons };
9677
+ }
9678
+ function aggregateRepoEdges(fileEdges) {
9679
+ const grouped = /* @__PURE__ */ new Map();
9680
+ for (const edge of fileEdges) {
9681
+ const key = repoEdgeKey({
9682
+ layer: "repo",
9683
+ sourceRepo: edge.sourceRepo,
9684
+ targetRepo: edge.targetRepo,
9685
+ relationship: edge.relationship
9686
+ });
9687
+ const bucket = grouped.get(key) ?? [];
9688
+ bucket.push(edge);
9689
+ grouped.set(key, bucket);
9690
+ }
9691
+ const repoEdges = [];
9692
+ for (const [key, group] of grouped.entries()) {
9693
+ const [layerValue = "repo", sourceRepo = "", targetRepo = ""] = key.split("\0");
9694
+ const relationship = group[0]?.relationship ?? "imports";
9695
+ const confidences = group.map((edge) => edge.confidence);
9696
+ const maxConfidence = Math.max(...confidences);
9697
+ const avgConfidence = confidences.reduce((sum, value) => sum + value, 0) / confidences.length;
9698
+ const repetitionBoost = Math.min(0.18, Math.log2(group.length + 1) * 0.06);
9699
+ const confidence = clampConfidence(maxConfidence * 0.6 + avgConfidence * 0.25 + repetitionBoost);
9700
+ const evidence = uniqueEvidenceRefs(group.flatMap((edge) => edge.evidence));
9701
+ const matchReasons6 = mergeReasons([], group.flatMap((edge) => edge.matchReasons));
9702
+ const repoEdge = {
9703
+ org: group[0]?.org ?? "",
9704
+ sourceRepo,
9705
+ sourcePath: "*",
9706
+ targetRepo,
9707
+ targetPath: void 0,
9708
+ layer: layerValue,
9709
+ relationship,
9710
+ evidence,
9711
+ matchReasons: matchReasons6,
9712
+ evidenceCount: 0,
9713
+ weak: false,
9714
+ confidence
9715
+ };
9716
+ updateWeakFlag(repoEdge, MIN_REPO_EDGE_CONFIDENCE, MIN_VISIBLE_EVIDENCE);
9717
+ repoEdges.push(repoEdge);
9718
+ }
9719
+ return repoEdges.sort((a, b) => b.confidence - a.confidence);
9720
+ }
9721
+ function buildQuality(repoEdges, hiddenRepoEdges) {
9722
+ const distribution = { ...DEFAULT_EDGE_DISTRIBUTION2 };
9723
+ for (const edge of repoEdges) {
9724
+ distribution[confidenceBucket(edge.confidence)] += 1;
9725
+ }
9726
+ return {
9727
+ edgeConfidenceDistribution: distribution,
9728
+ weakEdgesFiltered: hiddenRepoEdges.length,
9729
+ minVisibleConfidence: MIN_REPO_EDGE_CONFIDENCE,
9730
+ minVisibleEvidence: MIN_VISIBLE_EVIDENCE
9731
+ };
9732
+ }
9733
+ function isVisibleRepoEdge(edge) {
9734
+ return !edge.weak && edge.confidence >= MIN_REPO_EDGE_CONFIDENCE && edge.evidenceCount >= MIN_VISIBLE_EVIDENCE;
9735
+ }
9354
9736
  function rebuildOrgGraph(db, config, baseDirOrOptions) {
9355
9737
  initializeSchema(db);
9356
9738
  const options = resolveOptions(baseDirOrOptions);
9357
9739
  const startedAt = Date.now();
9358
9740
  try {
9741
+ const enabledRepos = config.repos.filter((repo) => repo.enabled);
9742
+ const repoByName = new Map(enabledRepos.map((repo) => [repo.fullName, repo]));
9359
9743
  options.onProgress?.({
9360
9744
  stage: "loading_package_manifests",
9361
9745
  org: config.org,
9362
- totalRepos: config.repos.filter((repo) => repo.enabled).length
9746
+ totalRepos: enabledRepos.length
9363
9747
  });
9364
9748
  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
9749
  const packageToRepo = /* @__PURE__ */ new Map();
9368
9750
  for (const [repo, names] of packageNames.entries()) {
9369
9751
  for (const name of names) packageToRepo.set(name, repo);
@@ -9374,31 +9756,22 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9374
9756
  repos: enabledRepos.length,
9375
9757
  packageNames: packageToRepo.size
9376
9758
  });
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);
9759
+ const fileEdgeMap = /* @__PURE__ */ new Map();
9760
+ const addFileEdge = (edge) => {
9761
+ upsertEdge(fileEdgeMap, { ...edge, layer: "file" }, MIN_FILE_EDGE_CONFIDENCE, 1);
9391
9762
  };
9392
9763
  enabledRepos.forEach((repo, index) => {
9393
9764
  const manifest = readPackageManifest(orgRepoLocalPath(config.org, repo, options.baseDir));
9394
9765
  for (const dependency of dependenciesFor(manifest)) {
9395
9766
  const targetRepo = packageToRepo.get(dependency);
9396
9767
  if (!targetRepo || targetRepo === repo.fullName) continue;
9397
- addEdge({
9768
+ const score = scorePackageDependency(dependency);
9769
+ addFileEdge({
9398
9770
  org: config.org,
9399
9771
  sourceRepo: repo.fullName,
9400
9772
  sourcePath: "package.json",
9401
9773
  targetRepo,
9774
+ targetPath: "package.json",
9402
9775
  relationship: "depends_on_package",
9403
9776
  evidence: [
9404
9777
  fileEvidence(
@@ -9407,7 +9780,8 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9407
9780
  `depends on ${sanitizeHistoricalText(dependency)}`
9408
9781
  )
9409
9782
  ],
9410
- confidence: 0.9
9783
+ matchReasons: score.reasons,
9784
+ confidence: score.confidence
9411
9785
  });
9412
9786
  }
9413
9787
  options.onProgress?.({
@@ -9416,7 +9790,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9416
9790
  current: index + 1,
9417
9791
  total: enabledRepos.length,
9418
9792
  repo: repo.fullName,
9419
- edges: edges.length
9793
+ edges: fileEdgeMap.size
9420
9794
  });
9421
9795
  });
9422
9796
  options.onProgress?.({ stage: "loading_imports", org: config.org });
@@ -9431,7 +9805,13 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9431
9805
  const rootSpecifier = packageRootForSpecifier(item.specifier);
9432
9806
  const targetRepo = packageToRepo.get(rootSpecifier) ?? packageToRepo.get(item.specifier);
9433
9807
  if (targetRepo && targetRepo !== item.repo) {
9434
- addEdge({
9808
+ const importedSymbols = parseJsonArray10(item.imported_symbols_json);
9809
+ const score = scoreImportEdge({
9810
+ specifier: item.specifier,
9811
+ importedPath: item.imported_path,
9812
+ importedSymbols
9813
+ });
9814
+ addFileEdge({
9435
9815
  org: config.org,
9436
9816
  sourceRepo: item.repo,
9437
9817
  sourcePath: item.source_path,
@@ -9445,7 +9825,8 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9445
9825
  `imports ${sanitizeHistoricalText(rootSpecifier || item.specifier)}`
9446
9826
  )
9447
9827
  ],
9448
- confidence: parseJsonArray10(item.imported_symbols_json).length > 0 ? 0.88 : 0.76
9828
+ matchReasons: score.reasons,
9829
+ confidence: score.confidence
9449
9830
  });
9450
9831
  }
9451
9832
  if (shouldEmitProgress3(index + 1, imports.length)) {
@@ -9455,7 +9836,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9455
9836
  current: index + 1,
9456
9837
  total: imports.length,
9457
9838
  sourcePath: item.source_path,
9458
- edges: edges.length
9839
+ edges: fileEdgeMap.size
9459
9840
  });
9460
9841
  }
9461
9842
  });
@@ -9469,25 +9850,27 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9469
9850
  (chunk) => repoByName.has(chunk.repo) && isApiProviderPath(chunk.file_path)
9470
9851
  );
9471
9852
  const apiContracts = [];
9472
- const contractKeys = /* @__PURE__ */ new Set();
9853
+ const contractByKey = /* @__PURE__ */ new Map();
9473
9854
  const contractsByToken = /* @__PURE__ */ new Map();
9474
9855
  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 = {
9856
+ for (const token of extractContracts(chunk.sanitized_text)) {
9857
+ const key = [chunk.repo, chunk.file_path, token.kind, token.normalized].join("\0");
9858
+ if (contractByKey.has(key)) continue;
9859
+ const contract = {
9481
9860
  repo: chunk.repo,
9482
9861
  filePath: chunk.file_path,
9483
- contract: sanitizedContract,
9484
- evidence: [fileEvidence(chunk.repo, chunk.file_path, `defines ${sanitizedContract}`)],
9485
- confidence: 0.74
9862
+ contract: token.raw,
9863
+ normalizedContract: token.normalized,
9864
+ kind: token.kind,
9865
+ symbols: token.symbols,
9866
+ evidence: [fileEvidence(chunk.repo, chunk.file_path, `defines ${token.raw}`)],
9867
+ confidence: scoreContract({ token, filePath: chunk.file_path })
9486
9868
  };
9487
- apiContracts.push(apiContract);
9488
- const bucket = contractsByToken.get(sanitizedContract) ?? [];
9489
- bucket.push(apiContract);
9490
- contractsByToken.set(sanitizedContract, bucket);
9869
+ contractByKey.set(key, contract);
9870
+ apiContracts.push(contract);
9871
+ const bucket = contractsByToken.get(contract.normalizedContract) ?? [];
9872
+ bucket.push(contract);
9873
+ contractsByToken.set(contract.normalizedContract, bucket);
9491
9874
  }
9492
9875
  if (shouldEmitProgress3(index + 1, providerChunks.length)) {
9493
9876
  options.onProgress?.({
@@ -9500,28 +9883,39 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9500
9883
  });
9501
9884
  }
9502
9885
  });
9503
- const apiConsumers = [];
9504
- const consumerKeys = /* @__PURE__ */ new Set();
9505
9886
  const consumerChunks = chunks.filter(
9506
9887
  (chunk) => repoByName.has(chunk.repo) && isApiConsumerText(chunk.sanitized_text)
9507
9888
  );
9889
+ const apiConsumers = [];
9890
+ const consumerKeySet = /* @__PURE__ */ new Set();
9508
9891
  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;
9892
+ const chunkTokens = extractContracts(chunk.sanitized_text);
9893
+ const chunkSymbols = parseJsonArray10(chunk.symbols_json);
9894
+ let matchesForChunk = 0;
9895
+ for (const consumerToken of chunkTokens) {
9896
+ const contracts = contractsByToken.get(consumerToken.normalized);
9897
+ if (!contracts?.length) continue;
9514
9898
  for (const contract of contracts) {
9515
9899
  if (chunk.repo === contract.repo) continue;
9900
+ const score = scoreConsumerMatch({
9901
+ consumerToken,
9902
+ contract,
9903
+ chunkSymbols
9904
+ });
9905
+ if (score.confidence < MIN_API_CONSUMER_CONFIDENCE) continue;
9516
9906
  const consumerKey = [
9517
9907
  contract.repo,
9518
9908
  contract.filePath,
9519
9909
  chunk.repo,
9520
9910
  chunk.file_path,
9521
- contract.contract
9911
+ contract.normalizedContract
9522
9912
  ].join("\0");
9523
- if (consumerKeys.has(consumerKey)) continue;
9524
- consumerKeys.add(consumerKey);
9913
+ if (consumerKeySet.has(consumerKey)) continue;
9914
+ consumerKeySet.add(consumerKey);
9915
+ const evidence = uniqueEvidenceRefs([
9916
+ ...contract.evidence,
9917
+ fileEvidence(chunk.repo, chunk.file_path, `consumes ${contract.contract}`)
9918
+ ]);
9525
9919
  const consumer = {
9526
9920
  org: config.org,
9527
9921
  providerRepo: contract.repo,
@@ -9529,23 +9923,24 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9529
9923
  consumerRepo: chunk.repo,
9530
9924
  consumerPath: chunk.file_path,
9531
9925
  contract: contract.contract,
9532
- evidence: [
9533
- ...contract.evidence,
9534
- fileEvidence(chunk.repo, chunk.file_path, `consumes ${contract.contract}`)
9535
- ],
9536
- confidence: 0.86
9926
+ evidence,
9927
+ matchReasons: mergeReasons([], score.reasons),
9928
+ evidenceCount: evidence.length,
9929
+ weak: score.confidence < MIN_API_CONSUMER_CONFIDENCE || evidence.length < MIN_VISIBLE_EVIDENCE,
9930
+ confidence: score.confidence
9537
9931
  };
9538
- chunkMatches += 1;
9539
9932
  apiConsumers.push(consumer);
9540
- addEdge({
9933
+ matchesForChunk += 1;
9934
+ addFileEdge({
9541
9935
  org: config.org,
9542
9936
  sourceRepo: chunk.repo,
9543
9937
  sourcePath: chunk.file_path,
9544
9938
  targetRepo: contract.repo,
9545
9939
  targetPath: contract.filePath,
9546
9940
  relationship: "api_consumer",
9547
- evidence: consumer.evidence,
9548
- confidence: consumer.confidence
9941
+ evidence,
9942
+ matchReasons: score.reasons,
9943
+ confidence: score.confidence
9549
9944
  });
9550
9945
  }
9551
9946
  }
@@ -9556,46 +9951,79 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9556
9951
  current: index + 1,
9557
9952
  total: consumerChunks.length,
9558
9953
  filePath: chunk.file_path,
9559
- matches: chunkMatches
9954
+ matches: matchesForChunk
9560
9955
  });
9561
9956
  }
9562
9957
  });
9958
+ const allFileEdges = [...fileEdgeMap.values()].sort((a, b) => b.confidence - a.confidence);
9959
+ const repoEdges = aggregateRepoEdges(allFileEdges);
9960
+ const visibleRepoEdges = repoEdges.filter(isVisibleRepoEdge);
9961
+ const hiddenRepoEdges = repoEdges.filter((edge) => !isVisibleRepoEdge(edge));
9962
+ const hiddenFileEdges = allFileEdges.filter(
9963
+ (edge) => edge.confidence < MIN_FILE_EDGE_CONFIDENCE || edge.evidenceCount < 1
9964
+ );
9965
+ const visibleFileEdges = allFileEdges.filter((edge) => !hiddenFileEdges.includes(edge));
9966
+ const quality = buildQuality(visibleRepoEdges, hiddenRepoEdges);
9563
9967
  options.onProgress?.({
9564
9968
  stage: "writing_org_graph",
9565
9969
  org: config.org,
9566
- edges: edges.length,
9970
+ edges: repoEdges.length,
9567
9971
  apiContracts: apiContracts.length,
9568
9972
  apiConsumers: apiConsumers.length
9569
9973
  });
9570
9974
  const now = (/* @__PURE__ */ new Date()).toISOString();
9975
+ const renderPrepStartedAt = Date.now();
9571
9976
  const transaction = db.transaction(() => {
9572
9977
  db.prepare("DELETE FROM org_cross_repo_edges WHERE org = ?").run(config.org);
9573
9978
  db.prepare("DELETE FROM org_api_contracts WHERE org = ?").run(config.org);
9574
9979
  db.prepare("DELETE FROM org_api_consumers WHERE org = ?").run(config.org);
9575
9980
  const insertEdge = db.prepare(
9576
9981
  `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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
9982
+ (id, org, source_repo, source_path, target_repo, target_path, layer, relationship,
9983
+ evidence_json, match_reasons_json, evidence_count, is_weak, confidence, created_at)
9984
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
9579
9985
  ON CONFLICT(id) DO UPDATE SET
9986
+ source_path = excluded.source_path,
9987
+ target_path = excluded.target_path,
9988
+ layer = excluded.layer,
9580
9989
  evidence_json = excluded.evidence_json,
9990
+ match_reasons_json = excluded.match_reasons_json,
9991
+ evidence_count = excluded.evidence_count,
9992
+ is_weak = excluded.is_weak,
9581
9993
  confidence = excluded.confidence,
9582
9994
  created_at = excluded.created_at`
9583
9995
  );
9584
- for (const [index, edge] of edges.entries()) {
9996
+ const persistEdge = (edge) => {
9585
9997
  insertEdge.run(
9586
- `oge_${stableId([edge.org, edge.sourceRepo, edge.sourcePath, edge.targetRepo, edge.targetPath ?? "", edge.relationship])}`,
9998
+ `oge_${stableId([
9999
+ edge.org,
10000
+ edge.layer,
10001
+ edge.sourceRepo,
10002
+ edge.sourcePath,
10003
+ edge.targetRepo,
10004
+ edge.targetPath ?? "",
10005
+ edge.relationship
10006
+ ])}`,
9587
10007
  edge.org,
9588
10008
  edge.sourceRepo,
9589
10009
  edge.sourcePath,
9590
10010
  edge.targetRepo,
9591
10011
  edge.targetPath ?? null,
10012
+ edge.layer,
9592
10013
  edge.relationship,
9593
10014
  evidenceJson(edge.evidence),
10015
+ JSON.stringify(edge.matchReasons),
10016
+ edge.evidenceCount,
10017
+ edge.weak ? 1 : 0,
9594
10018
  edge.confidence,
9595
10019
  now
9596
10020
  );
10021
+ };
10022
+ const persistedEdges = [...repoEdges, ...allFileEdges];
10023
+ for (const [index, edge] of persistedEdges.entries()) {
10024
+ persistEdge(edge);
9597
10025
  const current = index + 1;
9598
- if (shouldEmitProgress3(current, edges.length, 500)) {
10026
+ if (shouldEmitProgress3(current, persistedEdges.length, 500)) {
9599
10027
  options.onProgress?.({
9600
10028
  stage: "writing_org_graph",
9601
10029
  org: config.org,
@@ -9603,7 +10031,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9603
10031
  apiContracts: apiContracts.length,
9604
10032
  apiConsumers: apiConsumers.length,
9605
10033
  current,
9606
- total: edges.length,
10034
+ total: persistedEdges.length,
9607
10035
  kind: "edges"
9608
10036
  });
9609
10037
  }
@@ -9620,7 +10048,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9620
10048
  );
9621
10049
  for (const [index, contract] of apiContracts.entries()) {
9622
10050
  insertContract.run(
9623
- `oac_${stableId([config.org, contract.repo, contract.filePath, contract.contract])}`,
10051
+ `oac_${stableId([config.org, contract.repo, contract.filePath, contract.normalizedContract])}`,
9624
10052
  config.org,
9625
10053
  contract.repo,
9626
10054
  contract.filePath,
@@ -9634,7 +10062,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9634
10062
  options.onProgress?.({
9635
10063
  stage: "writing_org_graph",
9636
10064
  org: config.org,
9637
- edges: edges.length,
10065
+ edges: persistedEdges.length,
9638
10066
  apiContracts: current,
9639
10067
  apiConsumers: apiConsumers.length,
9640
10068
  current,
@@ -9645,11 +10073,15 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9645
10073
  }
9646
10074
  const insertConsumer = db.prepare(
9647
10075
  `INSERT INTO org_api_consumers
9648
- (id, org, provider_repo, provider_path, consumer_repo, consumer_path, contract, evidence_json, confidence, created_at)
9649
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
10076
+ (id, org, provider_repo, provider_path, consumer_repo, consumer_path, contract, evidence_json,
10077
+ match_reasons_json, evidence_count, is_weak, confidence, created_at)
10078
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
9650
10079
  ON CONFLICT(id) DO UPDATE SET
9651
10080
  contract = excluded.contract,
9652
10081
  evidence_json = excluded.evidence_json,
10082
+ match_reasons_json = excluded.match_reasons_json,
10083
+ evidence_count = excluded.evidence_count,
10084
+ is_weak = excluded.is_weak,
9653
10085
  confidence = excluded.confidence,
9654
10086
  created_at = excluded.created_at`
9655
10087
  );
@@ -9661,7 +10093,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9661
10093
  consumer.providerPath ?? "",
9662
10094
  consumer.consumerRepo,
9663
10095
  consumer.consumerPath,
9664
- consumer.contract
10096
+ normalizeToken(consumer.contract)
9665
10097
  ])}`,
9666
10098
  consumer.org,
9667
10099
  consumer.providerRepo,
@@ -9670,6 +10102,9 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9670
10102
  consumer.consumerPath,
9671
10103
  sanitizeHistoricalText(consumer.contract),
9672
10104
  evidenceJson(consumer.evidence),
10105
+ JSON.stringify(consumer.matchReasons),
10106
+ consumer.evidenceCount,
10107
+ consumer.weak ? 1 : 0,
9673
10108
  consumer.confidence,
9674
10109
  now
9675
10110
  );
@@ -9678,7 +10113,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9678
10113
  options.onProgress?.({
9679
10114
  stage: "writing_org_graph",
9680
10115
  org: config.org,
9681
- edges: edges.length,
10116
+ edges: persistedEdges.length,
9682
10117
  apiContracts: apiContracts.length,
9683
10118
  apiConsumers: current,
9684
10119
  current,
@@ -9689,6 +10124,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9689
10124
  }
9690
10125
  });
9691
10126
  transaction();
10127
+ const renderPrepMs = Date.now() - renderPrepStartedAt;
9692
10128
  const durationMs = Date.now() - startedAt;
9693
10129
  const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
9694
10130
  recordOrgGraphState(db, {
@@ -9696,22 +10132,40 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9696
10132
  status: "success",
9697
10133
  builtAt: finishedAt,
9698
10134
  durationMs,
9699
- edgeCount: edges.length,
10135
+ edgeCount: repoEdges.length,
10136
+ visibleEdgeCount: visibleRepoEdges.length,
10137
+ weakEdgeCount: hiddenRepoEdges.length,
10138
+ edgeConfidenceDistribution: quality.edgeConfidenceDistribution,
10139
+ lastRenderPrepMs: renderPrepMs,
9700
10140
  apiContractCount: apiContracts.length,
9701
10141
  apiConsumerCount: apiConsumers.length
9702
10142
  });
9703
10143
  options.onProgress?.({
9704
10144
  stage: "completed_org_graph",
9705
10145
  org: config.org,
9706
- edges: edges.length,
10146
+ edges: repoEdges.length,
9707
10147
  apiContracts: apiContracts.length,
9708
10148
  apiConsumers: apiConsumers.length,
9709
10149
  durationMs
9710
10150
  });
9711
10151
  return {
9712
- edges,
10152
+ edges: visibleRepoEdges,
10153
+ repoEdges: visibleRepoEdges,
10154
+ fileEdges: visibleFileEdges,
10155
+ hiddenFileEdges,
10156
+ hiddenRepoEdges,
9713
10157
  apiConsumers,
9714
- apiContracts,
10158
+ apiContracts: apiContracts.map((contract) => ({
10159
+ repo: contract.repo,
10160
+ filePath: contract.filePath,
10161
+ contract: contract.contract,
10162
+ evidence: contract.evidence,
10163
+ confidence: contract.confidence
10164
+ })),
10165
+ quality: {
10166
+ ...quality,
10167
+ lastRenderPrepMs: renderPrepMs
10168
+ },
9715
10169
  durationMs
9716
10170
  };
9717
10171
  } catch (error) {
@@ -10055,6 +10509,8 @@ async function indexOrgRepos(db, config, options = {}) {
10055
10509
  org: config.org,
10056
10510
  status: "skipped",
10057
10511
  edgeCount: counts.edges,
10512
+ visibleEdgeCount: counts.visibleEdges,
10513
+ weakEdgeCount: counts.weakEdges,
10058
10514
  apiContractCount: counts.apiContracts,
10059
10515
  apiConsumerCount: counts.apiConsumers
10060
10516
  });
@@ -10064,7 +10520,12 @@ async function indexOrgRepos(db, config, options = {}) {
10064
10520
  command,
10065
10521
  reason: "Graph skipped because --no-graph was passed."
10066
10522
  });
10067
- graph = { ...counts, skipped: true };
10523
+ graph = {
10524
+ edges: counts.visibleEdges,
10525
+ apiConsumers: counts.apiConsumers,
10526
+ apiContracts: counts.apiContracts,
10527
+ skipped: true
10528
+ };
10068
10529
  } else {
10069
10530
  try {
10070
10531
  const rebuiltGraph = rebuildOrgGraph(db, config, {
@@ -10079,7 +10540,12 @@ async function indexOrgRepos(db, config, options = {}) {
10079
10540
  } catch (error) {
10080
10541
  const message = error instanceof Error ? error.message : String(error);
10081
10542
  const counts = getOrgGraphCounts(db, config.org);
10082
- graph = { ...counts, error: message };
10543
+ graph = {
10544
+ edges: counts.visibleEdges,
10545
+ apiConsumers: counts.apiConsumers,
10546
+ apiContracts: counts.apiContracts,
10547
+ error: message
10548
+ };
10083
10549
  }
10084
10550
  }
10085
10551
  recordOrgIndexRun(db, {
@@ -10123,6 +10589,15 @@ function parseEvidence2(value) {
10123
10589
  return [];
10124
10590
  }
10125
10591
  }
10592
+ function parseStringArray(value) {
10593
+ if (!value) return [];
10594
+ try {
10595
+ const parsed = JSON.parse(value);
10596
+ return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
10597
+ } catch {
10598
+ return [];
10599
+ }
10600
+ }
10126
10601
  function fileEvidence2(repo, filePath, note) {
10127
10602
  return {
10128
10603
  prNumber: 0,
@@ -10154,7 +10629,7 @@ function affectedConsumers(db, org, repo, changedFiles) {
10154
10629
  const rows = db.prepare(
10155
10630
  `SELECT provider_repo, provider_path, consumer_repo, consumer_path, contract, evidence_json, confidence
10156
10631
  FROM org_api_consumers
10157
- WHERE org = ?`
10632
+ WHERE org = ? AND is_weak = 0`
10158
10633
  ).all(org);
10159
10634
  return rows.filter((row) => !repo || row.provider_repo === repo || row.consumer_repo === repo).filter((row) => {
10160
10635
  if (changedFiles.length === 0) return true;
@@ -10169,14 +10644,18 @@ function affectedConsumers(db, org, repo, changedFiles) {
10169
10644
  consumerPath: row.consumer_path,
10170
10645
  contract: sanitizeHistoricalText(row.contract),
10171
10646
  evidence: parseEvidence2(row.evidence_json),
10647
+ matchReasons: parseStringArray(row.match_reasons_json),
10648
+ evidenceCount: row.evidence_count ?? parseEvidence2(row.evidence_json).length,
10649
+ weak: (row.is_weak ?? 0) === 1,
10172
10650
  confidence: row.confidence
10173
10651
  }));
10174
10652
  }
10175
10653
  function affectedEdges(db, org, repo, changedFiles) {
10176
10654
  const rows = db.prepare(
10177
- `SELECT source_repo, source_path, target_repo, target_path, relationship, evidence_json, confidence
10655
+ `SELECT source_repo, source_path, target_repo, target_path, layer, relationship, evidence_json,
10656
+ match_reasons_json, evidence_count, is_weak, confidence
10178
10657
  FROM org_cross_repo_edges
10179
- WHERE org = ?`
10658
+ WHERE org = ? AND layer = 'file'`
10180
10659
  ).all(org);
10181
10660
  return rows.filter((row) => !repo || row.source_repo === repo || row.target_repo === repo).filter((row) => {
10182
10661
  if (changedFiles.length === 0) return true;
@@ -10187,8 +10666,12 @@ function affectedEdges(db, org, repo, changedFiles) {
10187
10666
  sourcePath: row.source_path,
10188
10667
  targetRepo: row.target_repo,
10189
10668
  targetPath: row.target_path ?? void 0,
10669
+ layer: row.layer,
10190
10670
  relationship: row.relationship,
10191
10671
  evidence: parseEvidence2(row.evidence_json),
10672
+ matchReasons: parseStringArray(row.match_reasons_json),
10673
+ evidenceCount: row.evidence_count ?? parseEvidence2(row.evidence_json).length,
10674
+ weak: (row.is_weak ?? 0) === 1,
10192
10675
  confidence: row.confidence
10193
10676
  }));
10194
10677
  }
@@ -10506,7 +10989,7 @@ function evidenceLabel(evidence) {
10506
10989
  if (first.prNumber > 0) return `PR #${first.prNumber}`;
10507
10990
  return first.filePath ? `file ${first.filePath}` : first.note ?? "local file evidence";
10508
10991
  }
10509
- function parseStringArray(value) {
10992
+ function parseStringArray2(value) {
10510
10993
  try {
10511
10994
  const parsed = JSON.parse(value);
10512
10995
  return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
@@ -10553,7 +11036,7 @@ function getWisdom(db, input, limit) {
10553
11036
  const lowerTerms = queryTerms(input).map((term) => term.toLowerCase());
10554
11037
  return rows.filter((row) => matchesRepo(row.repo, input.repos)).map((row) => ({
10555
11038
  row,
10556
- score: rowScore(input, row.sanitized_text, parseStringArray(row.file_paths_json), [], lowerTerms)
11039
+ score: rowScore(input, row.sanitized_text, parseStringArray2(row.file_paths_json), [], lowerTerms)
10557
11040
  })).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
11041
  }
10559
11042
  function getCodeEvidence(db, input, limit) {
@@ -10570,7 +11053,7 @@ function getCodeEvidence(db, input, limit) {
10570
11053
  input,
10571
11054
  row.sanitized_text,
10572
11055
  [row.file_path],
10573
- parseStringArray(row.symbols_json),
11056
+ parseStringArray2(row.symbols_json),
10574
11057
  lowerTerms
10575
11058
  )
10576
11059
  })).filter((item) => item.score > 0).sort((a, b) => b.score - a.score).slice(0, limit).map((item) => item.row);
@@ -10588,7 +11071,7 @@ function getArchitecture(db, input, limit) {
10588
11071
  score: rowScore(
10589
11072
  input,
10590
11073
  row.summary_sanitized,
10591
- parseStringArray(row.source_files_json),
11074
+ parseStringArray2(row.source_files_json),
10592
11075
  [],
10593
11076
  lowerTerms
10594
11077
  )
@@ -10599,9 +11082,10 @@ function findOrgApiConsumers(db, config, input) {
10599
11082
  const repoClause = input.repo ? " AND (provider_repo = ? OR consumer_repo = ?)" : "";
10600
11083
  const repoParams = input.repo ? [input.repo, input.repo] : [];
10601
11084
  const rows = db.prepare(
10602
- `SELECT provider_repo, provider_path, consumer_repo, consumer_path, contract, evidence_json, confidence
11085
+ `SELECT provider_repo, provider_path, consumer_repo, consumer_path, contract, evidence_json,
11086
+ match_reasons_json, evidence_count, is_weak, confidence
10603
11087
  FROM org_api_consumers
10604
- WHERE org = ?${repoClause}
11088
+ WHERE org = ? AND is_weak = 0${repoClause}
10605
11089
  ORDER BY confidence DESC`
10606
11090
  ).all(config.org, ...repoParams);
10607
11091
  const limit = Math.max(1, Math.min(input.maxResults ?? 8, 25));
@@ -10617,6 +11101,9 @@ function findOrgApiConsumers(db, config, input) {
10617
11101
  consumerPath: row.consumer_path,
10618
11102
  contract: sanitizeHistoricalText(row.contract),
10619
11103
  evidence: parseEvidence3(row.evidence_json),
11104
+ matchReasons: parseStringArray2(row.match_reasons_json ?? "[]"),
11105
+ evidenceCount: row.evidence_count ?? parseEvidence3(row.evidence_json).length,
11106
+ weak: (row.is_weak ?? 0) === 1,
10620
11107
  confidence: row.confidence
10621
11108
  }));
10622
11109
  }
@@ -10625,7 +11112,7 @@ function getOrgArchitectureMap(db, config, format = "mermaid") {
10625
11112
  const rows = db.prepare(
10626
11113
  `SELECT source_repo, source_path, target_repo, target_path, relationship, confidence
10627
11114
  FROM org_cross_repo_edges
10628
- WHERE org = ?
11115
+ WHERE org = ? AND layer = 'repo' AND is_weak = 0
10629
11116
  ORDER BY confidence DESC, source_repo, target_repo`
10630
11117
  ).all(config.org);
10631
11118
  const nodes = uniqueStrings(rows.flatMap((row) => [row.source_repo, row.target_repo])).map(
@@ -10703,7 +11190,7 @@ function buildOrgContextResult(db, config, input) {
10703
11190
  if (architecture.length === 0) lines.push("- No matching architecture patterns found.");
10704
11191
  else {
10705
11192
  for (const pattern of architecture) {
10706
- const files = parseStringArray(pattern.source_files_json);
11193
+ const files = parseStringArray2(pattern.source_files_json);
10707
11194
  lines.push(
10708
11195
  `- [${pattern.repo}] [${pattern.area}] ${pattern.summary_sanitized} Evidence: ${files[0] ?? "indexed current code"}.`
10709
11196
  );