@pratik7368patil/anchor-core 0.1.25 → 0.1.27

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
@@ -1299,6 +1299,7 @@ function countValidTeamRules(cwd) {
1299
1299
 
1300
1300
  // src/indexer/test-awareness.ts
1301
1301
  import path3 from "path";
1302
+ var TEST_AWARENESS_PROGRESS_INTERVAL = 500;
1302
1303
  function normalizePath(filePath) {
1303
1304
  return filePath.replace(/\\/g, "/").replace(/^\.\/+/, "");
1304
1305
  }
@@ -1335,25 +1336,127 @@ function strengthFor(reason) {
1335
1336
  if (reason === "same directory") return 0.7;
1336
1337
  return 0.5;
1337
1338
  }
1338
- function pathMentionedInTest(testPath, sourcePath, chunksByFile) {
1339
- const text = (chunksByFile.get(testPath) ?? []).map((chunk) => chunk.sanitizedText).join("\n");
1340
- if (!text) return false;
1341
- const sourceNoExt = sourcePath.replace(/\.[^.]+$/i, "");
1342
- const sourceBase = basenameWithoutExtensions(sourcePath);
1343
- return text.includes(sourcePath) || text.includes(sourceNoExt) || new RegExp(`from\\s+["'][^"']*${escapeRegExp(sourceBase)}["']`, "i").test(text) || new RegExp(`require\\(["'][^"']*${escapeRegExp(sourceBase)}["']\\)`, "i").test(text);
1339
+ function shouldEmitProgress(current, total) {
1340
+ return current === 0 || current === 1 || current === total || current % TEST_AWARENESS_PROGRESS_INTERVAL === 0;
1344
1341
  }
1345
- function escapeRegExp(value) {
1346
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1342
+ function addToMap(map, key, value) {
1343
+ const values = map.get(key) ?? [];
1344
+ values.push(value);
1345
+ map.set(key, values);
1346
+ }
1347
+ function withoutExtension(filePath) {
1348
+ return normalizePath(filePath).replace(/\.[^.]+$/i, "");
1349
+ }
1350
+ function testText(testPath, chunksByFile) {
1351
+ return (chunksByFile.get(testPath) ?? []).map((chunk) => chunk.sanitizedText).join("\n");
1352
+ }
1353
+ function importSpecifiers(text) {
1354
+ const specifiers = [];
1355
+ const patterns = [
1356
+ /\bfrom\s+["']([^"']+)["']/g,
1357
+ /\bimport\s*\(\s*["']([^"']+)["']\s*\)/g,
1358
+ /\brequire\s*\(\s*["']([^"']+)["']\s*\)/g
1359
+ ];
1360
+ for (const pattern of patterns) {
1361
+ for (const match of text.matchAll(pattern)) {
1362
+ if (match[1]) specifiers.push(match[1]);
1363
+ }
1364
+ }
1365
+ return uniqueStrings(specifiers);
1366
+ }
1367
+ function pathLikeMentions(text) {
1368
+ const mentions = /* @__PURE__ */ new Set();
1369
+ const pattern = /[A-Za-z0-9_@./-]+(?:\.[A-Za-z0-9_@./-]+)?/g;
1370
+ for (const match of text.matchAll(pattern)) {
1371
+ const value = match[0];
1372
+ if (value.includes("/") || /\.[A-Za-z0-9]+$/.test(value)) mentions.add(value);
1373
+ }
1374
+ return [...mentions];
1347
1375
  }
1348
- function inferTestAwareness(repo, codeFiles, codeChunks) {
1349
- const testFiles = codeFiles.filter((file) => isTestFilePath(file.path));
1350
- const sourceFiles = codeFiles.filter((file) => !isTestFilePath(file.path));
1376
+ function sourceCandidatesForSpecifier(testPath, specifier, sourcesByBase, sourcesByPath, sourcesByNoExt) {
1377
+ const normalizedSpecifier = normalizePath(specifier);
1378
+ const candidates = [];
1379
+ const add = (items) => {
1380
+ if (items) candidates.push(...items);
1381
+ };
1382
+ add(sourcesByPath.get(normalizedSpecifier));
1383
+ add(sourcesByNoExt.get(normalizedSpecifier));
1384
+ if (normalizedSpecifier.startsWith(".")) {
1385
+ const resolved = normalizePath(path3.posix.join(path3.posix.dirname(testPath), normalizedSpecifier));
1386
+ add(sourcesByPath.get(resolved));
1387
+ add(sourcesByNoExt.get(resolved));
1388
+ }
1389
+ const base = basenameWithoutExtensions(normalizedSpecifier).toLowerCase();
1390
+ if (base) add(sourcesByBase.get(base));
1391
+ return uniqueStrings(candidates.map((source) => source.path)).map((sourcePath) => sourcesByPath.get(sourcePath)?.[0]).filter((source) => source !== void 0);
1392
+ }
1393
+ function inferTestAwareness(repo, codeFiles, codeChunks, options = {}) {
1394
+ const testFiles = [];
1395
+ const sourceFiles = [];
1396
+ options.onProgress?.({
1397
+ stage: "inferring_test_awareness",
1398
+ repo,
1399
+ phase: "classifying_files",
1400
+ current: 0,
1401
+ total: codeFiles.length,
1402
+ testFiles: 0,
1403
+ testLinks: 0
1404
+ });
1405
+ for (const [index, file] of codeFiles.entries()) {
1406
+ if (isTestFilePath(file.path)) testFiles.push(file);
1407
+ else sourceFiles.push(file);
1408
+ const current = index + 1;
1409
+ if (shouldEmitProgress(current, codeFiles.length)) {
1410
+ options.onProgress?.({
1411
+ stage: "inferring_test_awareness",
1412
+ repo,
1413
+ phase: "classifying_files",
1414
+ current,
1415
+ total: codeFiles.length,
1416
+ filePath: file.path,
1417
+ testFiles: testFiles.length,
1418
+ testLinks: 0
1419
+ });
1420
+ }
1421
+ }
1351
1422
  const chunksByFile = /* @__PURE__ */ new Map();
1352
1423
  for (const chunk of codeChunks) {
1353
1424
  const chunks = chunksByFile.get(chunk.filePath) ?? [];
1354
1425
  chunks.push(chunk);
1355
1426
  chunksByFile.set(chunk.filePath, chunks);
1356
1427
  }
1428
+ const sourcesByBase = /* @__PURE__ */ new Map();
1429
+ const sourcesByDir = /* @__PURE__ */ new Map();
1430
+ const sourcesByPath = /* @__PURE__ */ new Map();
1431
+ const sourcesByNoExt = /* @__PURE__ */ new Map();
1432
+ options.onProgress?.({
1433
+ stage: "inferring_test_awareness",
1434
+ repo,
1435
+ phase: "indexing_sources",
1436
+ current: 0,
1437
+ total: sourceFiles.length,
1438
+ testFiles: testFiles.length,
1439
+ testLinks: 0
1440
+ });
1441
+ for (const [index, source] of sourceFiles.entries()) {
1442
+ addToMap(sourcesByBase, basenameWithoutExtensions(source.path).toLowerCase(), source);
1443
+ addToMap(sourcesByDir, sourceLikeDir(source.path).join("/"), source);
1444
+ addToMap(sourcesByPath, normalizePath(source.path), source);
1445
+ addToMap(sourcesByNoExt, withoutExtension(source.path), source);
1446
+ const current = index + 1;
1447
+ if (shouldEmitProgress(current, sourceFiles.length)) {
1448
+ options.onProgress?.({
1449
+ stage: "inferring_test_awareness",
1450
+ repo,
1451
+ phase: "indexing_sources",
1452
+ current,
1453
+ total: sourceFiles.length,
1454
+ filePath: source.path,
1455
+ testFiles: testFiles.length,
1456
+ testLinks: 0
1457
+ });
1458
+ }
1459
+ }
1357
1460
  const linkMap = /* @__PURE__ */ new Map();
1358
1461
  const addLink = (sourcePath, testPath, reason) => {
1359
1462
  const key = `${sourcePath}\0${testPath}\0${reason}`;
@@ -1365,22 +1468,78 @@ function inferTestAwareness(repo, codeFiles, codeChunks) {
1365
1468
  strength: strengthFor(reason)
1366
1469
  });
1367
1470
  };
1368
- for (const test of testFiles) {
1471
+ options.onProgress?.({
1472
+ stage: "inferring_test_awareness",
1473
+ repo,
1474
+ phase: "linking_tests",
1475
+ current: 0,
1476
+ total: testFiles.length,
1477
+ testFiles: testFiles.length,
1478
+ testLinks: 0
1479
+ });
1480
+ for (const [index, test] of testFiles.entries()) {
1369
1481
  const testBase = basenameWithoutExtensions(test.path).toLowerCase();
1370
1482
  const testDir = sourceLikeDir(test.path).join("/");
1371
- for (const source of sourceFiles) {
1372
- const sourceBase = basenameWithoutExtensions(source.path).toLowerCase();
1373
- const sourceDir = sourceLikeDir(source.path).join("/");
1374
- if (testBase === sourceBase) addLink(source.path, test.path, "same basename");
1375
- else if (testDir && sourceDir && testDir === sourceDir) {
1483
+ for (const source of sourcesByBase.get(testBase) ?? []) {
1484
+ addLink(source.path, test.path, "same basename");
1485
+ }
1486
+ if (testDir) {
1487
+ for (const source of sourcesByDir.get(testDir) ?? []) {
1488
+ if (basenameWithoutExtensions(source.path).toLowerCase() === testBase) continue;
1376
1489
  addLink(source.path, test.path, "same directory");
1377
1490
  }
1378
- if (pathMentionedInTest(test.path, source.path, chunksByFile)) {
1379
- addLink(source.path, test.path, "imported source path");
1491
+ }
1492
+ const text = testText(test.path, chunksByFile);
1493
+ const importedSources = /* @__PURE__ */ new Map();
1494
+ for (const specifier of importSpecifiers(text)) {
1495
+ for (const source of sourceCandidatesForSpecifier(
1496
+ test.path,
1497
+ specifier,
1498
+ sourcesByBase,
1499
+ sourcesByPath,
1500
+ sourcesByNoExt
1501
+ )) {
1502
+ importedSources.set(source.path, source);
1380
1503
  }
1381
1504
  }
1505
+ for (const mention of pathLikeMentions(text)) {
1506
+ for (const source of sourceCandidatesForSpecifier(
1507
+ test.path,
1508
+ mention,
1509
+ sourcesByBase,
1510
+ sourcesByPath,
1511
+ sourcesByNoExt
1512
+ )) {
1513
+ importedSources.set(source.path, source);
1514
+ }
1515
+ }
1516
+ for (const source of importedSources.values()) {
1517
+ addLink(source.path, test.path, "imported source path");
1518
+ }
1519
+ const current = index + 1;
1520
+ if (shouldEmitProgress(current, testFiles.length)) {
1521
+ options.onProgress?.({
1522
+ stage: "inferring_test_awareness",
1523
+ repo,
1524
+ phase: "linking_tests",
1525
+ current,
1526
+ total: testFiles.length,
1527
+ filePath: test.path,
1528
+ testFiles: testFiles.length,
1529
+ testLinks: linkMap.size
1530
+ });
1531
+ }
1382
1532
  }
1383
1533
  const dedupedTests = testFiles.map(testRecord);
1534
+ options.onProgress?.({
1535
+ stage: "inferring_test_awareness",
1536
+ repo,
1537
+ phase: "completed",
1538
+ current: testFiles.length,
1539
+ total: testFiles.length,
1540
+ testFiles: dedupedTests.length,
1541
+ testLinks: linkMap.size
1542
+ });
1384
1543
  return {
1385
1544
  testFiles: dedupedTests,
1386
1545
  testLinks: uniqueStrings([...linkMap.keys()]).map((key) => linkMap.get(key))
@@ -1535,7 +1694,7 @@ function calculateCoverage(input) {
1535
1694
  }
1536
1695
 
1537
1696
  // src/db/database.ts
1538
- var CODE_WRITE_PROGRESS_INTERVAL = 500;
1697
+ var CODE_WRITE_PROGRESS_INTERVAL = 150;
1539
1698
  function shouldEmitCodeWriteProgress(current, total) {
1540
1699
  return current === 0 || current === 1 || current === total || current % CODE_WRITE_PROGRESS_INTERVAL === 0;
1541
1700
  }
@@ -1911,7 +2070,9 @@ function replaceCodeIndex(db, repo, codeFiles, codeChunks, skippedFiles, cwd, ar
1911
2070
  const repoId = ensureRepository(db, repo);
1912
2071
  const now = (/* @__PURE__ */ new Date()).toISOString();
1913
2072
  options.onProgress?.({ stage: "writing_code_index", repo, phase: "Inferring test awareness" });
1914
- const testAwareness = inferTestAwareness(repo, codeFiles, codeChunks);
2073
+ const testAwareness = inferTestAwareness(repo, codeFiles, codeChunks, {
2074
+ onProgress: options.onProgress
2075
+ });
1915
2076
  options.onProgress?.({ stage: "writing_code_index", repo, phase: "Writing code index" });
1916
2077
  const transaction = db.transaction(() => {
1917
2078
  const existingChunks = db.prepare("SELECT id FROM code_chunks WHERE repo_id = ?").all(repoId);
@@ -2635,8 +2796,8 @@ function chunkCodeFile(file, options = {}) {
2635
2796
  import crypto2 from "crypto";
2636
2797
  import path6 from "path";
2637
2798
  var KNOWN_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json"];
2638
- var ARCHITECTURE_PROGRESS_INTERVAL = 250;
2639
- function shouldEmitProgress(current, total) {
2799
+ var ARCHITECTURE_PROGRESS_INTERVAL = 100;
2800
+ function shouldEmitProgress2(current, total) {
2640
2801
  return current === 0 || current === 1 || current === total || current % ARCHITECTURE_PROGRESS_INTERVAL === 0;
2641
2802
  }
2642
2803
  function classifyArchitectureArea(filePath, language, content = "") {
@@ -2747,15 +2908,47 @@ function extractCodeImports(sourcePath, content, codePaths, repo = "") {
2747
2908
  return true;
2748
2909
  });
2749
2910
  }
2750
- function relatedTestsFor(filePath, allPaths) {
2911
+ function addToStringMap(map, key, value) {
2912
+ const values = map.get(key) ?? [];
2913
+ values.push(value);
2914
+ map.set(key, values);
2915
+ }
2916
+ function testBaseFor(filePath) {
2917
+ return path6.posix.parse(filePath).name.replace(/\.(test|spec)$/i, "");
2918
+ }
2919
+ function buildRelatedTestIndex(allPaths) {
2920
+ const testPaths = allPaths.filter((candidate) => isTestFilePath(candidate));
2921
+ const byBase = /* @__PURE__ */ new Map();
2922
+ const byDirectory = /* @__PURE__ */ new Map();
2923
+ for (const testPath of testPaths) {
2924
+ addToStringMap(byBase, testBaseFor(testPath), testPath);
2925
+ const segments = path6.posix.dirname(testPath).split("/").filter(Boolean);
2926
+ for (let index = 1; index <= segments.length; index += 1) {
2927
+ addToStringMap(byDirectory, segments.slice(0, index).join("/"), testPath);
2928
+ }
2929
+ }
2930
+ return { testPaths, byBase, byDirectory };
2931
+ }
2932
+ function relatedTestsFor(filePath, index) {
2751
2933
  if (isTestFilePath(filePath)) return [];
2752
2934
  const parsed = path6.posix.parse(filePath);
2753
2935
  const basename = parsed.name.replace(/\.(test|spec)$/i, "");
2754
- return allPaths.filter((candidate) => isTestFilePath(candidate)).filter((candidate) => {
2755
- const candidateParsed = path6.posix.parse(candidate);
2756
- const candidateBase = candidateParsed.name.replace(/\.(test|spec)$/i, "");
2757
- return candidateBase === basename || candidate.startsWith(`${parsed.dir}/`) || candidate.includes(`/${basename}.`);
2758
- }).slice(0, 8);
2936
+ const related = [];
2937
+ const seen = /* @__PURE__ */ new Set();
2938
+ const add = (testPath) => {
2939
+ if (seen.has(testPath)) return;
2940
+ seen.add(testPath);
2941
+ related.push(testPath);
2942
+ };
2943
+ for (const testPath of index.byBase.get(basename) ?? []) add(testPath);
2944
+ if (parsed.dir) {
2945
+ for (const testPath of index.byDirectory.get(parsed.dir) ?? []) add(testPath);
2946
+ }
2947
+ for (const testPath of index.testPaths) {
2948
+ if (testPath.includes(`/${basename}.`)) add(testPath);
2949
+ if (related.length >= 8) break;
2950
+ }
2951
+ return related.slice(0, 8);
2759
2952
  }
2760
2953
  function directoryLabel(filePath) {
2761
2954
  const directory = path6.posix.dirname(filePath.replace(/\\/g, "/"));
@@ -2789,10 +2982,15 @@ function createPattern(input) {
2789
2982
  function buildArchitectureIndex(repo, files, chunks, options = {}) {
2790
2983
  const allPaths = files.map((file) => file.path);
2791
2984
  const codePaths = new Set(allPaths);
2792
- const symbolsByPath = /* @__PURE__ */ new Map();
2985
+ const relatedTestIndex = buildRelatedTestIndex(allPaths);
2986
+ const symbolSetsByPath = /* @__PURE__ */ new Map();
2793
2987
  for (const chunk of chunks) {
2794
- const existing = symbolsByPath.get(chunk.filePath) ?? [];
2795
- symbolsByPath.set(chunk.filePath, uniqueStrings([...existing, ...chunk.symbols]).slice(0, 40));
2988
+ const existing = symbolSetsByPath.get(chunk.filePath) ?? /* @__PURE__ */ new Set();
2989
+ for (const symbol of chunk.symbols) {
2990
+ if (existing.size >= 40) break;
2991
+ existing.add(symbol);
2992
+ }
2993
+ symbolSetsByPath.set(chunk.filePath, existing);
2796
2994
  }
2797
2995
  const imports = [];
2798
2996
  options.onProgress?.({
@@ -2805,7 +3003,7 @@ function buildArchitectureIndex(repo, files, chunks, options = {}) {
2805
3003
  for (const [index, file] of files.entries()) {
2806
3004
  imports.push(...extractCodeImports(file.path, file.content, codePaths, repo));
2807
3005
  const current = index + 1;
2808
- if (shouldEmitProgress(current, files.length)) {
3006
+ if (shouldEmitProgress2(current, files.length)) {
2809
3007
  options.onProgress?.({
2810
3008
  stage: "building_architecture_imports",
2811
3009
  repo,
@@ -2833,7 +3031,7 @@ function buildArchitectureIndex(repo, files, chunks, options = {}) {
2833
3031
  for (const [index, file] of files.entries()) {
2834
3032
  const area = classifyArchitectureArea(file.path, file.language, file.content);
2835
3033
  const fileImports = importsByPath.get(file.path) ?? [];
2836
- const symbols = symbolsByPath.get(file.path) ?? [];
3034
+ const symbols = [...symbolSetsByPath.get(file.path) ?? []];
2837
3035
  components.push({
2838
3036
  repo,
2839
3037
  path: file.path,
@@ -2844,12 +3042,12 @@ function buildArchitectureIndex(repo, files, chunks, options = {}) {
2844
3042
  imports: uniqueStrings(
2845
3043
  fileImports.map((item) => item.importedPath ?? item.specifier).filter(Boolean)
2846
3044
  ).slice(0, 20),
2847
- relatedTests: relatedTestsFor(file.path, allPaths),
3045
+ relatedTests: relatedTestsFor(file.path, relatedTestIndex),
2848
3046
  confidence: area === "unknown" ? 0.45 : 0.82,
2849
3047
  updatedAt: file.updatedAt
2850
3048
  });
2851
3049
  const current = index + 1;
2852
- if (shouldEmitProgress(current, files.length)) {
3050
+ if (shouldEmitProgress2(current, files.length)) {
2853
3051
  options.onProgress?.({
2854
3052
  stage: "building_architecture_components",
2855
3053
  repo,
@@ -2906,7 +3104,7 @@ function buildArchitectureIndex(repo, files, chunks, options = {}) {
2906
3104
  })
2907
3105
  );
2908
3106
  patternProgress += 1;
2909
- if (shouldEmitProgress(patternProgress, patternTotal)) {
3107
+ if (shouldEmitProgress2(patternProgress, patternTotal)) {
2910
3108
  options.onProgress?.({
2911
3109
  stage: "building_architecture_patterns",
2912
3110
  repo,
@@ -2931,7 +3129,7 @@ function buildArchitectureIndex(repo, files, chunks, options = {}) {
2931
3129
  })
2932
3130
  );
2933
3131
  patternProgress += 1;
2934
- if (shouldEmitProgress(patternProgress, patternTotal)) {
3132
+ if (shouldEmitProgress2(patternProgress, patternTotal)) {
2935
3133
  options.onProgress?.({
2936
3134
  stage: "building_architecture_patterns",
2937
3135
  repo,
@@ -3045,12 +3243,19 @@ function discoverGitFiles(cwd) {
3045
3243
  });
3046
3244
  return output.split("\n").map((line) => normalizeGitPath(line.trim())).filter(Boolean);
3047
3245
  }
3246
+ var DISCOVERY_SCAN_INTERVAL = 200;
3048
3247
  function discoverCodeFiles(cwd, repo, options = {}) {
3049
3248
  const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_CODE_FILE_BYTES;
3050
3249
  const rootPath = path7.resolve(cwd);
3051
3250
  const files = [];
3052
3251
  let skippedFiles = 0;
3053
- for (const filePath of discoverGitFiles(cwd)) {
3252
+ const gitFiles = discoverGitFiles(cwd);
3253
+ const total = gitFiles.length;
3254
+ for (const [scanIndex, filePath] of gitFiles.entries()) {
3255
+ const scanned = scanIndex + 1;
3256
+ if (scanned % DISCOVERY_SCAN_INTERVAL === 0 || scanned === total) {
3257
+ options.onScan?.(scanned, total);
3258
+ }
3054
3259
  if (isHardExcludedCodePath(filePath)) {
3055
3260
  skippedFiles += 1;
3056
3261
  continue;
@@ -3326,7 +3531,8 @@ function refreshTestCommands(db, cwd, repo, files = [], options = {}) {
3326
3531
  function indexCodebase(db, options) {
3327
3532
  options.onProgress?.({ stage: "discovering_code_files", repo: options.repo });
3328
3533
  const discovery = discoverCodeFiles(options.cwd, options.repo, {
3329
- maxFileBytes: options.maxFileBytes
3534
+ maxFileBytes: options.maxFileBytes,
3535
+ onScan: (scanned, total) => options.onProgress?.({ stage: "discovering_code_files", repo: options.repo, scanned, total })
3330
3536
  });
3331
3537
  options.onProgress?.({
3332
3538
  stage: "discovered_code_files",
@@ -3887,7 +4093,7 @@ function symbolMatch2(unit, querySymbols) {
3887
4093
  const lower = symbol.toLowerCase();
3888
4094
  if (unitSymbols.includes(lower)) best = Math.max(best, 1);
3889
4095
  else if (text.includes(`\`${lower}\``)) best = Math.max(best, 1);
3890
- else if (new RegExp(`\\b${escapeRegExp2(lower)}\\b`, "i").test(text))
4096
+ else if (new RegExp(`\\b${escapeRegExp(lower)}\\b`, "i").test(text))
3891
4097
  best = Math.max(best, 0.66);
3892
4098
  else if (unitSymbols.some((candidate) => candidate.includes(lower) || lower.includes(candidate))) {
3893
4099
  best = Math.max(best, 0.35);
@@ -3968,7 +4174,7 @@ function scoreUnit(unit, input, duplicateCount, repeatedEvidenceCount, freshness
3968
4174
  rankSignals: parts
3969
4175
  };
3970
4176
  }
3971
- function escapeRegExp2(value) {
4177
+ function escapeRegExp(value) {
3972
4178
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3973
4179
  }
3974
4180
  function loadCandidates(db, input) {
@@ -4131,7 +4337,7 @@ function symbolMatch3(chunk, querySymbols) {
4131
4337
  for (const symbol of querySymbols) {
4132
4338
  const lower = symbol.toLowerCase();
4133
4339
  if (chunkSymbols.includes(lower)) best = Math.max(best, 1);
4134
- else if (new RegExp(`\\b${escapeRegExp3(lower)}\\b`, "i").test(text)) best = Math.max(best, 0.7);
4340
+ else if (new RegExp(`\\b${escapeRegExp2(lower)}\\b`, "i").test(text)) best = Math.max(best, 0.7);
4135
4341
  else if (chunkSymbols.some((candidate) => candidate.includes(lower) || lower.includes(candidate))) {
4136
4342
  best = Math.max(best, 0.42);
4137
4343
  }
@@ -4167,7 +4373,7 @@ function matchReasons3(parts) {
4167
4373
  if (parts.recency >= 0.75) reasons.push("recent code file");
4168
4374
  return reasons.slice(0, 5);
4169
4375
  }
4170
- function escapeRegExp3(value) {
4376
+ function escapeRegExp2(value) {
4171
4377
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4172
4378
  }
4173
4379
  function escapeLike(value) {
@@ -4402,7 +4608,7 @@ function rowToRanked(row, input) {
4402
4608
  const text = row.sanitized_text ?? "";
4403
4609
  const matchedSymbols = (input.symbols ?? []).filter((symbol) => {
4404
4610
  const lower = symbol.toLowerCase();
4405
- return symbols.some((candidate) => candidate.toLowerCase() === lower) || new RegExp(`\\b${escapeRegExp4(symbol)}\\b`, "i").test(text);
4611
+ return symbols.some((candidate) => candidate.toLowerCase() === lower) || new RegExp(`\\b${escapeRegExp3(symbol)}\\b`, "i").test(text);
4406
4612
  });
4407
4613
  const exactFile = (input.files ?? []).some((file) => row.source_path === file);
4408
4614
  const basenameMatch = (input.files ?? []).some((file) => baseStem(file) === baseStem(row.path));
@@ -4422,7 +4628,7 @@ function rowToRanked(row, input) {
4422
4628
  matchedSymbols
4423
4629
  };
4424
4630
  }
4425
- function escapeRegExp4(value) {
4631
+ function escapeRegExp3(value) {
4426
4632
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4427
4633
  }
4428
4634
  function rankRelevantTests(db, input) {
@@ -8484,6 +8690,13 @@ function dependenciesFor(manifest) {
8484
8690
  ...Object.keys(manifest.peerDependencies ?? {})
8485
8691
  ]);
8486
8692
  }
8693
+ function packageRootForSpecifier(specifier) {
8694
+ const normalized = specifier.trim();
8695
+ if (!normalized) return "";
8696
+ const parts = normalized.split("/");
8697
+ if (normalized.startsWith("@") && parts.length >= 2) return `${parts[0]}/${parts[1]}`;
8698
+ return parts[0] ?? "";
8699
+ }
8487
8700
  function parseJsonArray9(value) {
8488
8701
  try {
8489
8702
  const parsed = JSON.parse(value);
@@ -8518,7 +8731,7 @@ function isApiConsumerText(text) {
8518
8731
  function evidenceJson(evidence) {
8519
8732
  return JSON.stringify(evidence);
8520
8733
  }
8521
- function shouldEmitProgress2(current, total, interval = 100) {
8734
+ function shouldEmitProgress3(current, total, interval = 100) {
8522
8735
  return current === 1 || current === total || current % interval === 0;
8523
8736
  }
8524
8737
  function resolveOptions(baseDirOrOptions) {
@@ -8598,33 +8811,30 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
8598
8811
  FROM code_imports ci
8599
8812
  JOIN repositories r ON r.id = ci.repo_id`
8600
8813
  ).all();
8601
- const packageMatchers = [...packageNames.entries()].flatMap(([repo, names]) => names.map((name) => ({ repo, name }))).sort((a, b) => b.name.length - a.name.length);
8602
8814
  imports.forEach((item, index) => {
8603
8815
  const sourceRepo = repoByName.get(item.repo);
8604
8816
  if (!sourceRepo) return;
8605
- for (const candidate of packageMatchers) {
8606
- if (candidate.repo === item.repo) continue;
8607
- const matched = item.specifier === candidate.name || item.specifier.startsWith(`${candidate.name}/`);
8608
- if (!matched) continue;
8817
+ const rootSpecifier = packageRootForSpecifier(item.specifier);
8818
+ const targetRepo = packageToRepo.get(rootSpecifier) ?? packageToRepo.get(item.specifier);
8819
+ if (targetRepo && targetRepo !== item.repo) {
8609
8820
  addEdge({
8610
8821
  org: config.org,
8611
8822
  sourceRepo: item.repo,
8612
8823
  sourcePath: item.source_path,
8613
- targetRepo: candidate.repo,
8824
+ targetRepo,
8614
8825
  targetPath: item.imported_path ?? void 0,
8615
8826
  relationship: "imports",
8616
8827
  evidence: [
8617
8828
  fileEvidence(
8618
8829
  item.repo,
8619
8830
  item.source_path,
8620
- `imports ${sanitizeHistoricalText(candidate.name)}`
8831
+ `imports ${sanitizeHistoricalText(rootSpecifier || item.specifier)}`
8621
8832
  )
8622
8833
  ],
8623
8834
  confidence: parseJsonArray9(item.imported_symbols_json).length > 0 ? 0.88 : 0.76
8624
8835
  });
8625
- break;
8626
8836
  }
8627
- if (shouldEmitProgress2(index + 1, imports.length)) {
8837
+ if (shouldEmitProgress3(index + 1, imports.length)) {
8628
8838
  options.onProgress?.({
8629
8839
  stage: "building_import_edges",
8630
8840
  org: config.org,
@@ -8665,7 +8875,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
8665
8875
  bucket.push(apiContract);
8666
8876
  contractsByToken.set(sanitizedContract, bucket);
8667
8877
  }
8668
- if (shouldEmitProgress2(index + 1, providerChunks.length)) {
8878
+ if (shouldEmitProgress3(index + 1, providerChunks.length)) {
8669
8879
  options.onProgress?.({
8670
8880
  stage: "extracting_api_contracts",
8671
8881
  org: config.org,
@@ -8725,7 +8935,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
8725
8935
  });
8726
8936
  }
8727
8937
  }
8728
- if (shouldEmitProgress2(index + 1, consumerChunks.length)) {
8938
+ if (shouldEmitProgress3(index + 1, consumerChunks.length)) {
8729
8939
  options.onProgress?.({
8730
8940
  stage: "matching_api_consumers",
8731
8941
  org: config.org,
@@ -8757,7 +8967,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
8757
8967
  confidence = excluded.confidence,
8758
8968
  created_at = excluded.created_at`
8759
8969
  );
8760
- for (const edge of edges) {
8970
+ for (const [index, edge] of edges.entries()) {
8761
8971
  insertEdge.run(
8762
8972
  `oge_${stableId([edge.org, edge.sourceRepo, edge.sourcePath, edge.targetRepo, edge.targetPath ?? "", edge.relationship])}`,
8763
8973
  edge.org,
@@ -8770,6 +8980,19 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
8770
8980
  edge.confidence,
8771
8981
  now
8772
8982
  );
8983
+ const current = index + 1;
8984
+ if (shouldEmitProgress3(current, edges.length, 500)) {
8985
+ options.onProgress?.({
8986
+ stage: "writing_org_graph",
8987
+ org: config.org,
8988
+ edges: current,
8989
+ apiContracts: apiContracts.length,
8990
+ apiConsumers: apiConsumers.length,
8991
+ current,
8992
+ total: edges.length,
8993
+ kind: "edges"
8994
+ });
8995
+ }
8773
8996
  }
8774
8997
  const insertContract = db.prepare(
8775
8998
  `INSERT INTO org_api_contracts
@@ -8781,7 +9004,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
8781
9004
  confidence = excluded.confidence,
8782
9005
  created_at = excluded.created_at`
8783
9006
  );
8784
- for (const contract of apiContracts) {
9007
+ for (const [index, contract] of apiContracts.entries()) {
8785
9008
  insertContract.run(
8786
9009
  `oac_${stableId([config.org, contract.repo, contract.filePath, contract.contract])}`,
8787
9010
  config.org,
@@ -8792,6 +9015,19 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
8792
9015
  contract.confidence,
8793
9016
  now
8794
9017
  );
9018
+ const current = index + 1;
9019
+ if (shouldEmitProgress3(current, apiContracts.length, 500)) {
9020
+ options.onProgress?.({
9021
+ stage: "writing_org_graph",
9022
+ org: config.org,
9023
+ edges: edges.length,
9024
+ apiContracts: current,
9025
+ apiConsumers: apiConsumers.length,
9026
+ current,
9027
+ total: apiContracts.length,
9028
+ kind: "contracts"
9029
+ });
9030
+ }
8795
9031
  }
8796
9032
  const insertConsumer = db.prepare(
8797
9033
  `INSERT INTO org_api_consumers
@@ -8803,7 +9039,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
8803
9039
  confidence = excluded.confidence,
8804
9040
  created_at = excluded.created_at`
8805
9041
  );
8806
- for (const consumer of apiConsumers) {
9042
+ for (const [index, consumer] of apiConsumers.entries()) {
8807
9043
  insertConsumer.run(
8808
9044
  `oap_${stableId([
8809
9045
  consumer.org,
@@ -8823,6 +9059,19 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
8823
9059
  consumer.confidence,
8824
9060
  now
8825
9061
  );
9062
+ const current = index + 1;
9063
+ if (shouldEmitProgress3(current, apiConsumers.length, 500)) {
9064
+ options.onProgress?.({
9065
+ stage: "writing_org_graph",
9066
+ org: config.org,
9067
+ edges: edges.length,
9068
+ apiContracts: apiContracts.length,
9069
+ apiConsumers: current,
9070
+ current,
9071
+ total: apiConsumers.length,
9072
+ kind: "consumers"
9073
+ });
9074
+ }
8826
9075
  }
8827
9076
  });
8828
9077
  transaction();