@pratik7368patil/anchor-core 0.1.24 → 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 +124 -5
- package/dist/index.js +658 -93
- 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);
|
|
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))
|
|
@@ -1535,6 +1694,10 @@ function calculateCoverage(input) {
|
|
|
1535
1694
|
}
|
|
1536
1695
|
|
|
1537
1696
|
// src/db/database.ts
|
|
1697
|
+
var CODE_WRITE_PROGRESS_INTERVAL = 500;
|
|
1698
|
+
function shouldEmitCodeWriteProgress(current, total) {
|
|
1699
|
+
return current === 0 || current === 1 || current === total || current % CODE_WRITE_PROGRESS_INTERVAL === 0;
|
|
1700
|
+
}
|
|
1538
1701
|
function defaultDatabasePath(cwd) {
|
|
1539
1702
|
return path4.join(cwd, ".anchor", "index.sqlite");
|
|
1540
1703
|
}
|
|
@@ -1902,13 +2065,24 @@ function upsertPullRequest(db, pr, wisdomUnits, regressionEvents = []) {
|
|
|
1902
2065
|
regressions: regressionEvents.length
|
|
1903
2066
|
};
|
|
1904
2067
|
}
|
|
1905
|
-
function replaceCodeIndex(db, repo, codeFiles, codeChunks, skippedFiles, cwd, architecture = { components: [], patterns: [], imports: [] }) {
|
|
2068
|
+
function replaceCodeIndex(db, repo, codeFiles, codeChunks, skippedFiles, cwd, architecture = { components: [], patterns: [], imports: [] }, options = {}) {
|
|
1906
2069
|
initializeSchema(db);
|
|
1907
2070
|
const repoId = ensureRepository(db, repo);
|
|
1908
2071
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1909
|
-
|
|
2072
|
+
options.onProgress?.({ stage: "writing_code_index", repo, phase: "Inferring test awareness" });
|
|
2073
|
+
const testAwareness = inferTestAwareness(repo, codeFiles, codeChunks, {
|
|
2074
|
+
onProgress: options.onProgress
|
|
2075
|
+
});
|
|
2076
|
+
options.onProgress?.({ stage: "writing_code_index", repo, phase: "Writing code index" });
|
|
1910
2077
|
const transaction = db.transaction(() => {
|
|
1911
2078
|
const existingChunks = db.prepare("SELECT id FROM code_chunks WHERE repo_id = ?").all(repoId);
|
|
2079
|
+
const existingPatternCount = db.prepare("SELECT COUNT(*) AS count FROM architecture_patterns WHERE repo_id = ?").get(repoId).count;
|
|
2080
|
+
options.onProgress?.({
|
|
2081
|
+
stage: "deleting_existing_code_index",
|
|
2082
|
+
repo,
|
|
2083
|
+
chunks: existingChunks.length,
|
|
2084
|
+
patterns: existingPatternCount
|
|
2085
|
+
});
|
|
1912
2086
|
const deleteFts = db.prepare("DELETE FROM code_chunks_fts WHERE chunkId = ?");
|
|
1913
2087
|
for (const row of existingChunks) deleteFts.run(row.id);
|
|
1914
2088
|
db.prepare("DELETE FROM code_chunks WHERE repo_id = ?").run(repoId);
|
|
@@ -1921,7 +2095,13 @@ function replaceCodeIndex(db, repo, codeFiles, codeChunks, skippedFiles, cwd, ar
|
|
|
1921
2095
|
(repo_id, path, language, size_bytes, content_hash, updated_at)
|
|
1922
2096
|
VALUES (?, ?, ?, ?, ?, ?)`
|
|
1923
2097
|
);
|
|
1924
|
-
|
|
2098
|
+
options.onProgress?.({
|
|
2099
|
+
stage: "writing_code_files",
|
|
2100
|
+
repo,
|
|
2101
|
+
current: 0,
|
|
2102
|
+
total: codeFiles.length
|
|
2103
|
+
});
|
|
2104
|
+
for (const [index, file] of codeFiles.entries()) {
|
|
1925
2105
|
insertFile.run(
|
|
1926
2106
|
repoId,
|
|
1927
2107
|
file.path,
|
|
@@ -1930,6 +2110,16 @@ function replaceCodeIndex(db, repo, codeFiles, codeChunks, skippedFiles, cwd, ar
|
|
|
1930
2110
|
file.contentHash,
|
|
1931
2111
|
file.updatedAt
|
|
1932
2112
|
);
|
|
2113
|
+
const current = index + 1;
|
|
2114
|
+
if (shouldEmitCodeWriteProgress(current, codeFiles.length)) {
|
|
2115
|
+
options.onProgress?.({
|
|
2116
|
+
stage: "writing_code_files",
|
|
2117
|
+
repo,
|
|
2118
|
+
current,
|
|
2119
|
+
total: codeFiles.length,
|
|
2120
|
+
filePath: file.path
|
|
2121
|
+
});
|
|
2122
|
+
}
|
|
1933
2123
|
}
|
|
1934
2124
|
const fileRows = db.prepare("SELECT id, path FROM code_files WHERE repo_id = ?").all(repoId);
|
|
1935
2125
|
const fileIds = new Map(fileRows.map((row) => [row.path, row.id]));
|
|
@@ -1944,7 +2134,15 @@ function replaceCodeIndex(db, repo, codeFiles, codeChunks, skippedFiles, cwd, ar
|
|
|
1944
2134
|
(chunkId, sanitizedText, filePath, symbols, language)
|
|
1945
2135
|
VALUES (?, ?, ?, ?, ?)`
|
|
1946
2136
|
);
|
|
1947
|
-
|
|
2137
|
+
options.onProgress?.({
|
|
2138
|
+
stage: "writing_code_chunks",
|
|
2139
|
+
repo,
|
|
2140
|
+
current: 0,
|
|
2141
|
+
total: codeChunks.length,
|
|
2142
|
+
chunks: 0
|
|
2143
|
+
});
|
|
2144
|
+
let writtenChunks = 0;
|
|
2145
|
+
for (const [index, chunk] of codeChunks.entries()) {
|
|
1948
2146
|
const fileId = fileIds.get(chunk.filePath);
|
|
1949
2147
|
if (!fileId) continue;
|
|
1950
2148
|
insertChunk.run(
|
|
@@ -1968,10 +2166,23 @@ function replaceCodeIndex(db, repo, codeFiles, codeChunks, skippedFiles, cwd, ar
|
|
|
1968
2166
|
chunk.symbols.join(" "),
|
|
1969
2167
|
chunk.language ?? ""
|
|
1970
2168
|
);
|
|
2169
|
+
writtenChunks += 1;
|
|
2170
|
+
const current = index + 1;
|
|
2171
|
+
if (shouldEmitCodeWriteProgress(current, codeChunks.length)) {
|
|
2172
|
+
options.onProgress?.({
|
|
2173
|
+
stage: "writing_code_chunks",
|
|
2174
|
+
repo,
|
|
2175
|
+
current,
|
|
2176
|
+
total: codeChunks.length,
|
|
2177
|
+
filePath: chunk.filePath,
|
|
2178
|
+
chunks: writtenChunks
|
|
2179
|
+
});
|
|
2180
|
+
}
|
|
1971
2181
|
}
|
|
1972
|
-
insertTestAwareness(db, repoId, testAwareness.testFiles, testAwareness.testLinks);
|
|
1973
|
-
insertArchitectureData(db, repoId, architecture);
|
|
1974
|
-
insertArchitectureMapEdges(db, repoId, repo, architecture, testAwareness.testLinks);
|
|
2182
|
+
insertTestAwareness(db, repoId, repo, testAwareness.testFiles, testAwareness.testLinks, options);
|
|
2183
|
+
insertArchitectureData(db, repoId, repo, architecture, options);
|
|
2184
|
+
insertArchitectureMapEdges(db, repoId, repo, architecture, testAwareness.testLinks, options);
|
|
2185
|
+
options.onProgress?.({ stage: "writing_code_index", repo, phase: "Updating index state" });
|
|
1975
2186
|
db.prepare(
|
|
1976
2187
|
`INSERT INTO code_index_state (repo, last_indexed_at, indexed_files, code_chunks, skipped_files)
|
|
1977
2188
|
VALUES (?, ?, ?, ?, ?)
|
|
@@ -2019,13 +2230,20 @@ function deleteExistingArchitectureData(db, repoId) {
|
|
|
2019
2230
|
db.prepare("DELETE FROM code_imports WHERE repo_id = ?").run(repoId);
|
|
2020
2231
|
db.prepare("DELETE FROM architecture_map_edges WHERE repo_id = ?").run(repoId);
|
|
2021
2232
|
}
|
|
2022
|
-
function insertArchitectureData(db, repoId, architecture) {
|
|
2233
|
+
function insertArchitectureData(db, repoId, repo, architecture, options = {}) {
|
|
2023
2234
|
const insertImport = db.prepare(
|
|
2024
2235
|
`INSERT INTO code_imports
|
|
2025
2236
|
(repo_id, source_path, specifier, imported_path, imported_symbols_json, kind)
|
|
2026
2237
|
VALUES (?, ?, ?, ?, ?, ?)`
|
|
2027
2238
|
);
|
|
2028
|
-
|
|
2239
|
+
options.onProgress?.({
|
|
2240
|
+
stage: "writing_architecture_data",
|
|
2241
|
+
repo,
|
|
2242
|
+
current: 0,
|
|
2243
|
+
total: architecture.imports.length,
|
|
2244
|
+
kind: "imports"
|
|
2245
|
+
});
|
|
2246
|
+
for (const [index, item] of architecture.imports.entries()) {
|
|
2029
2247
|
insertImport.run(
|
|
2030
2248
|
repoId,
|
|
2031
2249
|
item.sourcePath,
|
|
@@ -2034,6 +2252,16 @@ function insertArchitectureData(db, repoId, architecture) {
|
|
|
2034
2252
|
JSON.stringify(item.importedSymbols),
|
|
2035
2253
|
item.kind
|
|
2036
2254
|
);
|
|
2255
|
+
const current = index + 1;
|
|
2256
|
+
if (shouldEmitCodeWriteProgress(current, architecture.imports.length)) {
|
|
2257
|
+
options.onProgress?.({
|
|
2258
|
+
stage: "writing_architecture_data",
|
|
2259
|
+
repo,
|
|
2260
|
+
current,
|
|
2261
|
+
total: architecture.imports.length,
|
|
2262
|
+
kind: "imports"
|
|
2263
|
+
});
|
|
2264
|
+
}
|
|
2037
2265
|
}
|
|
2038
2266
|
const insertComponent = db.prepare(
|
|
2039
2267
|
`INSERT INTO architecture_components
|
|
@@ -2041,7 +2269,14 @@ function insertArchitectureData(db, repoId, architecture) {
|
|
|
2041
2269
|
confidence, updated_at)
|
|
2042
2270
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
2043
2271
|
);
|
|
2044
|
-
|
|
2272
|
+
options.onProgress?.({
|
|
2273
|
+
stage: "writing_architecture_data",
|
|
2274
|
+
repo,
|
|
2275
|
+
current: 0,
|
|
2276
|
+
total: architecture.components.length,
|
|
2277
|
+
kind: "components"
|
|
2278
|
+
});
|
|
2279
|
+
for (const [index, component] of architecture.components.entries()) {
|
|
2045
2280
|
insertComponent.run(
|
|
2046
2281
|
repoId,
|
|
2047
2282
|
component.path,
|
|
@@ -2054,6 +2289,16 @@ function insertArchitectureData(db, repoId, architecture) {
|
|
|
2054
2289
|
component.confidence,
|
|
2055
2290
|
component.updatedAt
|
|
2056
2291
|
);
|
|
2292
|
+
const current = index + 1;
|
|
2293
|
+
if (shouldEmitCodeWriteProgress(current, architecture.components.length)) {
|
|
2294
|
+
options.onProgress?.({
|
|
2295
|
+
stage: "writing_architecture_data",
|
|
2296
|
+
repo,
|
|
2297
|
+
current,
|
|
2298
|
+
total: architecture.components.length,
|
|
2299
|
+
kind: "components"
|
|
2300
|
+
});
|
|
2301
|
+
}
|
|
2057
2302
|
}
|
|
2058
2303
|
const insertPattern = db.prepare(
|
|
2059
2304
|
`INSERT INTO architecture_patterns
|
|
@@ -2065,7 +2310,14 @@ function insertArchitectureData(db, repoId, architecture) {
|
|
|
2065
2310
|
`INSERT INTO architecture_patterns_fts (patternId, summary, area, sourceFiles, symbols)
|
|
2066
2311
|
VALUES (?, ?, ?, ?, ?)`
|
|
2067
2312
|
);
|
|
2068
|
-
|
|
2313
|
+
options.onProgress?.({
|
|
2314
|
+
stage: "writing_architecture_data",
|
|
2315
|
+
repo,
|
|
2316
|
+
current: 0,
|
|
2317
|
+
total: architecture.patterns.length,
|
|
2318
|
+
kind: "patterns"
|
|
2319
|
+
});
|
|
2320
|
+
for (const [index, pattern] of architecture.patterns.entries()) {
|
|
2069
2321
|
insertPattern.run(
|
|
2070
2322
|
pattern.id,
|
|
2071
2323
|
repoId,
|
|
@@ -2086,9 +2338,19 @@ function insertArchitectureData(db, repoId, architecture) {
|
|
|
2086
2338
|
pattern.sourceFiles.join(" "),
|
|
2087
2339
|
pattern.symbols.join(" ")
|
|
2088
2340
|
);
|
|
2341
|
+
const current = index + 1;
|
|
2342
|
+
if (shouldEmitCodeWriteProgress(current, architecture.patterns.length)) {
|
|
2343
|
+
options.onProgress?.({
|
|
2344
|
+
stage: "writing_architecture_data",
|
|
2345
|
+
repo,
|
|
2346
|
+
current,
|
|
2347
|
+
total: architecture.patterns.length,
|
|
2348
|
+
kind: "patterns"
|
|
2349
|
+
});
|
|
2350
|
+
}
|
|
2089
2351
|
}
|
|
2090
2352
|
}
|
|
2091
|
-
function insertArchitectureMapEdges(db, repoId, repo, architecture, testLinks) {
|
|
2353
|
+
function insertArchitectureMapEdges(db, repoId, repo, architecture, testLinks, options = {}) {
|
|
2092
2354
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2093
2355
|
const insert = db.prepare(
|
|
2094
2356
|
`INSERT INTO architecture_map_edges
|
|
@@ -2103,11 +2365,40 @@ function insertArchitectureMapEdges(db, repoId, repo, architecture, testLinks) {
|
|
|
2103
2365
|
seen.add(id);
|
|
2104
2366
|
insert.run(id, repoId, repo, sourcePath, targetPath, relationship, weight, now);
|
|
2105
2367
|
};
|
|
2368
|
+
const total = architecture.imports.length + testLinks.length;
|
|
2369
|
+
let current = 0;
|
|
2370
|
+
options.onProgress?.({
|
|
2371
|
+
stage: "writing_architecture_map_edges",
|
|
2372
|
+
repo,
|
|
2373
|
+
current,
|
|
2374
|
+
total,
|
|
2375
|
+
edges: 0
|
|
2376
|
+
});
|
|
2106
2377
|
for (const item of architecture.imports) {
|
|
2107
2378
|
if (item.importedPath) addEdge(item.sourcePath, item.importedPath, "imports", 0.9);
|
|
2379
|
+
current += 1;
|
|
2380
|
+
if (shouldEmitCodeWriteProgress(current, total)) {
|
|
2381
|
+
options.onProgress?.({
|
|
2382
|
+
stage: "writing_architecture_map_edges",
|
|
2383
|
+
repo,
|
|
2384
|
+
current,
|
|
2385
|
+
total,
|
|
2386
|
+
edges: seen.size
|
|
2387
|
+
});
|
|
2388
|
+
}
|
|
2108
2389
|
}
|
|
2109
2390
|
for (const link of testLinks) {
|
|
2110
2391
|
addEdge(link.sourcePath, link.testPath, "tested_by", link.strength);
|
|
2392
|
+
current += 1;
|
|
2393
|
+
if (shouldEmitCodeWriteProgress(current, total)) {
|
|
2394
|
+
options.onProgress?.({
|
|
2395
|
+
stage: "writing_architecture_map_edges",
|
|
2396
|
+
repo,
|
|
2397
|
+
current,
|
|
2398
|
+
total,
|
|
2399
|
+
edges: seen.size
|
|
2400
|
+
});
|
|
2401
|
+
}
|
|
2111
2402
|
}
|
|
2112
2403
|
}
|
|
2113
2404
|
function insertPrCochangeTestLinks(db, repoId, filePaths) {
|
|
@@ -2123,13 +2414,20 @@ function insertPrCochangeTestLinks(db, repoId, filePaths) {
|
|
|
2123
2414
|
for (const testPath of testPaths) insert.run(repoId, sourcePath, testPath);
|
|
2124
2415
|
}
|
|
2125
2416
|
}
|
|
2126
|
-
function insertTestAwareness(db, repoId, testFiles, testLinks) {
|
|
2417
|
+
function insertTestAwareness(db, repoId, repo, testFiles, testLinks, options = {}) {
|
|
2127
2418
|
const insertTestFile = db.prepare(
|
|
2128
2419
|
`INSERT INTO test_files
|
|
2129
2420
|
(repo_id, path, language, size_bytes, content_hash, updated_at)
|
|
2130
2421
|
VALUES (?, ?, ?, ?, ?, ?)`
|
|
2131
2422
|
);
|
|
2132
|
-
|
|
2423
|
+
options.onProgress?.({
|
|
2424
|
+
stage: "writing_test_awareness",
|
|
2425
|
+
repo,
|
|
2426
|
+
current: 0,
|
|
2427
|
+
total: testFiles.length,
|
|
2428
|
+
kind: "test_files"
|
|
2429
|
+
});
|
|
2430
|
+
for (const [index, file] of testFiles.entries()) {
|
|
2133
2431
|
insertTestFile.run(
|
|
2134
2432
|
repoId,
|
|
2135
2433
|
file.path,
|
|
@@ -2138,13 +2436,40 @@ function insertTestAwareness(db, repoId, testFiles, testLinks) {
|
|
|
2138
2436
|
file.contentHash,
|
|
2139
2437
|
file.updatedAt
|
|
2140
2438
|
);
|
|
2439
|
+
const current = index + 1;
|
|
2440
|
+
if (shouldEmitCodeWriteProgress(current, testFiles.length)) {
|
|
2441
|
+
options.onProgress?.({
|
|
2442
|
+
stage: "writing_test_awareness",
|
|
2443
|
+
repo,
|
|
2444
|
+
current,
|
|
2445
|
+
total: testFiles.length,
|
|
2446
|
+
kind: "test_files"
|
|
2447
|
+
});
|
|
2448
|
+
}
|
|
2141
2449
|
}
|
|
2142
2450
|
const insertTestLink = db.prepare(
|
|
2143
2451
|
`INSERT INTO test_links (repo_id, source_path, test_path, reason, strength)
|
|
2144
2452
|
VALUES (?, ?, ?, ?, ?)`
|
|
2145
2453
|
);
|
|
2146
|
-
|
|
2454
|
+
options.onProgress?.({
|
|
2455
|
+
stage: "writing_test_awareness",
|
|
2456
|
+
repo,
|
|
2457
|
+
current: 0,
|
|
2458
|
+
total: testLinks.length,
|
|
2459
|
+
kind: "test_links"
|
|
2460
|
+
});
|
|
2461
|
+
for (const [index, link] of testLinks.entries()) {
|
|
2147
2462
|
insertTestLink.run(repoId, link.sourcePath, link.testPath, link.reason, link.strength);
|
|
2463
|
+
const current = index + 1;
|
|
2464
|
+
if (shouldEmitCodeWriteProgress(current, testLinks.length)) {
|
|
2465
|
+
options.onProgress?.({
|
|
2466
|
+
stage: "writing_test_awareness",
|
|
2467
|
+
repo,
|
|
2468
|
+
current,
|
|
2469
|
+
total: testLinks.length,
|
|
2470
|
+
kind: "test_links"
|
|
2471
|
+
});
|
|
2472
|
+
}
|
|
2148
2473
|
}
|
|
2149
2474
|
}
|
|
2150
2475
|
function recordIndexRun(db, run) {
|
|
@@ -2471,6 +2796,10 @@ function chunkCodeFile(file, options = {}) {
|
|
|
2471
2796
|
import crypto2 from "crypto";
|
|
2472
2797
|
import path6 from "path";
|
|
2473
2798
|
var KNOWN_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json"];
|
|
2799
|
+
var ARCHITECTURE_PROGRESS_INTERVAL = 250;
|
|
2800
|
+
function shouldEmitProgress2(current, total) {
|
|
2801
|
+
return current === 0 || current === 1 || current === total || current % ARCHITECTURE_PROGRESS_INTERVAL === 0;
|
|
2802
|
+
}
|
|
2474
2803
|
function classifyArchitectureArea(filePath, language, content = "") {
|
|
2475
2804
|
const normalized = filePath.replace(/\\/g, "/").toLowerCase();
|
|
2476
2805
|
const basename = path6.basename(normalized);
|
|
@@ -2579,15 +2908,47 @@ function extractCodeImports(sourcePath, content, codePaths, repo = "") {
|
|
|
2579
2908
|
return true;
|
|
2580
2909
|
});
|
|
2581
2910
|
}
|
|
2582
|
-
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) {
|
|
2583
2933
|
if (isTestFilePath(filePath)) return [];
|
|
2584
2934
|
const parsed = path6.posix.parse(filePath);
|
|
2585
2935
|
const basename = parsed.name.replace(/\.(test|spec)$/i, "");
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
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);
|
|
2591
2952
|
}
|
|
2592
2953
|
function directoryLabel(filePath) {
|
|
2593
2954
|
const directory = path6.posix.dirname(filePath.replace(/\\/g, "/"));
|
|
@@ -2618,28 +2979,60 @@ function createPattern(input) {
|
|
|
2618
2979
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2619
2980
|
};
|
|
2620
2981
|
}
|
|
2621
|
-
function buildArchitectureIndex(repo, files, chunks) {
|
|
2982
|
+
function buildArchitectureIndex(repo, files, chunks, options = {}) {
|
|
2622
2983
|
const allPaths = files.map((file) => file.path);
|
|
2623
2984
|
const codePaths = new Set(allPaths);
|
|
2624
|
-
const
|
|
2985
|
+
const relatedTestIndex = buildRelatedTestIndex(allPaths);
|
|
2986
|
+
const symbolSetsByPath = /* @__PURE__ */ new Map();
|
|
2625
2987
|
for (const chunk of chunks) {
|
|
2626
|
-
const existing =
|
|
2627
|
-
|
|
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);
|
|
2994
|
+
}
|
|
2995
|
+
const imports = [];
|
|
2996
|
+
options.onProgress?.({
|
|
2997
|
+
stage: "building_architecture_imports",
|
|
2998
|
+
repo,
|
|
2999
|
+
current: 0,
|
|
3000
|
+
total: files.length,
|
|
3001
|
+
imports: 0
|
|
3002
|
+
});
|
|
3003
|
+
for (const [index, file] of files.entries()) {
|
|
3004
|
+
imports.push(...extractCodeImports(file.path, file.content, codePaths, repo));
|
|
3005
|
+
const current = index + 1;
|
|
3006
|
+
if (shouldEmitProgress2(current, files.length)) {
|
|
3007
|
+
options.onProgress?.({
|
|
3008
|
+
stage: "building_architecture_imports",
|
|
3009
|
+
repo,
|
|
3010
|
+
current,
|
|
3011
|
+
total: files.length,
|
|
3012
|
+
filePath: file.path,
|
|
3013
|
+
imports: imports.length
|
|
3014
|
+
});
|
|
3015
|
+
}
|
|
2628
3016
|
}
|
|
2629
|
-
const imports = files.flatMap(
|
|
2630
|
-
(file) => extractCodeImports(file.path, file.content, codePaths, repo)
|
|
2631
|
-
);
|
|
2632
3017
|
const importsByPath = /* @__PURE__ */ new Map();
|
|
2633
3018
|
for (const item of imports) {
|
|
2634
3019
|
const existing = importsByPath.get(item.sourcePath) ?? [];
|
|
2635
3020
|
existing.push(item);
|
|
2636
3021
|
importsByPath.set(item.sourcePath, existing);
|
|
2637
3022
|
}
|
|
2638
|
-
const components =
|
|
3023
|
+
const components = [];
|
|
3024
|
+
options.onProgress?.({
|
|
3025
|
+
stage: "building_architecture_components",
|
|
3026
|
+
repo,
|
|
3027
|
+
current: 0,
|
|
3028
|
+
total: files.length,
|
|
3029
|
+
components: 0
|
|
3030
|
+
});
|
|
3031
|
+
for (const [index, file] of files.entries()) {
|
|
2639
3032
|
const area = classifyArchitectureArea(file.path, file.language, file.content);
|
|
2640
3033
|
const fileImports = importsByPath.get(file.path) ?? [];
|
|
2641
|
-
const symbols =
|
|
2642
|
-
|
|
3034
|
+
const symbols = [...symbolSetsByPath.get(file.path) ?? []];
|
|
3035
|
+
components.push({
|
|
2643
3036
|
repo,
|
|
2644
3037
|
path: file.path,
|
|
2645
3038
|
area,
|
|
@@ -2649,19 +3042,52 @@ function buildArchitectureIndex(repo, files, chunks) {
|
|
|
2649
3042
|
imports: uniqueStrings(
|
|
2650
3043
|
fileImports.map((item) => item.importedPath ?? item.specifier).filter(Boolean)
|
|
2651
3044
|
).slice(0, 20),
|
|
2652
|
-
relatedTests: relatedTestsFor(file.path,
|
|
3045
|
+
relatedTests: relatedTestsFor(file.path, relatedTestIndex),
|
|
2653
3046
|
confidence: area === "unknown" ? 0.45 : 0.82,
|
|
2654
3047
|
updatedAt: file.updatedAt
|
|
2655
|
-
};
|
|
2656
|
-
|
|
3048
|
+
});
|
|
3049
|
+
const current = index + 1;
|
|
3050
|
+
if (shouldEmitProgress2(current, files.length)) {
|
|
3051
|
+
options.onProgress?.({
|
|
3052
|
+
stage: "building_architecture_components",
|
|
3053
|
+
repo,
|
|
3054
|
+
current,
|
|
3055
|
+
total: files.length,
|
|
3056
|
+
filePath: file.path,
|
|
3057
|
+
components: components.length
|
|
3058
|
+
});
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
2657
3061
|
const componentByPath = new Map(components.map((component) => [component.path, component]));
|
|
2658
|
-
const patterns = [];
|
|
2659
3062
|
const componentsByArea = /* @__PURE__ */ new Map();
|
|
2660
3063
|
for (const component of components) {
|
|
2661
3064
|
const existing = componentsByArea.get(component.area) ?? [];
|
|
2662
3065
|
existing.push(component);
|
|
2663
3066
|
componentsByArea.set(component.area, existing);
|
|
2664
3067
|
}
|
|
3068
|
+
const importDirectionCounts = /* @__PURE__ */ new Map();
|
|
3069
|
+
for (const item of imports) {
|
|
3070
|
+
if (!item.importedPath) continue;
|
|
3071
|
+
const source = componentByPath.get(item.sourcePath);
|
|
3072
|
+
const target = componentByPath.get(item.importedPath);
|
|
3073
|
+
if (!source || !target || source.area === target.area) continue;
|
|
3074
|
+
const key = `${source.area}->${target.area}`;
|
|
3075
|
+
const existing = importDirectionCounts.get(key) ?? { count: 0, files: [], symbols: [] };
|
|
3076
|
+
existing.count += 1;
|
|
3077
|
+
existing.files.push(source.path, target.path);
|
|
3078
|
+
existing.symbols.push(...item.importedSymbols);
|
|
3079
|
+
importDirectionCounts.set(key, existing);
|
|
3080
|
+
}
|
|
3081
|
+
const patterns = [];
|
|
3082
|
+
const patternTotal = componentsByArea.size + importDirectionCounts.size;
|
|
3083
|
+
let patternProgress = 0;
|
|
3084
|
+
options.onProgress?.({
|
|
3085
|
+
stage: "building_architecture_patterns",
|
|
3086
|
+
repo,
|
|
3087
|
+
current: 0,
|
|
3088
|
+
total: patternTotal,
|
|
3089
|
+
patterns: 0
|
|
3090
|
+
});
|
|
2665
3091
|
for (const [area, areaComponents] of componentsByArea.entries()) {
|
|
2666
3092
|
const filesForArea = areaComponents.map((component) => component.path);
|
|
2667
3093
|
const directories = topDirectories(filesForArea);
|
|
@@ -2677,19 +3103,17 @@ function buildArchitectureIndex(repo, files, chunks) {
|
|
|
2677
3103
|
confidence: 0.55 + Math.min(0.3, filesForArea.length * 0.04)
|
|
2678
3104
|
})
|
|
2679
3105
|
);
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
existing.symbols.push(...item.importedSymbols);
|
|
2692
|
-
importDirectionCounts.set(key, existing);
|
|
3106
|
+
patternProgress += 1;
|
|
3107
|
+
if (shouldEmitProgress2(patternProgress, patternTotal)) {
|
|
3108
|
+
options.onProgress?.({
|
|
3109
|
+
stage: "building_architecture_patterns",
|
|
3110
|
+
repo,
|
|
3111
|
+
current: patternProgress,
|
|
3112
|
+
total: patternTotal,
|
|
3113
|
+
area,
|
|
3114
|
+
patterns: patterns.length
|
|
3115
|
+
});
|
|
3116
|
+
}
|
|
2693
3117
|
}
|
|
2694
3118
|
for (const [key, value] of importDirectionCounts.entries()) {
|
|
2695
3119
|
const [sourceArea, targetArea] = key.split("->");
|
|
@@ -2704,6 +3128,17 @@ function buildArchitectureIndex(repo, files, chunks) {
|
|
|
2704
3128
|
confidence: 0.62 + Math.min(0.25, value.count * 0.05)
|
|
2705
3129
|
})
|
|
2706
3130
|
);
|
|
3131
|
+
patternProgress += 1;
|
|
3132
|
+
if (shouldEmitProgress2(patternProgress, patternTotal)) {
|
|
3133
|
+
options.onProgress?.({
|
|
3134
|
+
stage: "building_architecture_patterns",
|
|
3135
|
+
repo,
|
|
3136
|
+
current: patternProgress,
|
|
3137
|
+
total: patternTotal,
|
|
3138
|
+
area: sourceArea,
|
|
3139
|
+
patterns: patterns.length
|
|
3140
|
+
});
|
|
3141
|
+
}
|
|
2707
3142
|
}
|
|
2708
3143
|
const testedComponents = components.filter((component) => component.relatedTests.length > 0);
|
|
2709
3144
|
if (testedComponents.length > 0) {
|
|
@@ -3033,8 +3468,24 @@ function detectTestCommands(db, cwd, files = []) {
|
|
|
3033
3468
|
return true;
|
|
3034
3469
|
});
|
|
3035
3470
|
}
|
|
3036
|
-
function refreshTestCommands(db, cwd, repo, files = []) {
|
|
3471
|
+
function refreshTestCommands(db, cwd, repo, files = [], options = {}) {
|
|
3472
|
+
options.onProgress?.({
|
|
3473
|
+
stage: "refreshing_test_commands",
|
|
3474
|
+
repo,
|
|
3475
|
+
phase: "detecting",
|
|
3476
|
+
current: 0,
|
|
3477
|
+
total: files.length,
|
|
3478
|
+
commands: 0
|
|
3479
|
+
});
|
|
3037
3480
|
const commands = detectTestCommands(db, cwd, files);
|
|
3481
|
+
options.onProgress?.({
|
|
3482
|
+
stage: "refreshing_test_commands",
|
|
3483
|
+
repo,
|
|
3484
|
+
phase: "writing",
|
|
3485
|
+
current: 0,
|
|
3486
|
+
total: commands.length,
|
|
3487
|
+
commands: commands.length
|
|
3488
|
+
});
|
|
3038
3489
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3039
3490
|
const transaction = db.transaction(() => {
|
|
3040
3491
|
db.prepare("DELETE FROM test_commands WHERE repo = ?").run(repo);
|
|
@@ -3042,7 +3493,7 @@ function refreshTestCommands(db, cwd, repo, files = []) {
|
|
|
3042
3493
|
`INSERT INTO test_commands (id, repo, file_path, command, reason, confidence, created_at)
|
|
3043
3494
|
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
3044
3495
|
);
|
|
3045
|
-
for (const command of commands) {
|
|
3496
|
+
for (const [index, command] of commands.entries()) {
|
|
3046
3497
|
insert.run(
|
|
3047
3498
|
commandId(repo, command),
|
|
3048
3499
|
repo,
|
|
@@ -3052,6 +3503,17 @@ function refreshTestCommands(db, cwd, repo, files = []) {
|
|
|
3052
3503
|
command.confidence,
|
|
3053
3504
|
now
|
|
3054
3505
|
);
|
|
3506
|
+
const current = index + 1;
|
|
3507
|
+
if (current === 1 || current === commands.length || current % 250 === 0) {
|
|
3508
|
+
options.onProgress?.({
|
|
3509
|
+
stage: "refreshing_test_commands",
|
|
3510
|
+
repo,
|
|
3511
|
+
phase: "writing",
|
|
3512
|
+
current,
|
|
3513
|
+
total: commands.length,
|
|
3514
|
+
commands: commands.length
|
|
3515
|
+
});
|
|
3516
|
+
}
|
|
3055
3517
|
}
|
|
3056
3518
|
});
|
|
3057
3519
|
transaction();
|
|
@@ -3090,7 +3552,9 @@ function indexCodebase(db, options) {
|
|
|
3090
3552
|
chunks: fileChunks.length
|
|
3091
3553
|
});
|
|
3092
3554
|
}
|
|
3093
|
-
const architecture = buildArchitectureIndex(options.repo, discovery.files, chunks
|
|
3555
|
+
const architecture = buildArchitectureIndex(options.repo, discovery.files, chunks, {
|
|
3556
|
+
onProgress: options.onProgress
|
|
3557
|
+
});
|
|
3094
3558
|
options.onProgress?.({
|
|
3095
3559
|
stage: "indexed_architecture",
|
|
3096
3560
|
repo: options.repo,
|
|
@@ -3105,9 +3569,10 @@ function indexCodebase(db, options) {
|
|
|
3105
3569
|
chunks,
|
|
3106
3570
|
discovery.skippedFiles,
|
|
3107
3571
|
options.cwd,
|
|
3108
|
-
architecture
|
|
3572
|
+
architecture,
|
|
3573
|
+
{ onProgress: options.onProgress }
|
|
3109
3574
|
);
|
|
3110
|
-
refreshTestCommands(db, options.cwd, options.repo);
|
|
3575
|
+
refreshTestCommands(db, options.cwd, options.repo, [], { onProgress: options.onProgress });
|
|
3111
3576
|
options.onProgress?.({
|
|
3112
3577
|
stage: "completed_code_index",
|
|
3113
3578
|
repo: options.repo,
|
|
@@ -3620,7 +4085,7 @@ function symbolMatch2(unit, querySymbols) {
|
|
|
3620
4085
|
const lower = symbol.toLowerCase();
|
|
3621
4086
|
if (unitSymbols.includes(lower)) best = Math.max(best, 1);
|
|
3622
4087
|
else if (text.includes(`\`${lower}\``)) best = Math.max(best, 1);
|
|
3623
|
-
else if (new RegExp(`\\b${
|
|
4088
|
+
else if (new RegExp(`\\b${escapeRegExp(lower)}\\b`, "i").test(text))
|
|
3624
4089
|
best = Math.max(best, 0.66);
|
|
3625
4090
|
else if (unitSymbols.some((candidate) => candidate.includes(lower) || lower.includes(candidate))) {
|
|
3626
4091
|
best = Math.max(best, 0.35);
|
|
@@ -3701,7 +4166,7 @@ function scoreUnit(unit, input, duplicateCount, repeatedEvidenceCount, freshness
|
|
|
3701
4166
|
rankSignals: parts
|
|
3702
4167
|
};
|
|
3703
4168
|
}
|
|
3704
|
-
function
|
|
4169
|
+
function escapeRegExp(value) {
|
|
3705
4170
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3706
4171
|
}
|
|
3707
4172
|
function loadCandidates(db, input) {
|
|
@@ -3864,7 +4329,7 @@ function symbolMatch3(chunk, querySymbols) {
|
|
|
3864
4329
|
for (const symbol of querySymbols) {
|
|
3865
4330
|
const lower = symbol.toLowerCase();
|
|
3866
4331
|
if (chunkSymbols.includes(lower)) best = Math.max(best, 1);
|
|
3867
|
-
else if (new RegExp(`\\b${
|
|
4332
|
+
else if (new RegExp(`\\b${escapeRegExp2(lower)}\\b`, "i").test(text)) best = Math.max(best, 0.7);
|
|
3868
4333
|
else if (chunkSymbols.some((candidate) => candidate.includes(lower) || lower.includes(candidate))) {
|
|
3869
4334
|
best = Math.max(best, 0.42);
|
|
3870
4335
|
}
|
|
@@ -3900,7 +4365,7 @@ function matchReasons3(parts) {
|
|
|
3900
4365
|
if (parts.recency >= 0.75) reasons.push("recent code file");
|
|
3901
4366
|
return reasons.slice(0, 5);
|
|
3902
4367
|
}
|
|
3903
|
-
function
|
|
4368
|
+
function escapeRegExp2(value) {
|
|
3904
4369
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3905
4370
|
}
|
|
3906
4371
|
function escapeLike(value) {
|
|
@@ -4135,7 +4600,7 @@ function rowToRanked(row, input) {
|
|
|
4135
4600
|
const text = row.sanitized_text ?? "";
|
|
4136
4601
|
const matchedSymbols = (input.symbols ?? []).filter((symbol) => {
|
|
4137
4602
|
const lower = symbol.toLowerCase();
|
|
4138
|
-
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);
|
|
4139
4604
|
});
|
|
4140
4605
|
const exactFile = (input.files ?? []).some((file) => row.source_path === file);
|
|
4141
4606
|
const basenameMatch = (input.files ?? []).some((file) => baseStem(file) === baseStem(row.path));
|
|
@@ -4155,7 +4620,7 @@ function rowToRanked(row, input) {
|
|
|
4155
4620
|
matchedSymbols
|
|
4156
4621
|
};
|
|
4157
4622
|
}
|
|
4158
|
-
function
|
|
4623
|
+
function escapeRegExp3(value) {
|
|
4159
4624
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4160
4625
|
}
|
|
4161
4626
|
function rankRelevantTests(db, input) {
|
|
@@ -7929,10 +8394,67 @@ function parseHeartbeat(value) {
|
|
|
7929
8394
|
repoIndex: typeof candidate.repoIndex === "number" ? candidate.repoIndex : void 0,
|
|
7930
8395
|
repoTotal: typeof candidate.repoTotal === "number" ? candidate.repoTotal : void 0,
|
|
7931
8396
|
phase: candidate.phase,
|
|
8397
|
+
timeline: parseTimeline(candidate.timeline),
|
|
7932
8398
|
startedAt: candidate.startedAt,
|
|
7933
8399
|
updatedAt: candidate.updatedAt
|
|
7934
8400
|
};
|
|
7935
8401
|
}
|
|
8402
|
+
function parseTimelineStatus(value) {
|
|
8403
|
+
if (value === "active" || value === "done" || value === "skipped" || value === "warn" || value === "fail" || value === "wait") {
|
|
8404
|
+
return value;
|
|
8405
|
+
}
|
|
8406
|
+
return void 0;
|
|
8407
|
+
}
|
|
8408
|
+
function parseTimelineNumber(value) {
|
|
8409
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
8410
|
+
}
|
|
8411
|
+
function parseTimelineStep(value) {
|
|
8412
|
+
if (!value || typeof value !== "object") return void 0;
|
|
8413
|
+
const candidate = value;
|
|
8414
|
+
const status = parseTimelineStatus(candidate.status);
|
|
8415
|
+
if (typeof candidate.id !== "string" || typeof candidate.label !== "string" || !status || typeof candidate.startedAt !== "string" || typeof candidate.updatedAt !== "string") {
|
|
8416
|
+
return void 0;
|
|
8417
|
+
}
|
|
8418
|
+
return {
|
|
8419
|
+
id: candidate.id,
|
|
8420
|
+
label: candidate.label,
|
|
8421
|
+
status,
|
|
8422
|
+
startedAt: candidate.startedAt,
|
|
8423
|
+
updatedAt: candidate.updatedAt,
|
|
8424
|
+
completedAt: typeof candidate.completedAt === "string" ? candidate.completedAt : void 0,
|
|
8425
|
+
durationMs: parseTimelineNumber(candidate.durationMs),
|
|
8426
|
+
current: parseTimelineNumber(candidate.current),
|
|
8427
|
+
total: parseTimelineNumber(candidate.total),
|
|
8428
|
+
detail: typeof candidate.detail === "string" ? candidate.detail : void 0
|
|
8429
|
+
};
|
|
8430
|
+
}
|
|
8431
|
+
function parseTimelineRepoSummary(value) {
|
|
8432
|
+
if (!value || typeof value !== "object") return void 0;
|
|
8433
|
+
const candidate = value;
|
|
8434
|
+
const status = parseTimelineStatus(candidate.status);
|
|
8435
|
+
if (typeof candidate.repo !== "string" || !status) return void 0;
|
|
8436
|
+
return {
|
|
8437
|
+
repo: candidate.repo,
|
|
8438
|
+
status,
|
|
8439
|
+
durationMs: parseTimelineNumber(candidate.durationMs) ?? 0,
|
|
8440
|
+
detail: typeof candidate.detail === "string" ? candidate.detail : void 0
|
|
8441
|
+
};
|
|
8442
|
+
}
|
|
8443
|
+
function parseTimeline(value) {
|
|
8444
|
+
if (!value || typeof value !== "object") return void 0;
|
|
8445
|
+
const candidate = value;
|
|
8446
|
+
if (!Array.isArray(candidate.steps) || !Array.isArray(candidate.recentRepos)) return void 0;
|
|
8447
|
+
const steps = candidate.steps.map(parseTimelineStep).filter((step) => step !== void 0);
|
|
8448
|
+
const recentRepos = candidate.recentRepos.map(parseTimelineRepoSummary).filter((repo) => repo !== void 0);
|
|
8449
|
+
return {
|
|
8450
|
+
repo: typeof candidate.repo === "string" ? candidate.repo : void 0,
|
|
8451
|
+
repoIndex: parseTimelineNumber(candidate.repoIndex),
|
|
8452
|
+
repoTotal: parseTimelineNumber(candidate.repoTotal),
|
|
8453
|
+
activeStepId: typeof candidate.activeStepId === "string" ? candidate.activeStepId : void 0,
|
|
8454
|
+
steps,
|
|
8455
|
+
recentRepos
|
|
8456
|
+
};
|
|
8457
|
+
}
|
|
7936
8458
|
function writeOrgHeartbeat(heartbeat, baseDir) {
|
|
7937
8459
|
atomicWriteJson2(orgHeartbeatPath(heartbeat.org, baseDir), heartbeat);
|
|
7938
8460
|
}
|
|
@@ -8160,6 +8682,13 @@ function dependenciesFor(manifest) {
|
|
|
8160
8682
|
...Object.keys(manifest.peerDependencies ?? {})
|
|
8161
8683
|
]);
|
|
8162
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
|
+
}
|
|
8163
8692
|
function parseJsonArray9(value) {
|
|
8164
8693
|
try {
|
|
8165
8694
|
const parsed = JSON.parse(value);
|
|
@@ -8194,7 +8723,7 @@ function isApiConsumerText(text) {
|
|
|
8194
8723
|
function evidenceJson(evidence) {
|
|
8195
8724
|
return JSON.stringify(evidence);
|
|
8196
8725
|
}
|
|
8197
|
-
function
|
|
8726
|
+
function shouldEmitProgress3(current, total, interval = 100) {
|
|
8198
8727
|
return current === 1 || current === total || current % interval === 0;
|
|
8199
8728
|
}
|
|
8200
8729
|
function resolveOptions(baseDirOrOptions) {
|
|
@@ -8274,33 +8803,30 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
|
|
|
8274
8803
|
FROM code_imports ci
|
|
8275
8804
|
JOIN repositories r ON r.id = ci.repo_id`
|
|
8276
8805
|
).all();
|
|
8277
|
-
const packageMatchers = [...packageNames.entries()].flatMap(([repo, names]) => names.map((name) => ({ repo, name }))).sort((a, b) => b.name.length - a.name.length);
|
|
8278
8806
|
imports.forEach((item, index) => {
|
|
8279
8807
|
const sourceRepo = repoByName.get(item.repo);
|
|
8280
8808
|
if (!sourceRepo) return;
|
|
8281
|
-
|
|
8282
|
-
|
|
8283
|
-
|
|
8284
|
-
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) {
|
|
8285
8812
|
addEdge({
|
|
8286
8813
|
org: config.org,
|
|
8287
8814
|
sourceRepo: item.repo,
|
|
8288
8815
|
sourcePath: item.source_path,
|
|
8289
|
-
targetRepo
|
|
8816
|
+
targetRepo,
|
|
8290
8817
|
targetPath: item.imported_path ?? void 0,
|
|
8291
8818
|
relationship: "imports",
|
|
8292
8819
|
evidence: [
|
|
8293
8820
|
fileEvidence(
|
|
8294
8821
|
item.repo,
|
|
8295
8822
|
item.source_path,
|
|
8296
|
-
`imports ${sanitizeHistoricalText(
|
|
8823
|
+
`imports ${sanitizeHistoricalText(rootSpecifier || item.specifier)}`
|
|
8297
8824
|
)
|
|
8298
8825
|
],
|
|
8299
8826
|
confidence: parseJsonArray9(item.imported_symbols_json).length > 0 ? 0.88 : 0.76
|
|
8300
8827
|
});
|
|
8301
|
-
break;
|
|
8302
8828
|
}
|
|
8303
|
-
if (
|
|
8829
|
+
if (shouldEmitProgress3(index + 1, imports.length)) {
|
|
8304
8830
|
options.onProgress?.({
|
|
8305
8831
|
stage: "building_import_edges",
|
|
8306
8832
|
org: config.org,
|
|
@@ -8341,7 +8867,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
|
|
|
8341
8867
|
bucket.push(apiContract);
|
|
8342
8868
|
contractsByToken.set(sanitizedContract, bucket);
|
|
8343
8869
|
}
|
|
8344
|
-
if (
|
|
8870
|
+
if (shouldEmitProgress3(index + 1, providerChunks.length)) {
|
|
8345
8871
|
options.onProgress?.({
|
|
8346
8872
|
stage: "extracting_api_contracts",
|
|
8347
8873
|
org: config.org,
|
|
@@ -8401,7 +8927,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
|
|
|
8401
8927
|
});
|
|
8402
8928
|
}
|
|
8403
8929
|
}
|
|
8404
|
-
if (
|
|
8930
|
+
if (shouldEmitProgress3(index + 1, consumerChunks.length)) {
|
|
8405
8931
|
options.onProgress?.({
|
|
8406
8932
|
stage: "matching_api_consumers",
|
|
8407
8933
|
org: config.org,
|
|
@@ -8433,7 +8959,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
|
|
|
8433
8959
|
confidence = excluded.confidence,
|
|
8434
8960
|
created_at = excluded.created_at`
|
|
8435
8961
|
);
|
|
8436
|
-
for (const edge of edges) {
|
|
8962
|
+
for (const [index, edge] of edges.entries()) {
|
|
8437
8963
|
insertEdge.run(
|
|
8438
8964
|
`oge_${stableId([edge.org, edge.sourceRepo, edge.sourcePath, edge.targetRepo, edge.targetPath ?? "", edge.relationship])}`,
|
|
8439
8965
|
edge.org,
|
|
@@ -8446,6 +8972,19 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
|
|
|
8446
8972
|
edge.confidence,
|
|
8447
8973
|
now
|
|
8448
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
|
+
}
|
|
8449
8988
|
}
|
|
8450
8989
|
const insertContract = db.prepare(
|
|
8451
8990
|
`INSERT INTO org_api_contracts
|
|
@@ -8457,7 +8996,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
|
|
|
8457
8996
|
confidence = excluded.confidence,
|
|
8458
8997
|
created_at = excluded.created_at`
|
|
8459
8998
|
);
|
|
8460
|
-
for (const contract of apiContracts) {
|
|
8999
|
+
for (const [index, contract] of apiContracts.entries()) {
|
|
8461
9000
|
insertContract.run(
|
|
8462
9001
|
`oac_${stableId([config.org, contract.repo, contract.filePath, contract.contract])}`,
|
|
8463
9002
|
config.org,
|
|
@@ -8468,6 +9007,19 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
|
|
|
8468
9007
|
contract.confidence,
|
|
8469
9008
|
now
|
|
8470
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
|
+
}
|
|
8471
9023
|
}
|
|
8472
9024
|
const insertConsumer = db.prepare(
|
|
8473
9025
|
`INSERT INTO org_api_consumers
|
|
@@ -8479,7 +9031,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
|
|
|
8479
9031
|
confidence = excluded.confidence,
|
|
8480
9032
|
created_at = excluded.created_at`
|
|
8481
9033
|
);
|
|
8482
|
-
for (const consumer of apiConsumers) {
|
|
9034
|
+
for (const [index, consumer] of apiConsumers.entries()) {
|
|
8483
9035
|
insertConsumer.run(
|
|
8484
9036
|
`oap_${stableId([
|
|
8485
9037
|
consumer.org,
|
|
@@ -8499,6 +9051,19 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
|
|
|
8499
9051
|
consumer.confidence,
|
|
8500
9052
|
now
|
|
8501
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
|
+
}
|
|
8502
9067
|
}
|
|
8503
9068
|
});
|
|
8504
9069
|
transaction();
|