@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.d.ts +19 -1
- package/dist/index.js +310 -61
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
1339
|
-
|
|
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
|
|
1346
|
-
|
|
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
|
|
1349
|
-
const
|
|
1350
|
-
const
|
|
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
|
-
|
|
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
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
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
|
-
|
|
1379
|
-
|
|
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 =
|
|
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 =
|
|
2639
|
-
function
|
|
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
|
|
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
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
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
|
|
2985
|
+
const relatedTestIndex = buildRelatedTestIndex(allPaths);
|
|
2986
|
+
const symbolSetsByPath = /* @__PURE__ */ new Map();
|
|
2793
2987
|
for (const chunk of chunks) {
|
|
2794
|
-
const existing =
|
|
2795
|
-
|
|
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 (
|
|
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 =
|
|
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,
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
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${
|
|
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
|
|
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${
|
|
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
|
|
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${
|
|
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
|
|
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
|
|
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
|
-
|
|
8606
|
-
|
|
8607
|
-
|
|
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
|
|
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(
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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();
|