@pratik7368patil/anchor-core 0.1.25 → 0.1.26
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 +16 -1
- package/dist/index.js +298 -57
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -574,6 +574,9 @@ type OrgGraphProgress = {
|
|
|
574
574
|
edges: number;
|
|
575
575
|
apiContracts: number;
|
|
576
576
|
apiConsumers: number;
|
|
577
|
+
current?: number;
|
|
578
|
+
total?: number;
|
|
579
|
+
kind?: "edges" | "contracts" | "consumers";
|
|
577
580
|
} | {
|
|
578
581
|
stage: "completed_org_graph";
|
|
579
582
|
org: string;
|
|
@@ -762,6 +765,15 @@ type CodeIndexProgress = {
|
|
|
762
765
|
stage: "writing_code_index";
|
|
763
766
|
repo: string;
|
|
764
767
|
phase: string;
|
|
768
|
+
} | {
|
|
769
|
+
stage: "inferring_test_awareness";
|
|
770
|
+
repo: string;
|
|
771
|
+
phase: "classifying_files" | "indexing_sources" | "linking_tests" | "completed";
|
|
772
|
+
current: number;
|
|
773
|
+
total: number;
|
|
774
|
+
testFiles: number;
|
|
775
|
+
testLinks: number;
|
|
776
|
+
filePath?: string;
|
|
765
777
|
} | {
|
|
766
778
|
stage: "deleting_existing_code_index";
|
|
767
779
|
repo: string;
|
|
@@ -1131,8 +1143,11 @@ declare function indexCodebase(db: AnchorDatabase, options: {
|
|
|
1131
1143
|
}): CodeIndexSummary;
|
|
1132
1144
|
declare function emptyCodeIndexSummary(cwd: string): CodeIndexSummary;
|
|
1133
1145
|
|
|
1146
|
+
type TestAwarenessOptions = {
|
|
1147
|
+
onProgress?: (progress: CodeIndexProgress) => void;
|
|
1148
|
+
};
|
|
1134
1149
|
declare function isTestFilePath(filePath: string): boolean;
|
|
1135
|
-
declare function inferTestAwareness(repo: string, codeFiles: CodeFileRecord[], codeChunks: CodeChunk[]): {
|
|
1150
|
+
declare function inferTestAwareness(repo: string, codeFiles: CodeFileRecord[], codeChunks: CodeChunk[], options?: TestAwarenessOptions): {
|
|
1136
1151
|
testFiles: TestFileRecord[];
|
|
1137
1152
|
testLinks: TestLink[];
|
|
1138
1153
|
};
|
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);
|
|
1347
1366
|
}
|
|
1348
|
-
function
|
|
1349
|
-
const
|
|
1350
|
-
const
|
|
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];
|
|
1375
|
+
}
|
|
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);
|
|
1503
|
+
}
|
|
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);
|
|
1380
1514
|
}
|
|
1381
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))
|
|
@@ -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);
|
|
@@ -2636,7 +2797,7 @@ import crypto2 from "crypto";
|
|
|
2636
2797
|
import path6 from "path";
|
|
2637
2798
|
var KNOWN_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json"];
|
|
2638
2799
|
var ARCHITECTURE_PROGRESS_INTERVAL = 250;
|
|
2639
|
-
function
|
|
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,
|
|
@@ -3887,7 +4085,7 @@ function symbolMatch2(unit, querySymbols) {
|
|
|
3887
4085
|
const lower = symbol.toLowerCase();
|
|
3888
4086
|
if (unitSymbols.includes(lower)) best = Math.max(best, 1);
|
|
3889
4087
|
else if (text.includes(`\`${lower}\``)) best = Math.max(best, 1);
|
|
3890
|
-
else if (new RegExp(`\\b${
|
|
4088
|
+
else if (new RegExp(`\\b${escapeRegExp(lower)}\\b`, "i").test(text))
|
|
3891
4089
|
best = Math.max(best, 0.66);
|
|
3892
4090
|
else if (unitSymbols.some((candidate) => candidate.includes(lower) || lower.includes(candidate))) {
|
|
3893
4091
|
best = Math.max(best, 0.35);
|
|
@@ -3968,7 +4166,7 @@ function scoreUnit(unit, input, duplicateCount, repeatedEvidenceCount, freshness
|
|
|
3968
4166
|
rankSignals: parts
|
|
3969
4167
|
};
|
|
3970
4168
|
}
|
|
3971
|
-
function
|
|
4169
|
+
function escapeRegExp(value) {
|
|
3972
4170
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3973
4171
|
}
|
|
3974
4172
|
function loadCandidates(db, input) {
|
|
@@ -4131,7 +4329,7 @@ function symbolMatch3(chunk, querySymbols) {
|
|
|
4131
4329
|
for (const symbol of querySymbols) {
|
|
4132
4330
|
const lower = symbol.toLowerCase();
|
|
4133
4331
|
if (chunkSymbols.includes(lower)) best = Math.max(best, 1);
|
|
4134
|
-
else if (new RegExp(`\\b${
|
|
4332
|
+
else if (new RegExp(`\\b${escapeRegExp2(lower)}\\b`, "i").test(text)) best = Math.max(best, 0.7);
|
|
4135
4333
|
else if (chunkSymbols.some((candidate) => candidate.includes(lower) || lower.includes(candidate))) {
|
|
4136
4334
|
best = Math.max(best, 0.42);
|
|
4137
4335
|
}
|
|
@@ -4167,7 +4365,7 @@ function matchReasons3(parts) {
|
|
|
4167
4365
|
if (parts.recency >= 0.75) reasons.push("recent code file");
|
|
4168
4366
|
return reasons.slice(0, 5);
|
|
4169
4367
|
}
|
|
4170
|
-
function
|
|
4368
|
+
function escapeRegExp2(value) {
|
|
4171
4369
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4172
4370
|
}
|
|
4173
4371
|
function escapeLike(value) {
|
|
@@ -4402,7 +4600,7 @@ function rowToRanked(row, input) {
|
|
|
4402
4600
|
const text = row.sanitized_text ?? "";
|
|
4403
4601
|
const matchedSymbols = (input.symbols ?? []).filter((symbol) => {
|
|
4404
4602
|
const lower = symbol.toLowerCase();
|
|
4405
|
-
return symbols.some((candidate) => candidate.toLowerCase() === lower) || new RegExp(`\\b${
|
|
4603
|
+
return symbols.some((candidate) => candidate.toLowerCase() === lower) || new RegExp(`\\b${escapeRegExp3(symbol)}\\b`, "i").test(text);
|
|
4406
4604
|
});
|
|
4407
4605
|
const exactFile = (input.files ?? []).some((file) => row.source_path === file);
|
|
4408
4606
|
const basenameMatch = (input.files ?? []).some((file) => baseStem(file) === baseStem(row.path));
|
|
@@ -4422,7 +4620,7 @@ function rowToRanked(row, input) {
|
|
|
4422
4620
|
matchedSymbols
|
|
4423
4621
|
};
|
|
4424
4622
|
}
|
|
4425
|
-
function
|
|
4623
|
+
function escapeRegExp3(value) {
|
|
4426
4624
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4427
4625
|
}
|
|
4428
4626
|
function rankRelevantTests(db, input) {
|
|
@@ -8484,6 +8682,13 @@ function dependenciesFor(manifest) {
|
|
|
8484
8682
|
...Object.keys(manifest.peerDependencies ?? {})
|
|
8485
8683
|
]);
|
|
8486
8684
|
}
|
|
8685
|
+
function packageRootForSpecifier(specifier) {
|
|
8686
|
+
const normalized = specifier.trim();
|
|
8687
|
+
if (!normalized) return "";
|
|
8688
|
+
const parts = normalized.split("/");
|
|
8689
|
+
if (normalized.startsWith("@") && parts.length >= 2) return `${parts[0]}/${parts[1]}`;
|
|
8690
|
+
return parts[0] ?? "";
|
|
8691
|
+
}
|
|
8487
8692
|
function parseJsonArray9(value) {
|
|
8488
8693
|
try {
|
|
8489
8694
|
const parsed = JSON.parse(value);
|
|
@@ -8518,7 +8723,7 @@ function isApiConsumerText(text) {
|
|
|
8518
8723
|
function evidenceJson(evidence) {
|
|
8519
8724
|
return JSON.stringify(evidence);
|
|
8520
8725
|
}
|
|
8521
|
-
function
|
|
8726
|
+
function shouldEmitProgress3(current, total, interval = 100) {
|
|
8522
8727
|
return current === 1 || current === total || current % interval === 0;
|
|
8523
8728
|
}
|
|
8524
8729
|
function resolveOptions(baseDirOrOptions) {
|
|
@@ -8598,33 +8803,30 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
|
|
|
8598
8803
|
FROM code_imports ci
|
|
8599
8804
|
JOIN repositories r ON r.id = ci.repo_id`
|
|
8600
8805
|
).all();
|
|
8601
|
-
const packageMatchers = [...packageNames.entries()].flatMap(([repo, names]) => names.map((name) => ({ repo, name }))).sort((a, b) => b.name.length - a.name.length);
|
|
8602
8806
|
imports.forEach((item, index) => {
|
|
8603
8807
|
const sourceRepo = repoByName.get(item.repo);
|
|
8604
8808
|
if (!sourceRepo) return;
|
|
8605
|
-
|
|
8606
|
-
|
|
8607
|
-
|
|
8608
|
-
if (!matched) continue;
|
|
8809
|
+
const rootSpecifier = packageRootForSpecifier(item.specifier);
|
|
8810
|
+
const targetRepo = packageToRepo.get(rootSpecifier) ?? packageToRepo.get(item.specifier);
|
|
8811
|
+
if (targetRepo && targetRepo !== item.repo) {
|
|
8609
8812
|
addEdge({
|
|
8610
8813
|
org: config.org,
|
|
8611
8814
|
sourceRepo: item.repo,
|
|
8612
8815
|
sourcePath: item.source_path,
|
|
8613
|
-
targetRepo
|
|
8816
|
+
targetRepo,
|
|
8614
8817
|
targetPath: item.imported_path ?? void 0,
|
|
8615
8818
|
relationship: "imports",
|
|
8616
8819
|
evidence: [
|
|
8617
8820
|
fileEvidence(
|
|
8618
8821
|
item.repo,
|
|
8619
8822
|
item.source_path,
|
|
8620
|
-
`imports ${sanitizeHistoricalText(
|
|
8823
|
+
`imports ${sanitizeHistoricalText(rootSpecifier || item.specifier)}`
|
|
8621
8824
|
)
|
|
8622
8825
|
],
|
|
8623
8826
|
confidence: parseJsonArray9(item.imported_symbols_json).length > 0 ? 0.88 : 0.76
|
|
8624
8827
|
});
|
|
8625
|
-
break;
|
|
8626
8828
|
}
|
|
8627
|
-
if (
|
|
8829
|
+
if (shouldEmitProgress3(index + 1, imports.length)) {
|
|
8628
8830
|
options.onProgress?.({
|
|
8629
8831
|
stage: "building_import_edges",
|
|
8630
8832
|
org: config.org,
|
|
@@ -8665,7 +8867,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
|
|
|
8665
8867
|
bucket.push(apiContract);
|
|
8666
8868
|
contractsByToken.set(sanitizedContract, bucket);
|
|
8667
8869
|
}
|
|
8668
|
-
if (
|
|
8870
|
+
if (shouldEmitProgress3(index + 1, providerChunks.length)) {
|
|
8669
8871
|
options.onProgress?.({
|
|
8670
8872
|
stage: "extracting_api_contracts",
|
|
8671
8873
|
org: config.org,
|
|
@@ -8725,7 +8927,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
|
|
|
8725
8927
|
});
|
|
8726
8928
|
}
|
|
8727
8929
|
}
|
|
8728
|
-
if (
|
|
8930
|
+
if (shouldEmitProgress3(index + 1, consumerChunks.length)) {
|
|
8729
8931
|
options.onProgress?.({
|
|
8730
8932
|
stage: "matching_api_consumers",
|
|
8731
8933
|
org: config.org,
|
|
@@ -8757,7 +8959,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
|
|
|
8757
8959
|
confidence = excluded.confidence,
|
|
8758
8960
|
created_at = excluded.created_at`
|
|
8759
8961
|
);
|
|
8760
|
-
for (const edge of edges) {
|
|
8962
|
+
for (const [index, edge] of edges.entries()) {
|
|
8761
8963
|
insertEdge.run(
|
|
8762
8964
|
`oge_${stableId([edge.org, edge.sourceRepo, edge.sourcePath, edge.targetRepo, edge.targetPath ?? "", edge.relationship])}`,
|
|
8763
8965
|
edge.org,
|
|
@@ -8770,6 +8972,19 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
|
|
|
8770
8972
|
edge.confidence,
|
|
8771
8973
|
now
|
|
8772
8974
|
);
|
|
8975
|
+
const current = index + 1;
|
|
8976
|
+
if (shouldEmitProgress3(current, edges.length, 500)) {
|
|
8977
|
+
options.onProgress?.({
|
|
8978
|
+
stage: "writing_org_graph",
|
|
8979
|
+
org: config.org,
|
|
8980
|
+
edges: current,
|
|
8981
|
+
apiContracts: apiContracts.length,
|
|
8982
|
+
apiConsumers: apiConsumers.length,
|
|
8983
|
+
current,
|
|
8984
|
+
total: edges.length,
|
|
8985
|
+
kind: "edges"
|
|
8986
|
+
});
|
|
8987
|
+
}
|
|
8773
8988
|
}
|
|
8774
8989
|
const insertContract = db.prepare(
|
|
8775
8990
|
`INSERT INTO org_api_contracts
|
|
@@ -8781,7 +8996,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
|
|
|
8781
8996
|
confidence = excluded.confidence,
|
|
8782
8997
|
created_at = excluded.created_at`
|
|
8783
8998
|
);
|
|
8784
|
-
for (const contract of apiContracts) {
|
|
8999
|
+
for (const [index, contract] of apiContracts.entries()) {
|
|
8785
9000
|
insertContract.run(
|
|
8786
9001
|
`oac_${stableId([config.org, contract.repo, contract.filePath, contract.contract])}`,
|
|
8787
9002
|
config.org,
|
|
@@ -8792,6 +9007,19 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
|
|
|
8792
9007
|
contract.confidence,
|
|
8793
9008
|
now
|
|
8794
9009
|
);
|
|
9010
|
+
const current = index + 1;
|
|
9011
|
+
if (shouldEmitProgress3(current, apiContracts.length, 500)) {
|
|
9012
|
+
options.onProgress?.({
|
|
9013
|
+
stage: "writing_org_graph",
|
|
9014
|
+
org: config.org,
|
|
9015
|
+
edges: edges.length,
|
|
9016
|
+
apiContracts: current,
|
|
9017
|
+
apiConsumers: apiConsumers.length,
|
|
9018
|
+
current,
|
|
9019
|
+
total: apiContracts.length,
|
|
9020
|
+
kind: "contracts"
|
|
9021
|
+
});
|
|
9022
|
+
}
|
|
8795
9023
|
}
|
|
8796
9024
|
const insertConsumer = db.prepare(
|
|
8797
9025
|
`INSERT INTO org_api_consumers
|
|
@@ -8803,7 +9031,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
|
|
|
8803
9031
|
confidence = excluded.confidence,
|
|
8804
9032
|
created_at = excluded.created_at`
|
|
8805
9033
|
);
|
|
8806
|
-
for (const consumer of apiConsumers) {
|
|
9034
|
+
for (const [index, consumer] of apiConsumers.entries()) {
|
|
8807
9035
|
insertConsumer.run(
|
|
8808
9036
|
`oap_${stableId([
|
|
8809
9037
|
consumer.org,
|
|
@@ -8823,6 +9051,19 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
|
|
|
8823
9051
|
consumer.confidence,
|
|
8824
9052
|
now
|
|
8825
9053
|
);
|
|
9054
|
+
const current = index + 1;
|
|
9055
|
+
if (shouldEmitProgress3(current, apiConsumers.length, 500)) {
|
|
9056
|
+
options.onProgress?.({
|
|
9057
|
+
stage: "writing_org_graph",
|
|
9058
|
+
org: config.org,
|
|
9059
|
+
edges: edges.length,
|
|
9060
|
+
apiContracts: apiContracts.length,
|
|
9061
|
+
apiConsumers: current,
|
|
9062
|
+
current,
|
|
9063
|
+
total: apiConsumers.length,
|
|
9064
|
+
kind: "consumers"
|
|
9065
|
+
});
|
|
9066
|
+
}
|
|
8826
9067
|
}
|
|
8827
9068
|
});
|
|
8828
9069
|
transaction();
|