@pratik7368patil/anchor-core 0.1.31 → 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();
@@ -2695,19 +2744,36 @@ function insertPrCochangeTestLinks(db, repoId, filePaths) {
2695
2744
  }
2696
2745
  }
2697
2746
  function insertTestAwareness(db, repoId, repo, testFiles, testLinks, options = {}) {
2747
+ const dedupedTestFilesByPath = /* @__PURE__ */ new Map();
2748
+ for (const file of testFiles) dedupedTestFilesByPath.set(file.path, file);
2749
+ const dedupedTestFiles = [...dedupedTestFilesByPath.values()];
2750
+ const dedupedTestLinksByKey = /* @__PURE__ */ new Map();
2751
+ for (const link of testLinks) {
2752
+ const key = `${link.sourcePath}\0${link.testPath}\0${link.reason}`;
2753
+ const existing = dedupedTestLinksByKey.get(key);
2754
+ if (!existing || link.strength > existing.strength) {
2755
+ dedupedTestLinksByKey.set(key, link);
2756
+ }
2757
+ }
2758
+ const dedupedTestLinks = [...dedupedTestLinksByKey.values()];
2698
2759
  const insertTestFile = db.prepare(
2699
2760
  `INSERT INTO test_files
2700
2761
  (repo_id, path, language, size_bytes, content_hash, updated_at)
2701
- VALUES (?, ?, ?, ?, ?, ?)`
2762
+ VALUES (?, ?, ?, ?, ?, ?)
2763
+ ON CONFLICT(repo_id, path) DO UPDATE SET
2764
+ language = excluded.language,
2765
+ size_bytes = excluded.size_bytes,
2766
+ content_hash = excluded.content_hash,
2767
+ updated_at = excluded.updated_at`
2702
2768
  );
2703
2769
  options.onProgress?.({
2704
2770
  stage: "writing_test_awareness",
2705
2771
  repo,
2706
2772
  current: 0,
2707
- total: testFiles.length,
2773
+ total: dedupedTestFiles.length,
2708
2774
  kind: "test_files"
2709
2775
  });
2710
- for (const [index, file] of testFiles.entries()) {
2776
+ for (const [index, file] of dedupedTestFiles.entries()) {
2711
2777
  insertTestFile.run(
2712
2778
  repoId,
2713
2779
  file.path,
@@ -2717,36 +2783,38 @@ function insertTestAwareness(db, repoId, repo, testFiles, testLinks, options = {
2717
2783
  file.updatedAt
2718
2784
  );
2719
2785
  const current = index + 1;
2720
- if (shouldEmitCodeWriteProgress(current, testFiles.length)) {
2786
+ if (shouldEmitCodeWriteProgress(current, dedupedTestFiles.length)) {
2721
2787
  options.onProgress?.({
2722
2788
  stage: "writing_test_awareness",
2723
2789
  repo,
2724
2790
  current,
2725
- total: testFiles.length,
2791
+ total: dedupedTestFiles.length,
2726
2792
  kind: "test_files"
2727
2793
  });
2728
2794
  }
2729
2795
  }
2730
2796
  const insertTestLink = db.prepare(
2731
2797
  `INSERT INTO test_links (repo_id, source_path, test_path, reason, strength)
2732
- VALUES (?, ?, ?, ?, ?)`
2798
+ VALUES (?, ?, ?, ?, ?)
2799
+ ON CONFLICT(repo_id, source_path, test_path, reason) DO UPDATE SET
2800
+ strength = excluded.strength`
2733
2801
  );
2734
2802
  options.onProgress?.({
2735
2803
  stage: "writing_test_awareness",
2736
2804
  repo,
2737
2805
  current: 0,
2738
- total: testLinks.length,
2806
+ total: dedupedTestLinks.length,
2739
2807
  kind: "test_links"
2740
2808
  });
2741
- for (const [index, link] of testLinks.entries()) {
2809
+ for (const [index, link] of dedupedTestLinks.entries()) {
2742
2810
  insertTestLink.run(repoId, link.sourcePath, link.testPath, link.reason, link.strength);
2743
2811
  const current = index + 1;
2744
- if (shouldEmitCodeWriteProgress(current, testLinks.length)) {
2812
+ if (shouldEmitCodeWriteProgress(current, dedupedTestLinks.length)) {
2745
2813
  options.onProgress?.({
2746
2814
  stage: "writing_test_awareness",
2747
2815
  repo,
2748
2816
  current,
2749
- total: testLinks.length,
2817
+ total: dedupedTestLinks.length,
2750
2818
  kind: "test_links"
2751
2819
  });
2752
2820
  }
@@ -8687,6 +8755,24 @@ function resolveOrgForTool(org, baseDir = defaultOrgBaseDir()) {
8687
8755
 
8688
8756
  // src/org/database.ts
8689
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
+ }
8690
8776
  function openOrgDatabase(org, baseDir) {
8691
8777
  const root = orgRoot(org, baseDir);
8692
8778
  const db = openAnchorDatabase(root, orgDatabasePath(org, baseDir));
@@ -8816,8 +8902,9 @@ function recordOrgGraphState(db, input) {
8816
8902
  db.prepare(
8817
8903
  `INSERT INTO org_graph_state
8818
8904
  (org, last_built_at, last_status, last_duration_ms, edge_count, api_contract_count,
8819
- api_consumer_count, last_error, updated_at)
8820
- 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
8821
8908
  ON CONFLICT(org) DO UPDATE SET
8822
8909
  last_built_at = COALESCE(excluded.last_built_at, org_graph_state.last_built_at),
8823
8910
  last_status = excluded.last_status,
@@ -8825,6 +8912,10 @@ function recordOrgGraphState(db, input) {
8825
8912
  edge_count = excluded.edge_count,
8826
8913
  api_contract_count = excluded.api_contract_count,
8827
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,
8828
8919
  last_error = excluded.last_error,
8829
8920
  updated_at = excluded.updated_at`
8830
8921
  ).run(
@@ -8835,6 +8926,10 @@ function recordOrgGraphState(db, input) {
8835
8926
  input.edgeCount ?? 0,
8836
8927
  input.apiContractCount ?? 0,
8837
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,
8838
8933
  input.error ?? null,
8839
8934
  now
8840
8935
  );
@@ -8849,6 +8944,10 @@ function getOrgGraphState(db, org) {
8849
8944
  lastStatus: row.last_status ?? void 0,
8850
8945
  lastDurationMs: row.last_duration_ms ?? void 0,
8851
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,
8852
8951
  apiContractCount: row.api_contract_count ?? void 0,
8853
8952
  apiConsumerCount: row.api_consumer_count ?? void 0,
8854
8953
  lastError: row.last_error ?? void 0
@@ -8861,7 +8960,19 @@ function count(db, table, where = "", params = []) {
8861
8960
  function getOrgGraphCounts(db, org) {
8862
8961
  initializeSchema(db);
8863
8962
  return {
8864
- 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
+ ),
8865
8976
  apiContracts: count(db, "org_api_contracts", "WHERE org = ?", [org]),
8866
8977
  apiConsumers: count(db, "org_api_consumers", "WHERE org = ?", [org])
8867
8978
  };
@@ -8888,11 +8999,22 @@ function getOrgStatus(db, config, baseDir, options = {}) {
8888
8999
  const codeFileCount = count(db, "code_files");
8889
9000
  const codeChunkCount = count(db, "code_chunks");
8890
9001
  const wisdomUnitCount = count(db, "wisdom_units");
8891
- 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
+ );
8892
9014
  const apiContractCount = count(db, "org_api_contracts", "WHERE org = ?", [config.org]);
8893
9015
  const apiConsumerCount = count(db, "org_api_consumers", "WHERE org = ?", [config.org]);
8894
9016
  const anomalyCount = count(db, "org_anomaly_events", "WHERE org = ?", [config.org]);
8895
- const graphState = db.prepare("SELECT * FROM org_graph_state WHERE org = ?").get(config.org);
9017
+ const graphState = getOrgGraphState(db, config.org);
8896
9018
  let score = 0;
8897
9019
  const reasons = [];
8898
9020
  if (enabledRepos.length > 0) {
@@ -8936,10 +9058,14 @@ function getOrgStatus(db, config, baseDir, options = {}) {
8936
9058
  apiContractCount,
8937
9059
  apiConsumerCount,
8938
9060
  anomalyCount,
8939
- graphLastBuiltAt: graphState?.last_built_at ?? void 0,
8940
- graphLastStatus: graphState?.last_status ?? void 0,
8941
- graphLastDurationMs: graphState?.last_duration_ms ?? void 0,
8942
- 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 },
8943
9069
  coverageScore: score,
8944
9070
  coverageGrade: grade(score),
8945
9071
  coverageReasons: reasons,
@@ -9243,6 +9369,34 @@ function orgCloneStateFromResult(org, repo, result) {
9243
9369
  import crypto9 from "crypto";
9244
9370
  import fs13 from "fs";
9245
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
+ };
9246
9400
  function stableId(parts) {
9247
9401
  return crypto9.createHash("sha256").update(parts.join("\0")).digest("hex").slice(0, 32);
9248
9402
  }
@@ -9255,6 +9409,9 @@ function fileEvidence(repo, filePath, note) {
9255
9409
  note
9256
9410
  };
9257
9411
  }
9412
+ function evidenceJson(evidence) {
9413
+ return JSON.stringify(evidence);
9414
+ }
9258
9415
  function readPackageManifest(repoPath) {
9259
9416
  const packagePath = path23.join(repoPath, "package.json");
9260
9417
  if (!fs13.existsSync(packagePath)) return void 0;
@@ -9300,19 +9457,53 @@ function parseJsonArray10(value) {
9300
9457
  return [];
9301
9458
  }
9302
9459
  }
9303
- function extractContracts(text) {
9304
- const contracts = [];
9305
- const routeMatches = text.matchAll(/["'`]((?:\/api)?\/[A-Za-z0-9_./:{}-]{2,})["'`]/g);
9306
- for (const match of routeMatches) {
9307
- const route = match[1];
9308
- if (route && route.length <= 120 && !route.includes(" ")) contracts.push(route);
9309
- }
9310
- const gqlMatches = text.matchAll(/\b(query|mutation)\s+([A-Za-z0-9_]+)/g);
9311
- for (const match of gqlMatches) {
9312
- const operation = match[2];
9313
- 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}`;
9314
9470
  }
9315
- 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);
9316
9507
  }
9317
9508
  function isApiProviderPath(filePath) {
9318
9509
  const normalized = filePath.toLowerCase();
@@ -9323,28 +9514,216 @@ function isApiProviderPath(filePath) {
9323
9514
  function isApiConsumerText(text) {
9324
9515
  return /\b(fetch|axios|ky|graphql|gql|client|sdk|request)\b/i.test(text);
9325
9516
  }
9326
- function evidenceJson(evidence) {
9327
- return JSON.stringify(evidence);
9328
- }
9329
9517
  function shouldEmitProgress3(current, total, interval = 100) {
9330
9518
  return current === 1 || current === total || current % interval === 0;
9331
9519
  }
9332
9520
  function resolveOptions(baseDirOrOptions) {
9333
9521
  return typeof baseDirOrOptions === "string" ? { baseDir: baseDirOrOptions } : baseDirOrOptions ?? {};
9334
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
+ }
9335
9714
  function rebuildOrgGraph(db, config, baseDirOrOptions) {
9336
9715
  initializeSchema(db);
9337
9716
  const options = resolveOptions(baseDirOrOptions);
9338
9717
  const startedAt = Date.now();
9339
9718
  try {
9719
+ const enabledRepos = config.repos.filter((repo) => repo.enabled);
9720
+ const repoByName = new Map(enabledRepos.map((repo) => [repo.fullName, repo]));
9340
9721
  options.onProgress?.({
9341
9722
  stage: "loading_package_manifests",
9342
9723
  org: config.org,
9343
- totalRepos: config.repos.filter((repo) => repo.enabled).length
9724
+ totalRepos: enabledRepos.length
9344
9725
  });
9345
9726
  const packageNames = repoPackageNames(config, options.baseDir);
9346
- const enabledRepos = config.repos.filter((repo) => repo.enabled);
9347
- const repoByName = new Map(enabledRepos.map((repo) => [repo.fullName, repo]));
9348
9727
  const packageToRepo = /* @__PURE__ */ new Map();
9349
9728
  for (const [repo, names] of packageNames.entries()) {
9350
9729
  for (const name of names) packageToRepo.set(name, repo);
@@ -9355,31 +9734,22 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9355
9734
  repos: enabledRepos.length,
9356
9735
  packageNames: packageToRepo.size
9357
9736
  });
9358
- const edges = [];
9359
- const edgeKeys = /* @__PURE__ */ new Set();
9360
- const addEdge = (edge) => {
9361
- if (edge.sourceRepo === edge.targetRepo) return;
9362
- const key = [
9363
- edge.sourceRepo,
9364
- edge.sourcePath,
9365
- edge.targetRepo,
9366
- edge.targetPath ?? "",
9367
- edge.relationship
9368
- ].join("\0");
9369
- if (edgeKeys.has(key)) return;
9370
- edgeKeys.add(key);
9371
- edges.push(edge);
9737
+ const fileEdgeMap = /* @__PURE__ */ new Map();
9738
+ const addFileEdge = (edge) => {
9739
+ upsertEdge(fileEdgeMap, { ...edge, layer: "file" }, MIN_FILE_EDGE_CONFIDENCE, 1);
9372
9740
  };
9373
9741
  enabledRepos.forEach((repo, index) => {
9374
9742
  const manifest = readPackageManifest(orgRepoLocalPath(config.org, repo, options.baseDir));
9375
9743
  for (const dependency of dependenciesFor(manifest)) {
9376
9744
  const targetRepo = packageToRepo.get(dependency);
9377
9745
  if (!targetRepo || targetRepo === repo.fullName) continue;
9378
- addEdge({
9746
+ const score = scorePackageDependency(dependency);
9747
+ addFileEdge({
9379
9748
  org: config.org,
9380
9749
  sourceRepo: repo.fullName,
9381
9750
  sourcePath: "package.json",
9382
9751
  targetRepo,
9752
+ targetPath: "package.json",
9383
9753
  relationship: "depends_on_package",
9384
9754
  evidence: [
9385
9755
  fileEvidence(
@@ -9388,7 +9758,8 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9388
9758
  `depends on ${sanitizeHistoricalText(dependency)}`
9389
9759
  )
9390
9760
  ],
9391
- confidence: 0.9
9761
+ matchReasons: score.reasons,
9762
+ confidence: score.confidence
9392
9763
  });
9393
9764
  }
9394
9765
  options.onProgress?.({
@@ -9397,7 +9768,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9397
9768
  current: index + 1,
9398
9769
  total: enabledRepos.length,
9399
9770
  repo: repo.fullName,
9400
- edges: edges.length
9771
+ edges: fileEdgeMap.size
9401
9772
  });
9402
9773
  });
9403
9774
  options.onProgress?.({ stage: "loading_imports", org: config.org });
@@ -9412,7 +9783,13 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9412
9783
  const rootSpecifier = packageRootForSpecifier(item.specifier);
9413
9784
  const targetRepo = packageToRepo.get(rootSpecifier) ?? packageToRepo.get(item.specifier);
9414
9785
  if (targetRepo && targetRepo !== item.repo) {
9415
- 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({
9416
9793
  org: config.org,
9417
9794
  sourceRepo: item.repo,
9418
9795
  sourcePath: item.source_path,
@@ -9426,7 +9803,8 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9426
9803
  `imports ${sanitizeHistoricalText(rootSpecifier || item.specifier)}`
9427
9804
  )
9428
9805
  ],
9429
- confidence: parseJsonArray10(item.imported_symbols_json).length > 0 ? 0.88 : 0.76
9806
+ matchReasons: score.reasons,
9807
+ confidence: score.confidence
9430
9808
  });
9431
9809
  }
9432
9810
  if (shouldEmitProgress3(index + 1, imports.length)) {
@@ -9436,7 +9814,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9436
9814
  current: index + 1,
9437
9815
  total: imports.length,
9438
9816
  sourcePath: item.source_path,
9439
- edges: edges.length
9817
+ edges: fileEdgeMap.size
9440
9818
  });
9441
9819
  }
9442
9820
  });
@@ -9450,25 +9828,27 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9450
9828
  (chunk) => repoByName.has(chunk.repo) && isApiProviderPath(chunk.file_path)
9451
9829
  );
9452
9830
  const apiContracts = [];
9453
- const contractKeys = /* @__PURE__ */ new Set();
9831
+ const contractByKey = /* @__PURE__ */ new Map();
9454
9832
  const contractsByToken = /* @__PURE__ */ new Map();
9455
9833
  providerChunks.forEach((chunk, index) => {
9456
- for (const contract of extractContracts(chunk.sanitized_text)) {
9457
- const sanitizedContract = sanitizeHistoricalText(contract);
9458
- const key = [chunk.repo, chunk.file_path, sanitizedContract].join("\0");
9459
- if (contractKeys.has(key)) continue;
9460
- contractKeys.add(key);
9461
- 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 = {
9462
9838
  repo: chunk.repo,
9463
9839
  filePath: chunk.file_path,
9464
- contract: sanitizedContract,
9465
- evidence: [fileEvidence(chunk.repo, chunk.file_path, `defines ${sanitizedContract}`)],
9466
- 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 })
9467
9846
  };
9468
- apiContracts.push(apiContract);
9469
- const bucket = contractsByToken.get(sanitizedContract) ?? [];
9470
- bucket.push(apiContract);
9471
- 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);
9472
9852
  }
9473
9853
  if (shouldEmitProgress3(index + 1, providerChunks.length)) {
9474
9854
  options.onProgress?.({
@@ -9481,28 +9861,39 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9481
9861
  });
9482
9862
  }
9483
9863
  });
9484
- const apiConsumers = [];
9485
- const consumerKeys = /* @__PURE__ */ new Set();
9486
9864
  const consumerChunks = chunks.filter(
9487
9865
  (chunk) => repoByName.has(chunk.repo) && isApiConsumerText(chunk.sanitized_text)
9488
9866
  );
9867
+ const apiConsumers = [];
9868
+ const consumerKeySet = /* @__PURE__ */ new Set();
9489
9869
  consumerChunks.forEach((chunk, index) => {
9490
- const consumerTokens = extractContracts(chunk.sanitized_text);
9491
- let chunkMatches = 0;
9492
- for (const token of consumerTokens) {
9493
- const contracts = contractsByToken.get(sanitizeHistoricalText(token));
9494
- 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;
9495
9876
  for (const contract of contracts) {
9496
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;
9497
9884
  const consumerKey = [
9498
9885
  contract.repo,
9499
9886
  contract.filePath,
9500
9887
  chunk.repo,
9501
9888
  chunk.file_path,
9502
- contract.contract
9889
+ contract.normalizedContract
9503
9890
  ].join("\0");
9504
- if (consumerKeys.has(consumerKey)) continue;
9505
- 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
+ ]);
9506
9897
  const consumer = {
9507
9898
  org: config.org,
9508
9899
  providerRepo: contract.repo,
@@ -9510,23 +9901,24 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9510
9901
  consumerRepo: chunk.repo,
9511
9902
  consumerPath: chunk.file_path,
9512
9903
  contract: contract.contract,
9513
- evidence: [
9514
- ...contract.evidence,
9515
- fileEvidence(chunk.repo, chunk.file_path, `consumes ${contract.contract}`)
9516
- ],
9517
- 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
9518
9909
  };
9519
- chunkMatches += 1;
9520
9910
  apiConsumers.push(consumer);
9521
- addEdge({
9911
+ matchesForChunk += 1;
9912
+ addFileEdge({
9522
9913
  org: config.org,
9523
9914
  sourceRepo: chunk.repo,
9524
9915
  sourcePath: chunk.file_path,
9525
9916
  targetRepo: contract.repo,
9526
9917
  targetPath: contract.filePath,
9527
9918
  relationship: "api_consumer",
9528
- evidence: consumer.evidence,
9529
- confidence: consumer.confidence
9919
+ evidence,
9920
+ matchReasons: score.reasons,
9921
+ confidence: score.confidence
9530
9922
  });
9531
9923
  }
9532
9924
  }
@@ -9537,46 +9929,79 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9537
9929
  current: index + 1,
9538
9930
  total: consumerChunks.length,
9539
9931
  filePath: chunk.file_path,
9540
- matches: chunkMatches
9932
+ matches: matchesForChunk
9541
9933
  });
9542
9934
  }
9543
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);
9544
9945
  options.onProgress?.({
9545
9946
  stage: "writing_org_graph",
9546
9947
  org: config.org,
9547
- edges: edges.length,
9948
+ edges: repoEdges.length,
9548
9949
  apiContracts: apiContracts.length,
9549
9950
  apiConsumers: apiConsumers.length
9550
9951
  });
9551
9952
  const now = (/* @__PURE__ */ new Date()).toISOString();
9953
+ const renderPrepStartedAt = Date.now();
9552
9954
  const transaction = db.transaction(() => {
9553
9955
  db.prepare("DELETE FROM org_cross_repo_edges WHERE org = ?").run(config.org);
9554
9956
  db.prepare("DELETE FROM org_api_contracts WHERE org = ?").run(config.org);
9555
9957
  db.prepare("DELETE FROM org_api_consumers WHERE org = ?").run(config.org);
9556
9958
  const insertEdge = db.prepare(
9557
9959
  `INSERT INTO org_cross_repo_edges
9558
- (id, org, source_repo, source_path, target_repo, target_path, relationship, evidence_json, confidence, created_at)
9559
- 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
9560
9963
  ON CONFLICT(id) DO UPDATE SET
9964
+ source_path = excluded.source_path,
9965
+ target_path = excluded.target_path,
9966
+ layer = excluded.layer,
9561
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,
9562
9971
  confidence = excluded.confidence,
9563
9972
  created_at = excluded.created_at`
9564
9973
  );
9565
- for (const [index, edge] of edges.entries()) {
9974
+ const persistEdge = (edge) => {
9566
9975
  insertEdge.run(
9567
- `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
+ ])}`,
9568
9985
  edge.org,
9569
9986
  edge.sourceRepo,
9570
9987
  edge.sourcePath,
9571
9988
  edge.targetRepo,
9572
9989
  edge.targetPath ?? null,
9990
+ edge.layer,
9573
9991
  edge.relationship,
9574
9992
  evidenceJson(edge.evidence),
9993
+ JSON.stringify(edge.matchReasons),
9994
+ edge.evidenceCount,
9995
+ edge.weak ? 1 : 0,
9575
9996
  edge.confidence,
9576
9997
  now
9577
9998
  );
9999
+ };
10000
+ const persistedEdges = [...repoEdges, ...allFileEdges];
10001
+ for (const [index, edge] of persistedEdges.entries()) {
10002
+ persistEdge(edge);
9578
10003
  const current = index + 1;
9579
- if (shouldEmitProgress3(current, edges.length, 500)) {
10004
+ if (shouldEmitProgress3(current, persistedEdges.length, 500)) {
9580
10005
  options.onProgress?.({
9581
10006
  stage: "writing_org_graph",
9582
10007
  org: config.org,
@@ -9584,7 +10009,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9584
10009
  apiContracts: apiContracts.length,
9585
10010
  apiConsumers: apiConsumers.length,
9586
10011
  current,
9587
- total: edges.length,
10012
+ total: persistedEdges.length,
9588
10013
  kind: "edges"
9589
10014
  });
9590
10015
  }
@@ -9601,7 +10026,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9601
10026
  );
9602
10027
  for (const [index, contract] of apiContracts.entries()) {
9603
10028
  insertContract.run(
9604
- `oac_${stableId([config.org, contract.repo, contract.filePath, contract.contract])}`,
10029
+ `oac_${stableId([config.org, contract.repo, contract.filePath, contract.normalizedContract])}`,
9605
10030
  config.org,
9606
10031
  contract.repo,
9607
10032
  contract.filePath,
@@ -9615,7 +10040,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9615
10040
  options.onProgress?.({
9616
10041
  stage: "writing_org_graph",
9617
10042
  org: config.org,
9618
- edges: edges.length,
10043
+ edges: persistedEdges.length,
9619
10044
  apiContracts: current,
9620
10045
  apiConsumers: apiConsumers.length,
9621
10046
  current,
@@ -9626,11 +10051,15 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9626
10051
  }
9627
10052
  const insertConsumer = db.prepare(
9628
10053
  `INSERT INTO org_api_consumers
9629
- (id, org, provider_repo, provider_path, consumer_repo, consumer_path, contract, evidence_json, confidence, created_at)
9630
- 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
9631
10057
  ON CONFLICT(id) DO UPDATE SET
9632
10058
  contract = excluded.contract,
9633
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,
9634
10063
  confidence = excluded.confidence,
9635
10064
  created_at = excluded.created_at`
9636
10065
  );
@@ -9642,7 +10071,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9642
10071
  consumer.providerPath ?? "",
9643
10072
  consumer.consumerRepo,
9644
10073
  consumer.consumerPath,
9645
- consumer.contract
10074
+ normalizeToken(consumer.contract)
9646
10075
  ])}`,
9647
10076
  consumer.org,
9648
10077
  consumer.providerRepo,
@@ -9651,6 +10080,9 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9651
10080
  consumer.consumerPath,
9652
10081
  sanitizeHistoricalText(consumer.contract),
9653
10082
  evidenceJson(consumer.evidence),
10083
+ JSON.stringify(consumer.matchReasons),
10084
+ consumer.evidenceCount,
10085
+ consumer.weak ? 1 : 0,
9654
10086
  consumer.confidence,
9655
10087
  now
9656
10088
  );
@@ -9659,7 +10091,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9659
10091
  options.onProgress?.({
9660
10092
  stage: "writing_org_graph",
9661
10093
  org: config.org,
9662
- edges: edges.length,
10094
+ edges: persistedEdges.length,
9663
10095
  apiContracts: apiContracts.length,
9664
10096
  apiConsumers: current,
9665
10097
  current,
@@ -9670,6 +10102,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9670
10102
  }
9671
10103
  });
9672
10104
  transaction();
10105
+ const renderPrepMs = Date.now() - renderPrepStartedAt;
9673
10106
  const durationMs = Date.now() - startedAt;
9674
10107
  const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
9675
10108
  recordOrgGraphState(db, {
@@ -9677,22 +10110,40 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
9677
10110
  status: "success",
9678
10111
  builtAt: finishedAt,
9679
10112
  durationMs,
9680
- edgeCount: edges.length,
10113
+ edgeCount: repoEdges.length,
10114
+ visibleEdgeCount: visibleRepoEdges.length,
10115
+ weakEdgeCount: hiddenRepoEdges.length,
10116
+ edgeConfidenceDistribution: quality.edgeConfidenceDistribution,
10117
+ lastRenderPrepMs: renderPrepMs,
9681
10118
  apiContractCount: apiContracts.length,
9682
10119
  apiConsumerCount: apiConsumers.length
9683
10120
  });
9684
10121
  options.onProgress?.({
9685
10122
  stage: "completed_org_graph",
9686
10123
  org: config.org,
9687
- edges: edges.length,
10124
+ edges: repoEdges.length,
9688
10125
  apiContracts: apiContracts.length,
9689
10126
  apiConsumers: apiConsumers.length,
9690
10127
  durationMs
9691
10128
  });
9692
10129
  return {
9693
- edges,
10130
+ edges: visibleRepoEdges,
10131
+ repoEdges: visibleRepoEdges,
10132
+ fileEdges: visibleFileEdges,
10133
+ hiddenFileEdges,
10134
+ hiddenRepoEdges,
9694
10135
  apiConsumers,
9695
- 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
+ },
9696
10147
  durationMs
9697
10148
  };
9698
10149
  } catch (error) {
@@ -10036,6 +10487,8 @@ async function indexOrgRepos(db, config, options = {}) {
10036
10487
  org: config.org,
10037
10488
  status: "skipped",
10038
10489
  edgeCount: counts.edges,
10490
+ visibleEdgeCount: counts.visibleEdges,
10491
+ weakEdgeCount: counts.weakEdges,
10039
10492
  apiContractCount: counts.apiContracts,
10040
10493
  apiConsumerCount: counts.apiConsumers
10041
10494
  });
@@ -10045,7 +10498,12 @@ async function indexOrgRepos(db, config, options = {}) {
10045
10498
  command,
10046
10499
  reason: "Graph skipped because --no-graph was passed."
10047
10500
  });
10048
- graph = { ...counts, skipped: true };
10501
+ graph = {
10502
+ edges: counts.visibleEdges,
10503
+ apiConsumers: counts.apiConsumers,
10504
+ apiContracts: counts.apiContracts,
10505
+ skipped: true
10506
+ };
10049
10507
  } else {
10050
10508
  try {
10051
10509
  const rebuiltGraph = rebuildOrgGraph(db, config, {
@@ -10060,7 +10518,12 @@ async function indexOrgRepos(db, config, options = {}) {
10060
10518
  } catch (error) {
10061
10519
  const message = error instanceof Error ? error.message : String(error);
10062
10520
  const counts = getOrgGraphCounts(db, config.org);
10063
- graph = { ...counts, error: message };
10521
+ graph = {
10522
+ edges: counts.visibleEdges,
10523
+ apiConsumers: counts.apiConsumers,
10524
+ apiContracts: counts.apiContracts,
10525
+ error: message
10526
+ };
10064
10527
  }
10065
10528
  }
10066
10529
  recordOrgIndexRun(db, {
@@ -10104,6 +10567,15 @@ function parseEvidence2(value) {
10104
10567
  return [];
10105
10568
  }
10106
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
+ }
10107
10579
  function fileEvidence2(repo, filePath, note) {
10108
10580
  return {
10109
10581
  prNumber: 0,
@@ -10135,7 +10607,7 @@ function affectedConsumers(db, org, repo, changedFiles) {
10135
10607
  const rows = db.prepare(
10136
10608
  `SELECT provider_repo, provider_path, consumer_repo, consumer_path, contract, evidence_json, confidence
10137
10609
  FROM org_api_consumers
10138
- WHERE org = ?`
10610
+ WHERE org = ? AND is_weak = 0`
10139
10611
  ).all(org);
10140
10612
  return rows.filter((row) => !repo || row.provider_repo === repo || row.consumer_repo === repo).filter((row) => {
10141
10613
  if (changedFiles.length === 0) return true;
@@ -10150,14 +10622,18 @@ function affectedConsumers(db, org, repo, changedFiles) {
10150
10622
  consumerPath: row.consumer_path,
10151
10623
  contract: sanitizeHistoricalText(row.contract),
10152
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,
10153
10628
  confidence: row.confidence
10154
10629
  }));
10155
10630
  }
10156
10631
  function affectedEdges(db, org, repo, changedFiles) {
10157
10632
  const rows = db.prepare(
10158
- `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
10159
10635
  FROM org_cross_repo_edges
10160
- WHERE org = ?`
10636
+ WHERE org = ? AND layer = 'file'`
10161
10637
  ).all(org);
10162
10638
  return rows.filter((row) => !repo || row.source_repo === repo || row.target_repo === repo).filter((row) => {
10163
10639
  if (changedFiles.length === 0) return true;
@@ -10168,8 +10644,12 @@ function affectedEdges(db, org, repo, changedFiles) {
10168
10644
  sourcePath: row.source_path,
10169
10645
  targetRepo: row.target_repo,
10170
10646
  targetPath: row.target_path ?? void 0,
10647
+ layer: row.layer,
10171
10648
  relationship: row.relationship,
10172
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,
10173
10653
  confidence: row.confidence
10174
10654
  }));
10175
10655
  }
@@ -10487,7 +10967,7 @@ function evidenceLabel(evidence) {
10487
10967
  if (first.prNumber > 0) return `PR #${first.prNumber}`;
10488
10968
  return first.filePath ? `file ${first.filePath}` : first.note ?? "local file evidence";
10489
10969
  }
10490
- function parseStringArray(value) {
10970
+ function parseStringArray2(value) {
10491
10971
  try {
10492
10972
  const parsed = JSON.parse(value);
10493
10973
  return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
@@ -10534,7 +11014,7 @@ function getWisdom(db, input, limit) {
10534
11014
  const lowerTerms = queryTerms(input).map((term) => term.toLowerCase());
10535
11015
  return rows.filter((row) => matchesRepo(row.repo, input.repos)).map((row) => ({
10536
11016
  row,
10537
- 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)
10538
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);
10539
11019
  }
10540
11020
  function getCodeEvidence(db, input, limit) {
@@ -10551,7 +11031,7 @@ function getCodeEvidence(db, input, limit) {
10551
11031
  input,
10552
11032
  row.sanitized_text,
10553
11033
  [row.file_path],
10554
- parseStringArray(row.symbols_json),
11034
+ parseStringArray2(row.symbols_json),
10555
11035
  lowerTerms
10556
11036
  )
10557
11037
  })).filter((item) => item.score > 0).sort((a, b) => b.score - a.score).slice(0, limit).map((item) => item.row);
@@ -10569,7 +11049,7 @@ function getArchitecture(db, input, limit) {
10569
11049
  score: rowScore(
10570
11050
  input,
10571
11051
  row.summary_sanitized,
10572
- parseStringArray(row.source_files_json),
11052
+ parseStringArray2(row.source_files_json),
10573
11053
  [],
10574
11054
  lowerTerms
10575
11055
  )
@@ -10580,9 +11060,10 @@ function findOrgApiConsumers(db, config, input) {
10580
11060
  const repoClause = input.repo ? " AND (provider_repo = ? OR consumer_repo = ?)" : "";
10581
11061
  const repoParams = input.repo ? [input.repo, input.repo] : [];
10582
11062
  const rows = db.prepare(
10583
- `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
10584
11065
  FROM org_api_consumers
10585
- WHERE org = ?${repoClause}
11066
+ WHERE org = ? AND is_weak = 0${repoClause}
10586
11067
  ORDER BY confidence DESC`
10587
11068
  ).all(config.org, ...repoParams);
10588
11069
  const limit = Math.max(1, Math.min(input.maxResults ?? 8, 25));
@@ -10598,6 +11079,9 @@ function findOrgApiConsumers(db, config, input) {
10598
11079
  consumerPath: row.consumer_path,
10599
11080
  contract: sanitizeHistoricalText(row.contract),
10600
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,
10601
11085
  confidence: row.confidence
10602
11086
  }));
10603
11087
  }
@@ -10606,7 +11090,7 @@ function getOrgArchitectureMap(db, config, format = "mermaid") {
10606
11090
  const rows = db.prepare(
10607
11091
  `SELECT source_repo, source_path, target_repo, target_path, relationship, confidence
10608
11092
  FROM org_cross_repo_edges
10609
- WHERE org = ?
11093
+ WHERE org = ? AND layer = 'repo' AND is_weak = 0
10610
11094
  ORDER BY confidence DESC, source_repo, target_repo`
10611
11095
  ).all(config.org);
10612
11096
  const nodes = uniqueStrings(rows.flatMap((row) => [row.source_repo, row.target_repo])).map(
@@ -10684,7 +11168,7 @@ function buildOrgContextResult(db, config, input) {
10684
11168
  if (architecture.length === 0) lines.push("- No matching architecture patterns found.");
10685
11169
  else {
10686
11170
  for (const pattern of architecture) {
10687
- const files = parseStringArray(pattern.source_files_json);
11171
+ const files = parseStringArray2(pattern.source_files_json);
10688
11172
  lines.push(
10689
11173
  `- [${pattern.repo}] [${pattern.area}] ${pattern.summary_sanitized} Evidence: ${files[0] ?? "indexed current code"}.`
10690
11174
  );