@pratik7368patil/anchor-core 0.1.23 → 0.1.25
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 +243 -5
- package/dist/index.js +751 -118
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1535,16 +1535,27 @@ function calculateCoverage(input) {
|
|
|
1535
1535
|
}
|
|
1536
1536
|
|
|
1537
1537
|
// src/db/database.ts
|
|
1538
|
+
var CODE_WRITE_PROGRESS_INTERVAL = 500;
|
|
1539
|
+
function shouldEmitCodeWriteProgress(current, total) {
|
|
1540
|
+
return current === 0 || current === 1 || current === total || current % CODE_WRITE_PROGRESS_INTERVAL === 0;
|
|
1541
|
+
}
|
|
1538
1542
|
function defaultDatabasePath(cwd) {
|
|
1539
1543
|
return path4.join(cwd, ".anchor", "index.sqlite");
|
|
1540
1544
|
}
|
|
1541
1545
|
function openAnchorDatabase(cwd, databasePath = defaultDatabasePath(cwd)) {
|
|
1542
1546
|
fs3.mkdirSync(path4.dirname(databasePath), { recursive: true });
|
|
1543
1547
|
const db = new Database(databasePath);
|
|
1548
|
+
db.pragma("busy_timeout = 5000");
|
|
1544
1549
|
db.pragma("journal_mode = WAL");
|
|
1545
1550
|
db.pragma("foreign_keys = ON");
|
|
1546
1551
|
return db;
|
|
1547
1552
|
}
|
|
1553
|
+
function openAnchorDatabaseReadOnly(databasePath) {
|
|
1554
|
+
const db = new Database(databasePath, { readonly: true, fileMustExist: true });
|
|
1555
|
+
db.pragma("busy_timeout = 5000");
|
|
1556
|
+
db.pragma("foreign_keys = ON");
|
|
1557
|
+
return db;
|
|
1558
|
+
}
|
|
1548
1559
|
function initializeSchema(db) {
|
|
1549
1560
|
db.exec(SCHEMA_SQL);
|
|
1550
1561
|
ensureColumn(db, "sync_state", "history_coverage", "TEXT");
|
|
@@ -1895,13 +1906,22 @@ function upsertPullRequest(db, pr, wisdomUnits, regressionEvents = []) {
|
|
|
1895
1906
|
regressions: regressionEvents.length
|
|
1896
1907
|
};
|
|
1897
1908
|
}
|
|
1898
|
-
function replaceCodeIndex(db, repo, codeFiles, codeChunks, skippedFiles, cwd, architecture = { components: [], patterns: [], imports: [] }) {
|
|
1909
|
+
function replaceCodeIndex(db, repo, codeFiles, codeChunks, skippedFiles, cwd, architecture = { components: [], patterns: [], imports: [] }, options = {}) {
|
|
1899
1910
|
initializeSchema(db);
|
|
1900
1911
|
const repoId = ensureRepository(db, repo);
|
|
1901
1912
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1913
|
+
options.onProgress?.({ stage: "writing_code_index", repo, phase: "Inferring test awareness" });
|
|
1902
1914
|
const testAwareness = inferTestAwareness(repo, codeFiles, codeChunks);
|
|
1915
|
+
options.onProgress?.({ stage: "writing_code_index", repo, phase: "Writing code index" });
|
|
1903
1916
|
const transaction = db.transaction(() => {
|
|
1904
1917
|
const existingChunks = db.prepare("SELECT id FROM code_chunks WHERE repo_id = ?").all(repoId);
|
|
1918
|
+
const existingPatternCount = db.prepare("SELECT COUNT(*) AS count FROM architecture_patterns WHERE repo_id = ?").get(repoId).count;
|
|
1919
|
+
options.onProgress?.({
|
|
1920
|
+
stage: "deleting_existing_code_index",
|
|
1921
|
+
repo,
|
|
1922
|
+
chunks: existingChunks.length,
|
|
1923
|
+
patterns: existingPatternCount
|
|
1924
|
+
});
|
|
1905
1925
|
const deleteFts = db.prepare("DELETE FROM code_chunks_fts WHERE chunkId = ?");
|
|
1906
1926
|
for (const row of existingChunks) deleteFts.run(row.id);
|
|
1907
1927
|
db.prepare("DELETE FROM code_chunks WHERE repo_id = ?").run(repoId);
|
|
@@ -1914,7 +1934,13 @@ function replaceCodeIndex(db, repo, codeFiles, codeChunks, skippedFiles, cwd, ar
|
|
|
1914
1934
|
(repo_id, path, language, size_bytes, content_hash, updated_at)
|
|
1915
1935
|
VALUES (?, ?, ?, ?, ?, ?)`
|
|
1916
1936
|
);
|
|
1917
|
-
|
|
1937
|
+
options.onProgress?.({
|
|
1938
|
+
stage: "writing_code_files",
|
|
1939
|
+
repo,
|
|
1940
|
+
current: 0,
|
|
1941
|
+
total: codeFiles.length
|
|
1942
|
+
});
|
|
1943
|
+
for (const [index, file] of codeFiles.entries()) {
|
|
1918
1944
|
insertFile.run(
|
|
1919
1945
|
repoId,
|
|
1920
1946
|
file.path,
|
|
@@ -1923,6 +1949,16 @@ function replaceCodeIndex(db, repo, codeFiles, codeChunks, skippedFiles, cwd, ar
|
|
|
1923
1949
|
file.contentHash,
|
|
1924
1950
|
file.updatedAt
|
|
1925
1951
|
);
|
|
1952
|
+
const current = index + 1;
|
|
1953
|
+
if (shouldEmitCodeWriteProgress(current, codeFiles.length)) {
|
|
1954
|
+
options.onProgress?.({
|
|
1955
|
+
stage: "writing_code_files",
|
|
1956
|
+
repo,
|
|
1957
|
+
current,
|
|
1958
|
+
total: codeFiles.length,
|
|
1959
|
+
filePath: file.path
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1926
1962
|
}
|
|
1927
1963
|
const fileRows = db.prepare("SELECT id, path FROM code_files WHERE repo_id = ?").all(repoId);
|
|
1928
1964
|
const fileIds = new Map(fileRows.map((row) => [row.path, row.id]));
|
|
@@ -1937,7 +1973,15 @@ function replaceCodeIndex(db, repo, codeFiles, codeChunks, skippedFiles, cwd, ar
|
|
|
1937
1973
|
(chunkId, sanitizedText, filePath, symbols, language)
|
|
1938
1974
|
VALUES (?, ?, ?, ?, ?)`
|
|
1939
1975
|
);
|
|
1940
|
-
|
|
1976
|
+
options.onProgress?.({
|
|
1977
|
+
stage: "writing_code_chunks",
|
|
1978
|
+
repo,
|
|
1979
|
+
current: 0,
|
|
1980
|
+
total: codeChunks.length,
|
|
1981
|
+
chunks: 0
|
|
1982
|
+
});
|
|
1983
|
+
let writtenChunks = 0;
|
|
1984
|
+
for (const [index, chunk] of codeChunks.entries()) {
|
|
1941
1985
|
const fileId = fileIds.get(chunk.filePath);
|
|
1942
1986
|
if (!fileId) continue;
|
|
1943
1987
|
insertChunk.run(
|
|
@@ -1961,10 +2005,23 @@ function replaceCodeIndex(db, repo, codeFiles, codeChunks, skippedFiles, cwd, ar
|
|
|
1961
2005
|
chunk.symbols.join(" "),
|
|
1962
2006
|
chunk.language ?? ""
|
|
1963
2007
|
);
|
|
2008
|
+
writtenChunks += 1;
|
|
2009
|
+
const current = index + 1;
|
|
2010
|
+
if (shouldEmitCodeWriteProgress(current, codeChunks.length)) {
|
|
2011
|
+
options.onProgress?.({
|
|
2012
|
+
stage: "writing_code_chunks",
|
|
2013
|
+
repo,
|
|
2014
|
+
current,
|
|
2015
|
+
total: codeChunks.length,
|
|
2016
|
+
filePath: chunk.filePath,
|
|
2017
|
+
chunks: writtenChunks
|
|
2018
|
+
});
|
|
2019
|
+
}
|
|
1964
2020
|
}
|
|
1965
|
-
insertTestAwareness(db, repoId, testAwareness.testFiles, testAwareness.testLinks);
|
|
1966
|
-
insertArchitectureData(db, repoId, architecture);
|
|
1967
|
-
insertArchitectureMapEdges(db, repoId, repo, architecture, testAwareness.testLinks);
|
|
2021
|
+
insertTestAwareness(db, repoId, repo, testAwareness.testFiles, testAwareness.testLinks, options);
|
|
2022
|
+
insertArchitectureData(db, repoId, repo, architecture, options);
|
|
2023
|
+
insertArchitectureMapEdges(db, repoId, repo, architecture, testAwareness.testLinks, options);
|
|
2024
|
+
options.onProgress?.({ stage: "writing_code_index", repo, phase: "Updating index state" });
|
|
1968
2025
|
db.prepare(
|
|
1969
2026
|
`INSERT INTO code_index_state (repo, last_indexed_at, indexed_files, code_chunks, skipped_files)
|
|
1970
2027
|
VALUES (?, ?, ?, ?, ?)
|
|
@@ -2012,13 +2069,20 @@ function deleteExistingArchitectureData(db, repoId) {
|
|
|
2012
2069
|
db.prepare("DELETE FROM code_imports WHERE repo_id = ?").run(repoId);
|
|
2013
2070
|
db.prepare("DELETE FROM architecture_map_edges WHERE repo_id = ?").run(repoId);
|
|
2014
2071
|
}
|
|
2015
|
-
function insertArchitectureData(db, repoId, architecture) {
|
|
2072
|
+
function insertArchitectureData(db, repoId, repo, architecture, options = {}) {
|
|
2016
2073
|
const insertImport = db.prepare(
|
|
2017
2074
|
`INSERT INTO code_imports
|
|
2018
2075
|
(repo_id, source_path, specifier, imported_path, imported_symbols_json, kind)
|
|
2019
2076
|
VALUES (?, ?, ?, ?, ?, ?)`
|
|
2020
2077
|
);
|
|
2021
|
-
|
|
2078
|
+
options.onProgress?.({
|
|
2079
|
+
stage: "writing_architecture_data",
|
|
2080
|
+
repo,
|
|
2081
|
+
current: 0,
|
|
2082
|
+
total: architecture.imports.length,
|
|
2083
|
+
kind: "imports"
|
|
2084
|
+
});
|
|
2085
|
+
for (const [index, item] of architecture.imports.entries()) {
|
|
2022
2086
|
insertImport.run(
|
|
2023
2087
|
repoId,
|
|
2024
2088
|
item.sourcePath,
|
|
@@ -2027,6 +2091,16 @@ function insertArchitectureData(db, repoId, architecture) {
|
|
|
2027
2091
|
JSON.stringify(item.importedSymbols),
|
|
2028
2092
|
item.kind
|
|
2029
2093
|
);
|
|
2094
|
+
const current = index + 1;
|
|
2095
|
+
if (shouldEmitCodeWriteProgress(current, architecture.imports.length)) {
|
|
2096
|
+
options.onProgress?.({
|
|
2097
|
+
stage: "writing_architecture_data",
|
|
2098
|
+
repo,
|
|
2099
|
+
current,
|
|
2100
|
+
total: architecture.imports.length,
|
|
2101
|
+
kind: "imports"
|
|
2102
|
+
});
|
|
2103
|
+
}
|
|
2030
2104
|
}
|
|
2031
2105
|
const insertComponent = db.prepare(
|
|
2032
2106
|
`INSERT INTO architecture_components
|
|
@@ -2034,7 +2108,14 @@ function insertArchitectureData(db, repoId, architecture) {
|
|
|
2034
2108
|
confidence, updated_at)
|
|
2035
2109
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
2036
2110
|
);
|
|
2037
|
-
|
|
2111
|
+
options.onProgress?.({
|
|
2112
|
+
stage: "writing_architecture_data",
|
|
2113
|
+
repo,
|
|
2114
|
+
current: 0,
|
|
2115
|
+
total: architecture.components.length,
|
|
2116
|
+
kind: "components"
|
|
2117
|
+
});
|
|
2118
|
+
for (const [index, component] of architecture.components.entries()) {
|
|
2038
2119
|
insertComponent.run(
|
|
2039
2120
|
repoId,
|
|
2040
2121
|
component.path,
|
|
@@ -2047,6 +2128,16 @@ function insertArchitectureData(db, repoId, architecture) {
|
|
|
2047
2128
|
component.confidence,
|
|
2048
2129
|
component.updatedAt
|
|
2049
2130
|
);
|
|
2131
|
+
const current = index + 1;
|
|
2132
|
+
if (shouldEmitCodeWriteProgress(current, architecture.components.length)) {
|
|
2133
|
+
options.onProgress?.({
|
|
2134
|
+
stage: "writing_architecture_data",
|
|
2135
|
+
repo,
|
|
2136
|
+
current,
|
|
2137
|
+
total: architecture.components.length,
|
|
2138
|
+
kind: "components"
|
|
2139
|
+
});
|
|
2140
|
+
}
|
|
2050
2141
|
}
|
|
2051
2142
|
const insertPattern = db.prepare(
|
|
2052
2143
|
`INSERT INTO architecture_patterns
|
|
@@ -2058,7 +2149,14 @@ function insertArchitectureData(db, repoId, architecture) {
|
|
|
2058
2149
|
`INSERT INTO architecture_patterns_fts (patternId, summary, area, sourceFiles, symbols)
|
|
2059
2150
|
VALUES (?, ?, ?, ?, ?)`
|
|
2060
2151
|
);
|
|
2061
|
-
|
|
2152
|
+
options.onProgress?.({
|
|
2153
|
+
stage: "writing_architecture_data",
|
|
2154
|
+
repo,
|
|
2155
|
+
current: 0,
|
|
2156
|
+
total: architecture.patterns.length,
|
|
2157
|
+
kind: "patterns"
|
|
2158
|
+
});
|
|
2159
|
+
for (const [index, pattern] of architecture.patterns.entries()) {
|
|
2062
2160
|
insertPattern.run(
|
|
2063
2161
|
pattern.id,
|
|
2064
2162
|
repoId,
|
|
@@ -2079,9 +2177,19 @@ function insertArchitectureData(db, repoId, architecture) {
|
|
|
2079
2177
|
pattern.sourceFiles.join(" "),
|
|
2080
2178
|
pattern.symbols.join(" ")
|
|
2081
2179
|
);
|
|
2180
|
+
const current = index + 1;
|
|
2181
|
+
if (shouldEmitCodeWriteProgress(current, architecture.patterns.length)) {
|
|
2182
|
+
options.onProgress?.({
|
|
2183
|
+
stage: "writing_architecture_data",
|
|
2184
|
+
repo,
|
|
2185
|
+
current,
|
|
2186
|
+
total: architecture.patterns.length,
|
|
2187
|
+
kind: "patterns"
|
|
2188
|
+
});
|
|
2189
|
+
}
|
|
2082
2190
|
}
|
|
2083
2191
|
}
|
|
2084
|
-
function insertArchitectureMapEdges(db, repoId, repo, architecture, testLinks) {
|
|
2192
|
+
function insertArchitectureMapEdges(db, repoId, repo, architecture, testLinks, options = {}) {
|
|
2085
2193
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2086
2194
|
const insert = db.prepare(
|
|
2087
2195
|
`INSERT INTO architecture_map_edges
|
|
@@ -2096,11 +2204,40 @@ function insertArchitectureMapEdges(db, repoId, repo, architecture, testLinks) {
|
|
|
2096
2204
|
seen.add(id);
|
|
2097
2205
|
insert.run(id, repoId, repo, sourcePath, targetPath, relationship, weight, now);
|
|
2098
2206
|
};
|
|
2207
|
+
const total = architecture.imports.length + testLinks.length;
|
|
2208
|
+
let current = 0;
|
|
2209
|
+
options.onProgress?.({
|
|
2210
|
+
stage: "writing_architecture_map_edges",
|
|
2211
|
+
repo,
|
|
2212
|
+
current,
|
|
2213
|
+
total,
|
|
2214
|
+
edges: 0
|
|
2215
|
+
});
|
|
2099
2216
|
for (const item of architecture.imports) {
|
|
2100
2217
|
if (item.importedPath) addEdge(item.sourcePath, item.importedPath, "imports", 0.9);
|
|
2218
|
+
current += 1;
|
|
2219
|
+
if (shouldEmitCodeWriteProgress(current, total)) {
|
|
2220
|
+
options.onProgress?.({
|
|
2221
|
+
stage: "writing_architecture_map_edges",
|
|
2222
|
+
repo,
|
|
2223
|
+
current,
|
|
2224
|
+
total,
|
|
2225
|
+
edges: seen.size
|
|
2226
|
+
});
|
|
2227
|
+
}
|
|
2101
2228
|
}
|
|
2102
2229
|
for (const link of testLinks) {
|
|
2103
2230
|
addEdge(link.sourcePath, link.testPath, "tested_by", link.strength);
|
|
2231
|
+
current += 1;
|
|
2232
|
+
if (shouldEmitCodeWriteProgress(current, total)) {
|
|
2233
|
+
options.onProgress?.({
|
|
2234
|
+
stage: "writing_architecture_map_edges",
|
|
2235
|
+
repo,
|
|
2236
|
+
current,
|
|
2237
|
+
total,
|
|
2238
|
+
edges: seen.size
|
|
2239
|
+
});
|
|
2240
|
+
}
|
|
2104
2241
|
}
|
|
2105
2242
|
}
|
|
2106
2243
|
function insertPrCochangeTestLinks(db, repoId, filePaths) {
|
|
@@ -2116,13 +2253,20 @@ function insertPrCochangeTestLinks(db, repoId, filePaths) {
|
|
|
2116
2253
|
for (const testPath of testPaths) insert.run(repoId, sourcePath, testPath);
|
|
2117
2254
|
}
|
|
2118
2255
|
}
|
|
2119
|
-
function insertTestAwareness(db, repoId, testFiles, testLinks) {
|
|
2256
|
+
function insertTestAwareness(db, repoId, repo, testFiles, testLinks, options = {}) {
|
|
2120
2257
|
const insertTestFile = db.prepare(
|
|
2121
2258
|
`INSERT INTO test_files
|
|
2122
2259
|
(repo_id, path, language, size_bytes, content_hash, updated_at)
|
|
2123
2260
|
VALUES (?, ?, ?, ?, ?, ?)`
|
|
2124
2261
|
);
|
|
2125
|
-
|
|
2262
|
+
options.onProgress?.({
|
|
2263
|
+
stage: "writing_test_awareness",
|
|
2264
|
+
repo,
|
|
2265
|
+
current: 0,
|
|
2266
|
+
total: testFiles.length,
|
|
2267
|
+
kind: "test_files"
|
|
2268
|
+
});
|
|
2269
|
+
for (const [index, file] of testFiles.entries()) {
|
|
2126
2270
|
insertTestFile.run(
|
|
2127
2271
|
repoId,
|
|
2128
2272
|
file.path,
|
|
@@ -2131,13 +2275,40 @@ function insertTestAwareness(db, repoId, testFiles, testLinks) {
|
|
|
2131
2275
|
file.contentHash,
|
|
2132
2276
|
file.updatedAt
|
|
2133
2277
|
);
|
|
2278
|
+
const current = index + 1;
|
|
2279
|
+
if (shouldEmitCodeWriteProgress(current, testFiles.length)) {
|
|
2280
|
+
options.onProgress?.({
|
|
2281
|
+
stage: "writing_test_awareness",
|
|
2282
|
+
repo,
|
|
2283
|
+
current,
|
|
2284
|
+
total: testFiles.length,
|
|
2285
|
+
kind: "test_files"
|
|
2286
|
+
});
|
|
2287
|
+
}
|
|
2134
2288
|
}
|
|
2135
2289
|
const insertTestLink = db.prepare(
|
|
2136
2290
|
`INSERT INTO test_links (repo_id, source_path, test_path, reason, strength)
|
|
2137
2291
|
VALUES (?, ?, ?, ?, ?)`
|
|
2138
2292
|
);
|
|
2139
|
-
|
|
2293
|
+
options.onProgress?.({
|
|
2294
|
+
stage: "writing_test_awareness",
|
|
2295
|
+
repo,
|
|
2296
|
+
current: 0,
|
|
2297
|
+
total: testLinks.length,
|
|
2298
|
+
kind: "test_links"
|
|
2299
|
+
});
|
|
2300
|
+
for (const [index, link] of testLinks.entries()) {
|
|
2140
2301
|
insertTestLink.run(repoId, link.sourcePath, link.testPath, link.reason, link.strength);
|
|
2302
|
+
const current = index + 1;
|
|
2303
|
+
if (shouldEmitCodeWriteProgress(current, testLinks.length)) {
|
|
2304
|
+
options.onProgress?.({
|
|
2305
|
+
stage: "writing_test_awareness",
|
|
2306
|
+
repo,
|
|
2307
|
+
current,
|
|
2308
|
+
total: testLinks.length,
|
|
2309
|
+
kind: "test_links"
|
|
2310
|
+
});
|
|
2311
|
+
}
|
|
2141
2312
|
}
|
|
2142
2313
|
}
|
|
2143
2314
|
function recordIndexRun(db, run) {
|
|
@@ -2464,6 +2635,10 @@ function chunkCodeFile(file, options = {}) {
|
|
|
2464
2635
|
import crypto2 from "crypto";
|
|
2465
2636
|
import path6 from "path";
|
|
2466
2637
|
var KNOWN_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json"];
|
|
2638
|
+
var ARCHITECTURE_PROGRESS_INTERVAL = 250;
|
|
2639
|
+
function shouldEmitProgress(current, total) {
|
|
2640
|
+
return current === 0 || current === 1 || current === total || current % ARCHITECTURE_PROGRESS_INTERVAL === 0;
|
|
2641
|
+
}
|
|
2467
2642
|
function classifyArchitectureArea(filePath, language, content = "") {
|
|
2468
2643
|
const normalized = filePath.replace(/\\/g, "/").toLowerCase();
|
|
2469
2644
|
const basename = path6.basename(normalized);
|
|
@@ -2611,7 +2786,7 @@ function createPattern(input) {
|
|
|
2611
2786
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2612
2787
|
};
|
|
2613
2788
|
}
|
|
2614
|
-
function buildArchitectureIndex(repo, files, chunks) {
|
|
2789
|
+
function buildArchitectureIndex(repo, files, chunks, options = {}) {
|
|
2615
2790
|
const allPaths = files.map((file) => file.path);
|
|
2616
2791
|
const codePaths = new Set(allPaths);
|
|
2617
2792
|
const symbolsByPath = /* @__PURE__ */ new Map();
|
|
@@ -2619,20 +2794,47 @@ function buildArchitectureIndex(repo, files, chunks) {
|
|
|
2619
2794
|
const existing = symbolsByPath.get(chunk.filePath) ?? [];
|
|
2620
2795
|
symbolsByPath.set(chunk.filePath, uniqueStrings([...existing, ...chunk.symbols]).slice(0, 40));
|
|
2621
2796
|
}
|
|
2622
|
-
const imports =
|
|
2623
|
-
|
|
2624
|
-
|
|
2797
|
+
const imports = [];
|
|
2798
|
+
options.onProgress?.({
|
|
2799
|
+
stage: "building_architecture_imports",
|
|
2800
|
+
repo,
|
|
2801
|
+
current: 0,
|
|
2802
|
+
total: files.length,
|
|
2803
|
+
imports: 0
|
|
2804
|
+
});
|
|
2805
|
+
for (const [index, file] of files.entries()) {
|
|
2806
|
+
imports.push(...extractCodeImports(file.path, file.content, codePaths, repo));
|
|
2807
|
+
const current = index + 1;
|
|
2808
|
+
if (shouldEmitProgress(current, files.length)) {
|
|
2809
|
+
options.onProgress?.({
|
|
2810
|
+
stage: "building_architecture_imports",
|
|
2811
|
+
repo,
|
|
2812
|
+
current,
|
|
2813
|
+
total: files.length,
|
|
2814
|
+
filePath: file.path,
|
|
2815
|
+
imports: imports.length
|
|
2816
|
+
});
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2625
2819
|
const importsByPath = /* @__PURE__ */ new Map();
|
|
2626
2820
|
for (const item of imports) {
|
|
2627
2821
|
const existing = importsByPath.get(item.sourcePath) ?? [];
|
|
2628
2822
|
existing.push(item);
|
|
2629
2823
|
importsByPath.set(item.sourcePath, existing);
|
|
2630
2824
|
}
|
|
2631
|
-
const components =
|
|
2825
|
+
const components = [];
|
|
2826
|
+
options.onProgress?.({
|
|
2827
|
+
stage: "building_architecture_components",
|
|
2828
|
+
repo,
|
|
2829
|
+
current: 0,
|
|
2830
|
+
total: files.length,
|
|
2831
|
+
components: 0
|
|
2832
|
+
});
|
|
2833
|
+
for (const [index, file] of files.entries()) {
|
|
2632
2834
|
const area = classifyArchitectureArea(file.path, file.language, file.content);
|
|
2633
2835
|
const fileImports = importsByPath.get(file.path) ?? [];
|
|
2634
2836
|
const symbols = symbolsByPath.get(file.path) ?? [];
|
|
2635
|
-
|
|
2837
|
+
components.push({
|
|
2636
2838
|
repo,
|
|
2637
2839
|
path: file.path,
|
|
2638
2840
|
area,
|
|
@@ -2645,16 +2847,49 @@ function buildArchitectureIndex(repo, files, chunks) {
|
|
|
2645
2847
|
relatedTests: relatedTestsFor(file.path, allPaths),
|
|
2646
2848
|
confidence: area === "unknown" ? 0.45 : 0.82,
|
|
2647
2849
|
updatedAt: file.updatedAt
|
|
2648
|
-
};
|
|
2649
|
-
|
|
2850
|
+
});
|
|
2851
|
+
const current = index + 1;
|
|
2852
|
+
if (shouldEmitProgress(current, files.length)) {
|
|
2853
|
+
options.onProgress?.({
|
|
2854
|
+
stage: "building_architecture_components",
|
|
2855
|
+
repo,
|
|
2856
|
+
current,
|
|
2857
|
+
total: files.length,
|
|
2858
|
+
filePath: file.path,
|
|
2859
|
+
components: components.length
|
|
2860
|
+
});
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2650
2863
|
const componentByPath = new Map(components.map((component) => [component.path, component]));
|
|
2651
|
-
const patterns = [];
|
|
2652
2864
|
const componentsByArea = /* @__PURE__ */ new Map();
|
|
2653
2865
|
for (const component of components) {
|
|
2654
2866
|
const existing = componentsByArea.get(component.area) ?? [];
|
|
2655
2867
|
existing.push(component);
|
|
2656
2868
|
componentsByArea.set(component.area, existing);
|
|
2657
2869
|
}
|
|
2870
|
+
const importDirectionCounts = /* @__PURE__ */ new Map();
|
|
2871
|
+
for (const item of imports) {
|
|
2872
|
+
if (!item.importedPath) continue;
|
|
2873
|
+
const source = componentByPath.get(item.sourcePath);
|
|
2874
|
+
const target = componentByPath.get(item.importedPath);
|
|
2875
|
+
if (!source || !target || source.area === target.area) continue;
|
|
2876
|
+
const key = `${source.area}->${target.area}`;
|
|
2877
|
+
const existing = importDirectionCounts.get(key) ?? { count: 0, files: [], symbols: [] };
|
|
2878
|
+
existing.count += 1;
|
|
2879
|
+
existing.files.push(source.path, target.path);
|
|
2880
|
+
existing.symbols.push(...item.importedSymbols);
|
|
2881
|
+
importDirectionCounts.set(key, existing);
|
|
2882
|
+
}
|
|
2883
|
+
const patterns = [];
|
|
2884
|
+
const patternTotal = componentsByArea.size + importDirectionCounts.size;
|
|
2885
|
+
let patternProgress = 0;
|
|
2886
|
+
options.onProgress?.({
|
|
2887
|
+
stage: "building_architecture_patterns",
|
|
2888
|
+
repo,
|
|
2889
|
+
current: 0,
|
|
2890
|
+
total: patternTotal,
|
|
2891
|
+
patterns: 0
|
|
2892
|
+
});
|
|
2658
2893
|
for (const [area, areaComponents] of componentsByArea.entries()) {
|
|
2659
2894
|
const filesForArea = areaComponents.map((component) => component.path);
|
|
2660
2895
|
const directories = topDirectories(filesForArea);
|
|
@@ -2670,19 +2905,17 @@ function buildArchitectureIndex(repo, files, chunks) {
|
|
|
2670
2905
|
confidence: 0.55 + Math.min(0.3, filesForArea.length * 0.04)
|
|
2671
2906
|
})
|
|
2672
2907
|
);
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
existing.symbols.push(...item.importedSymbols);
|
|
2685
|
-
importDirectionCounts.set(key, existing);
|
|
2908
|
+
patternProgress += 1;
|
|
2909
|
+
if (shouldEmitProgress(patternProgress, patternTotal)) {
|
|
2910
|
+
options.onProgress?.({
|
|
2911
|
+
stage: "building_architecture_patterns",
|
|
2912
|
+
repo,
|
|
2913
|
+
current: patternProgress,
|
|
2914
|
+
total: patternTotal,
|
|
2915
|
+
area,
|
|
2916
|
+
patterns: patterns.length
|
|
2917
|
+
});
|
|
2918
|
+
}
|
|
2686
2919
|
}
|
|
2687
2920
|
for (const [key, value] of importDirectionCounts.entries()) {
|
|
2688
2921
|
const [sourceArea, targetArea] = key.split("->");
|
|
@@ -2697,6 +2930,17 @@ function buildArchitectureIndex(repo, files, chunks) {
|
|
|
2697
2930
|
confidence: 0.62 + Math.min(0.25, value.count * 0.05)
|
|
2698
2931
|
})
|
|
2699
2932
|
);
|
|
2933
|
+
patternProgress += 1;
|
|
2934
|
+
if (shouldEmitProgress(patternProgress, patternTotal)) {
|
|
2935
|
+
options.onProgress?.({
|
|
2936
|
+
stage: "building_architecture_patterns",
|
|
2937
|
+
repo,
|
|
2938
|
+
current: patternProgress,
|
|
2939
|
+
total: patternTotal,
|
|
2940
|
+
area: sourceArea,
|
|
2941
|
+
patterns: patterns.length
|
|
2942
|
+
});
|
|
2943
|
+
}
|
|
2700
2944
|
}
|
|
2701
2945
|
const testedComponents = components.filter((component) => component.relatedTests.length > 0);
|
|
2702
2946
|
if (testedComponents.length > 0) {
|
|
@@ -3026,8 +3270,24 @@ function detectTestCommands(db, cwd, files = []) {
|
|
|
3026
3270
|
return true;
|
|
3027
3271
|
});
|
|
3028
3272
|
}
|
|
3029
|
-
function refreshTestCommands(db, cwd, repo, files = []) {
|
|
3273
|
+
function refreshTestCommands(db, cwd, repo, files = [], options = {}) {
|
|
3274
|
+
options.onProgress?.({
|
|
3275
|
+
stage: "refreshing_test_commands",
|
|
3276
|
+
repo,
|
|
3277
|
+
phase: "detecting",
|
|
3278
|
+
current: 0,
|
|
3279
|
+
total: files.length,
|
|
3280
|
+
commands: 0
|
|
3281
|
+
});
|
|
3030
3282
|
const commands = detectTestCommands(db, cwd, files);
|
|
3283
|
+
options.onProgress?.({
|
|
3284
|
+
stage: "refreshing_test_commands",
|
|
3285
|
+
repo,
|
|
3286
|
+
phase: "writing",
|
|
3287
|
+
current: 0,
|
|
3288
|
+
total: commands.length,
|
|
3289
|
+
commands: commands.length
|
|
3290
|
+
});
|
|
3031
3291
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3032
3292
|
const transaction = db.transaction(() => {
|
|
3033
3293
|
db.prepare("DELETE FROM test_commands WHERE repo = ?").run(repo);
|
|
@@ -3035,7 +3295,7 @@ function refreshTestCommands(db, cwd, repo, files = []) {
|
|
|
3035
3295
|
`INSERT INTO test_commands (id, repo, file_path, command, reason, confidence, created_at)
|
|
3036
3296
|
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
3037
3297
|
);
|
|
3038
|
-
for (const command of commands) {
|
|
3298
|
+
for (const [index, command] of commands.entries()) {
|
|
3039
3299
|
insert.run(
|
|
3040
3300
|
commandId(repo, command),
|
|
3041
3301
|
repo,
|
|
@@ -3045,6 +3305,17 @@ function refreshTestCommands(db, cwd, repo, files = []) {
|
|
|
3045
3305
|
command.confidence,
|
|
3046
3306
|
now
|
|
3047
3307
|
);
|
|
3308
|
+
const current = index + 1;
|
|
3309
|
+
if (current === 1 || current === commands.length || current % 250 === 0) {
|
|
3310
|
+
options.onProgress?.({
|
|
3311
|
+
stage: "refreshing_test_commands",
|
|
3312
|
+
repo,
|
|
3313
|
+
phase: "writing",
|
|
3314
|
+
current,
|
|
3315
|
+
total: commands.length,
|
|
3316
|
+
commands: commands.length
|
|
3317
|
+
});
|
|
3318
|
+
}
|
|
3048
3319
|
}
|
|
3049
3320
|
});
|
|
3050
3321
|
transaction();
|
|
@@ -3083,7 +3354,9 @@ function indexCodebase(db, options) {
|
|
|
3083
3354
|
chunks: fileChunks.length
|
|
3084
3355
|
});
|
|
3085
3356
|
}
|
|
3086
|
-
const architecture = buildArchitectureIndex(options.repo, discovery.files, chunks
|
|
3357
|
+
const architecture = buildArchitectureIndex(options.repo, discovery.files, chunks, {
|
|
3358
|
+
onProgress: options.onProgress
|
|
3359
|
+
});
|
|
3087
3360
|
options.onProgress?.({
|
|
3088
3361
|
stage: "indexed_architecture",
|
|
3089
3362
|
repo: options.repo,
|
|
@@ -3098,9 +3371,22 @@ function indexCodebase(db, options) {
|
|
|
3098
3371
|
chunks,
|
|
3099
3372
|
discovery.skippedFiles,
|
|
3100
3373
|
options.cwd,
|
|
3101
|
-
architecture
|
|
3374
|
+
architecture,
|
|
3375
|
+
{ onProgress: options.onProgress }
|
|
3102
3376
|
);
|
|
3103
|
-
refreshTestCommands(db, options.cwd, options.repo);
|
|
3377
|
+
refreshTestCommands(db, options.cwd, options.repo, [], { onProgress: options.onProgress });
|
|
3378
|
+
options.onProgress?.({
|
|
3379
|
+
stage: "completed_code_index",
|
|
3380
|
+
repo: options.repo,
|
|
3381
|
+
files: summary.indexedFiles,
|
|
3382
|
+
chunks: summary.codeChunksCreated,
|
|
3383
|
+
skippedFiles: summary.skippedFiles,
|
|
3384
|
+
testFiles: summary.testFilesIndexed,
|
|
3385
|
+
testLinks: summary.testLinksCreated,
|
|
3386
|
+
architectureComponents: summary.architectureComponentsIndexed,
|
|
3387
|
+
architecturePatterns: summary.architecturePatternsIndexed,
|
|
3388
|
+
architectureImports: summary.architectureImportsIndexed
|
|
3389
|
+
});
|
|
3104
3390
|
return summary;
|
|
3105
3391
|
}
|
|
3106
3392
|
function emptyCodeIndexSummary(cwd) {
|
|
@@ -3425,7 +3711,8 @@ function indexPullRequests(db, pullRequests, options) {
|
|
|
3425
3711
|
current: index + 1,
|
|
3426
3712
|
total: pullRequests.length,
|
|
3427
3713
|
prNumber: pr.number,
|
|
3428
|
-
wisdomUnitsCreated: result.wisdom
|
|
3714
|
+
wisdomUnitsCreated: result.wisdom,
|
|
3715
|
+
regressionEventsCreated: result.regressions
|
|
3429
3716
|
});
|
|
3430
3717
|
}
|
|
3431
3718
|
if (options.updateSyncStateAfter !== false) {
|
|
@@ -6292,6 +6579,18 @@ function parseGraphQLResponse(text, status, headers) {
|
|
|
6292
6579
|
);
|
|
6293
6580
|
}
|
|
6294
6581
|
}
|
|
6582
|
+
function sleep2(ms) {
|
|
6583
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
6584
|
+
}
|
|
6585
|
+
function isTransientGraphQLError(error) {
|
|
6586
|
+
const status = error.status;
|
|
6587
|
+
if (status === 502 || status === 503 || status === 504) return true;
|
|
6588
|
+
const message = (error.message ?? "").toLowerCase();
|
|
6589
|
+
return message.includes("fetch failed") || message.includes("econnreset") || message.includes("etimedout") || message.includes("socket hang up") || message.includes("network") || message.includes("non-json response") && (message.includes("text/html") || message.includes("<!doctype") || message.includes("<html") || typeof status === "number" && status >= 500);
|
|
6590
|
+
}
|
|
6591
|
+
function transientRetryDelayMs(attempt) {
|
|
6592
|
+
return Math.min(4e3, 500 * 2 ** Math.max(0, attempt - 1));
|
|
6593
|
+
}
|
|
6295
6594
|
function createGitHubGraphQLRequester(options) {
|
|
6296
6595
|
if (!options.token.trim()) {
|
|
6297
6596
|
throw new Error(
|
|
@@ -6303,36 +6602,55 @@ function createGitHubGraphQLRequester(options) {
|
|
|
6303
6602
|
return async function requestGitHubGraphQL(query, variables, requestOptions) {
|
|
6304
6603
|
return requestWithGitHubRateLimit(
|
|
6305
6604
|
async () => {
|
|
6306
|
-
const
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6605
|
+
const maxAttempts = Math.max(1, requestOptions.maxTransientRetries ?? 3);
|
|
6606
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
6607
|
+
try {
|
|
6608
|
+
const response = await fetchImpl("https://api.github.com/graphql", {
|
|
6609
|
+
method: "POST",
|
|
6610
|
+
headers: {
|
|
6611
|
+
accept: "application/vnd.github+json",
|
|
6612
|
+
authorization: `Bearer ${options.token}`,
|
|
6613
|
+
"content-type": "application/json",
|
|
6614
|
+
"user-agent": "anchor-local-mcp"
|
|
6615
|
+
},
|
|
6616
|
+
body: JSON.stringify({ query, variables })
|
|
6617
|
+
});
|
|
6618
|
+
const headers = headersToRecord(response.headers);
|
|
6619
|
+
const raw = parseGraphQLResponse(await response.text(), response.status, headers);
|
|
6620
|
+
if (!response.ok || raw.errors?.length) {
|
|
6621
|
+
throw new GitHubGraphQLError(errorMessage(response.status, raw.errors), {
|
|
6622
|
+
status: errorStatus(response.status, raw.errors),
|
|
6623
|
+
headers
|
|
6624
|
+
});
|
|
6625
|
+
}
|
|
6626
|
+
if (!raw.data) {
|
|
6627
|
+
throw new GitHubGraphQLError("GitHub GraphQL response did not include data.", {
|
|
6628
|
+
status: response.status,
|
|
6629
|
+
headers
|
|
6630
|
+
});
|
|
6631
|
+
}
|
|
6632
|
+
updateGitHubGraphQLRateLimitState(
|
|
6633
|
+
requestOptions.controller,
|
|
6634
|
+
raw.data.rateLimit,
|
|
6635
|
+
requestOptions.requestName
|
|
6636
|
+
);
|
|
6637
|
+
return { data: raw.data, headers };
|
|
6638
|
+
} catch (error) {
|
|
6639
|
+
if (attempt >= maxAttempts || !isTransientGraphQLError(error)) throw error;
|
|
6640
|
+
const waitMs = transientRetryDelayMs(attempt);
|
|
6641
|
+
requestOptions.onTransientRetry?.({
|
|
6642
|
+
attempt,
|
|
6643
|
+
maxAttempts,
|
|
6644
|
+
waitMs,
|
|
6645
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
6646
|
+
});
|
|
6647
|
+
await sleep2(waitMs);
|
|
6648
|
+
}
|
|
6329
6649
|
}
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
);
|
|
6335
|
-
return { data: raw.data, headers };
|
|
6650
|
+
throw new GitHubGraphQLError("GitHub GraphQL request retry loop exited unexpectedly.", {
|
|
6651
|
+
status: 500,
|
|
6652
|
+
headers: {}
|
|
6653
|
+
});
|
|
6336
6654
|
},
|
|
6337
6655
|
{
|
|
6338
6656
|
controller: requestOptions.controller,
|
|
@@ -6518,6 +6836,16 @@ var GraphQLBudget = class {
|
|
|
6518
6836
|
};
|
|
6519
6837
|
}
|
|
6520
6838
|
};
|
|
6839
|
+
function graphqlRetryProgress(repo, onProgress) {
|
|
6840
|
+
return (retry) => onProgress?.({
|
|
6841
|
+
stage: "github_graphql_retry",
|
|
6842
|
+
repo,
|
|
6843
|
+
attempt: retry.attempt,
|
|
6844
|
+
maxAttempts: retry.maxAttempts,
|
|
6845
|
+
waitMs: retry.waitMs,
|
|
6846
|
+
reason: retry.reason
|
|
6847
|
+
});
|
|
6848
|
+
}
|
|
6521
6849
|
var PULL_REQUEST_FIELDS = `
|
|
6522
6850
|
number
|
|
6523
6851
|
url
|
|
@@ -6655,7 +6983,8 @@ function connectionNodes(connection) {
|
|
|
6655
6983
|
async function requestGraphQLWithBudget(requestGraphQL, query, variables, options) {
|
|
6656
6984
|
const response = await requestGraphQL(query, variables, {
|
|
6657
6985
|
controller: options.controller,
|
|
6658
|
-
requestName: options.requestName
|
|
6986
|
+
requestName: options.requestName,
|
|
6987
|
+
onTransientRetry: options.onTransientRetry
|
|
6659
6988
|
});
|
|
6660
6989
|
options.budget.observe(response.data.rateLimit);
|
|
6661
6990
|
return response;
|
|
@@ -6741,7 +7070,8 @@ async function appendAdditionalFiles(requestGraphQL, record, initialConnection,
|
|
|
6741
7070
|
{
|
|
6742
7071
|
controller: options.controller,
|
|
6743
7072
|
requestName: `GraphQL /repos/${record.repo}/pulls/${record.number}/files`,
|
|
6744
|
-
budget: options.budget
|
|
7073
|
+
budget: options.budget,
|
|
7074
|
+
onTransientRetry: options.onTransientRetry
|
|
6745
7075
|
}
|
|
6746
7076
|
);
|
|
6747
7077
|
record.files.push(
|
|
@@ -6767,7 +7097,8 @@ async function appendAdditionalIssueComments(requestGraphQL, record, initialConn
|
|
|
6767
7097
|
{
|
|
6768
7098
|
controller: options.controller,
|
|
6769
7099
|
requestName: `GraphQL /repos/${record.repo}/issues/${record.number}/comments`,
|
|
6770
|
-
budget: options.budget
|
|
7100
|
+
budget: options.budget,
|
|
7101
|
+
onTransientRetry: options.onTransientRetry
|
|
6771
7102
|
}
|
|
6772
7103
|
);
|
|
6773
7104
|
record.issueComments?.push(...connectionNodes(connection).map(mapIssueComment));
|
|
@@ -6791,7 +7122,8 @@ async function appendAdditionalCommits(requestGraphQL, record, initialConnection
|
|
|
6791
7122
|
{
|
|
6792
7123
|
controller: options.controller,
|
|
6793
7124
|
requestName: `GraphQL /repos/${record.repo}/pulls/${record.number}/commits`,
|
|
6794
|
-
budget: options.budget
|
|
7125
|
+
budget: options.budget,
|
|
7126
|
+
onTransientRetry: options.onTransientRetry
|
|
6795
7127
|
}
|
|
6796
7128
|
);
|
|
6797
7129
|
record.commits?.push(
|
|
@@ -6816,7 +7148,8 @@ async function appendAdditionalReviewComments(requestGraphQL, record, review, op
|
|
|
6816
7148
|
{
|
|
6817
7149
|
controller: options.controller,
|
|
6818
7150
|
requestName: `GraphQL /pull-request-reviews/${review.id}/comments`,
|
|
6819
|
-
budget: options.budget
|
|
7151
|
+
budget: options.budget,
|
|
7152
|
+
onTransientRetry: options.onTransientRetry
|
|
6820
7153
|
}
|
|
6821
7154
|
);
|
|
6822
7155
|
const connection = response.data.node?.comments;
|
|
@@ -6842,7 +7175,8 @@ async function appendAdditionalReviews(requestGraphQL, record, initialConnection
|
|
|
6842
7175
|
{
|
|
6843
7176
|
controller: options.controller,
|
|
6844
7177
|
requestName: `GraphQL /repos/${record.repo}/pulls/${record.number}/reviews`,
|
|
6845
|
-
budget: options.budget
|
|
7178
|
+
budget: options.budget,
|
|
7179
|
+
onTransientRetry: options.onTransientRetry
|
|
6846
7180
|
}
|
|
6847
7181
|
);
|
|
6848
7182
|
const reviewNodes = connectionNodes(connection);
|
|
@@ -6856,7 +7190,8 @@ async function appendAdditionalReviews(requestGraphQL, record, initialConnection
|
|
|
6856
7190
|
for (const review of reviewsToHydrate) {
|
|
6857
7191
|
await appendAdditionalReviewComments(requestGraphQL, record, review, {
|
|
6858
7192
|
controller: options.controller,
|
|
6859
|
-
budget: options.budget
|
|
7193
|
+
budget: options.budget,
|
|
7194
|
+
onTransientRetry: options.onTransientRetry
|
|
6860
7195
|
});
|
|
6861
7196
|
}
|
|
6862
7197
|
}
|
|
@@ -7012,6 +7347,7 @@ async function fetchMergedPullRequestsWithGraphQL(options) {
|
|
|
7012
7347
|
checkpoint?.pageSize ?? Math.min(INITIAL_PULL_REQUEST_PAGE_SIZE, options.limit ?? INITIAL_PULL_REQUEST_PAGE_SIZE)
|
|
7013
7348
|
);
|
|
7014
7349
|
const budget = new GraphQLBudget(GRAPHQL_RATE_LIMIT_RESERVE);
|
|
7350
|
+
const onTransientRetry = graphqlRetryProgress(options.repo, options.onProgress);
|
|
7015
7351
|
const checkpointScope = checkpoint?.scope ?? `${options.repo}|${options.limit === void 0 ? "all" : `limit:${options.limit}`}|since:${options.since ?? ""}`;
|
|
7016
7352
|
options.onProgress?.({
|
|
7017
7353
|
stage: "discovering_pull_requests",
|
|
@@ -7038,7 +7374,8 @@ async function fetchMergedPullRequestsWithGraphQL(options) {
|
|
|
7038
7374
|
{
|
|
7039
7375
|
controller: options.controller,
|
|
7040
7376
|
requestName: "GraphQL rate limit preflight",
|
|
7041
|
-
budget
|
|
7377
|
+
budget,
|
|
7378
|
+
onTransientRetry
|
|
7042
7379
|
}
|
|
7043
7380
|
);
|
|
7044
7381
|
const preflightRateLimit = budget.rateLimit();
|
|
@@ -7100,7 +7437,8 @@ async function fetchMergedPullRequestsWithGraphQL(options) {
|
|
|
7100
7437
|
{
|
|
7101
7438
|
controller: options.controller,
|
|
7102
7439
|
requestName: `GraphQL /repos/${options.repo}/pullRequests`,
|
|
7103
|
-
budget
|
|
7440
|
+
budget,
|
|
7441
|
+
onTransientRetry
|
|
7104
7442
|
}
|
|
7105
7443
|
);
|
|
7106
7444
|
} catch (error) {
|
|
@@ -7131,7 +7469,8 @@ async function fetchMergedPullRequestsWithGraphQL(options) {
|
|
|
7131
7469
|
owner,
|
|
7132
7470
|
name,
|
|
7133
7471
|
controller: options.controller,
|
|
7134
|
-
budget
|
|
7472
|
+
budget,
|
|
7473
|
+
onTransientRetry
|
|
7135
7474
|
});
|
|
7136
7475
|
records.push(record);
|
|
7137
7476
|
if (options.limit !== void 0 && records.length >= options.limit) break;
|
|
@@ -7553,6 +7892,9 @@ function openOrgDatabase(org, baseDir) {
|
|
|
7553
7892
|
initializeSchema(db);
|
|
7554
7893
|
return db;
|
|
7555
7894
|
}
|
|
7895
|
+
function openOrgDatabaseReadOnly(org, baseDir) {
|
|
7896
|
+
return openAnchorDatabaseReadOnly(orgDatabasePath(org, baseDir));
|
|
7897
|
+
}
|
|
7556
7898
|
function syncOrgConfigToDatabase(db, config, baseDir) {
|
|
7557
7899
|
initializeSchema(db);
|
|
7558
7900
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -7730,9 +8072,11 @@ function grade(score) {
|
|
|
7730
8072
|
if (score < 80) return "good";
|
|
7731
8073
|
return "excellent";
|
|
7732
8074
|
}
|
|
7733
|
-
function getOrgStatus(db, config, baseDir) {
|
|
7734
|
-
|
|
7735
|
-
|
|
8075
|
+
function getOrgStatus(db, config, baseDir, options = {}) {
|
|
8076
|
+
if (options.syncConfig !== false) {
|
|
8077
|
+
initializeSchema(db);
|
|
8078
|
+
syncOrgConfigToDatabase(db, config, baseDir);
|
|
8079
|
+
}
|
|
7736
8080
|
const enabledRepos = config.repos.filter((repo) => repo.enabled);
|
|
7737
8081
|
const states = new Map(
|
|
7738
8082
|
db.prepare("SELECT * FROM org_repo_state WHERE org = ?").all(config.org).map((row) => [row.repo, row])
|
|
@@ -7779,6 +8123,8 @@ function getOrgStatus(db, config, baseDir) {
|
|
|
7779
8123
|
org: config.org,
|
|
7780
8124
|
root: orgRoot(config.org, baseDir),
|
|
7781
8125
|
databasePath: orgDatabasePath(config.org, baseDir),
|
|
8126
|
+
statusReadError: options.statusReadError,
|
|
8127
|
+
activeRun: options.activeRun,
|
|
7782
8128
|
repoCount: config.repos.length,
|
|
7783
8129
|
enabledRepoCount: enabledRepos.length,
|
|
7784
8130
|
clonedRepoCount,
|
|
@@ -7813,10 +8159,141 @@ function getOrgStatus(db, config, baseDir) {
|
|
|
7813
8159
|
};
|
|
7814
8160
|
}
|
|
7815
8161
|
|
|
7816
|
-
// src/org/
|
|
7817
|
-
import { execFileSync as execFileSync4 } from "child_process";
|
|
8162
|
+
// src/org/heartbeat.ts
|
|
7818
8163
|
import fs11 from "fs";
|
|
7819
8164
|
import path21 from "path";
|
|
8165
|
+
var HEARTBEAT_STALE_AFTER_MS = 2 * 60 * 1e3;
|
|
8166
|
+
function orgHeartbeatPath(org, baseDir) {
|
|
8167
|
+
return path21.join(orgRoot(validateOrgName(org), baseDir), "sync-heartbeat.json");
|
|
8168
|
+
}
|
|
8169
|
+
function atomicWriteJson2(filePath, value) {
|
|
8170
|
+
fs11.mkdirSync(path21.dirname(filePath), { recursive: true });
|
|
8171
|
+
const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
8172
|
+
fs11.writeFileSync(tmp, `${JSON.stringify(value, null, 2)}
|
|
8173
|
+
`, { mode: 384 });
|
|
8174
|
+
fs11.renameSync(tmp, filePath);
|
|
8175
|
+
}
|
|
8176
|
+
function processIsRunning(pid) {
|
|
8177
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
8178
|
+
try {
|
|
8179
|
+
process.kill(pid, 0);
|
|
8180
|
+
return true;
|
|
8181
|
+
} catch {
|
|
8182
|
+
return false;
|
|
8183
|
+
}
|
|
8184
|
+
}
|
|
8185
|
+
function parseHeartbeat(value) {
|
|
8186
|
+
if (!value || typeof value !== "object") return void 0;
|
|
8187
|
+
const candidate = value;
|
|
8188
|
+
if (typeof candidate.pid !== "number" || typeof candidate.command !== "string" || typeof candidate.org !== "string" || typeof candidate.phase !== "string" || typeof candidate.startedAt !== "string" || typeof candidate.updatedAt !== "string") {
|
|
8189
|
+
return void 0;
|
|
8190
|
+
}
|
|
8191
|
+
return {
|
|
8192
|
+
pid: candidate.pid,
|
|
8193
|
+
command: candidate.command,
|
|
8194
|
+
org: candidate.org,
|
|
8195
|
+
repo: typeof candidate.repo === "string" ? candidate.repo : void 0,
|
|
8196
|
+
repoIndex: typeof candidate.repoIndex === "number" ? candidate.repoIndex : void 0,
|
|
8197
|
+
repoTotal: typeof candidate.repoTotal === "number" ? candidate.repoTotal : void 0,
|
|
8198
|
+
phase: candidate.phase,
|
|
8199
|
+
timeline: parseTimeline(candidate.timeline),
|
|
8200
|
+
startedAt: candidate.startedAt,
|
|
8201
|
+
updatedAt: candidate.updatedAt
|
|
8202
|
+
};
|
|
8203
|
+
}
|
|
8204
|
+
function parseTimelineStatus(value) {
|
|
8205
|
+
if (value === "active" || value === "done" || value === "skipped" || value === "warn" || value === "fail" || value === "wait") {
|
|
8206
|
+
return value;
|
|
8207
|
+
}
|
|
8208
|
+
return void 0;
|
|
8209
|
+
}
|
|
8210
|
+
function parseTimelineNumber(value) {
|
|
8211
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
8212
|
+
}
|
|
8213
|
+
function parseTimelineStep(value) {
|
|
8214
|
+
if (!value || typeof value !== "object") return void 0;
|
|
8215
|
+
const candidate = value;
|
|
8216
|
+
const status = parseTimelineStatus(candidate.status);
|
|
8217
|
+
if (typeof candidate.id !== "string" || typeof candidate.label !== "string" || !status || typeof candidate.startedAt !== "string" || typeof candidate.updatedAt !== "string") {
|
|
8218
|
+
return void 0;
|
|
8219
|
+
}
|
|
8220
|
+
return {
|
|
8221
|
+
id: candidate.id,
|
|
8222
|
+
label: candidate.label,
|
|
8223
|
+
status,
|
|
8224
|
+
startedAt: candidate.startedAt,
|
|
8225
|
+
updatedAt: candidate.updatedAt,
|
|
8226
|
+
completedAt: typeof candidate.completedAt === "string" ? candidate.completedAt : void 0,
|
|
8227
|
+
durationMs: parseTimelineNumber(candidate.durationMs),
|
|
8228
|
+
current: parseTimelineNumber(candidate.current),
|
|
8229
|
+
total: parseTimelineNumber(candidate.total),
|
|
8230
|
+
detail: typeof candidate.detail === "string" ? candidate.detail : void 0
|
|
8231
|
+
};
|
|
8232
|
+
}
|
|
8233
|
+
function parseTimelineRepoSummary(value) {
|
|
8234
|
+
if (!value || typeof value !== "object") return void 0;
|
|
8235
|
+
const candidate = value;
|
|
8236
|
+
const status = parseTimelineStatus(candidate.status);
|
|
8237
|
+
if (typeof candidate.repo !== "string" || !status) return void 0;
|
|
8238
|
+
return {
|
|
8239
|
+
repo: candidate.repo,
|
|
8240
|
+
status,
|
|
8241
|
+
durationMs: parseTimelineNumber(candidate.durationMs) ?? 0,
|
|
8242
|
+
detail: typeof candidate.detail === "string" ? candidate.detail : void 0
|
|
8243
|
+
};
|
|
8244
|
+
}
|
|
8245
|
+
function parseTimeline(value) {
|
|
8246
|
+
if (!value || typeof value !== "object") return void 0;
|
|
8247
|
+
const candidate = value;
|
|
8248
|
+
if (!Array.isArray(candidate.steps) || !Array.isArray(candidate.recentRepos)) return void 0;
|
|
8249
|
+
const steps = candidate.steps.map(parseTimelineStep).filter((step) => step !== void 0);
|
|
8250
|
+
const recentRepos = candidate.recentRepos.map(parseTimelineRepoSummary).filter((repo) => repo !== void 0);
|
|
8251
|
+
return {
|
|
8252
|
+
repo: typeof candidate.repo === "string" ? candidate.repo : void 0,
|
|
8253
|
+
repoIndex: parseTimelineNumber(candidate.repoIndex),
|
|
8254
|
+
repoTotal: parseTimelineNumber(candidate.repoTotal),
|
|
8255
|
+
activeStepId: typeof candidate.activeStepId === "string" ? candidate.activeStepId : void 0,
|
|
8256
|
+
steps,
|
|
8257
|
+
recentRepos
|
|
8258
|
+
};
|
|
8259
|
+
}
|
|
8260
|
+
function writeOrgHeartbeat(heartbeat, baseDir) {
|
|
8261
|
+
atomicWriteJson2(orgHeartbeatPath(heartbeat.org, baseDir), heartbeat);
|
|
8262
|
+
}
|
|
8263
|
+
function clearOrgHeartbeat(org, baseDir) {
|
|
8264
|
+
try {
|
|
8265
|
+
const filePath = orgHeartbeatPath(org, baseDir);
|
|
8266
|
+
if (fs11.existsSync(filePath)) fs11.unlinkSync(filePath);
|
|
8267
|
+
} catch {
|
|
8268
|
+
}
|
|
8269
|
+
}
|
|
8270
|
+
function readOrgHeartbeat(org, baseDir) {
|
|
8271
|
+
const filePath = orgHeartbeatPath(org, baseDir);
|
|
8272
|
+
if (!fs11.existsSync(filePath)) return void 0;
|
|
8273
|
+
try {
|
|
8274
|
+
const heartbeat = parseHeartbeat(JSON.parse(fs11.readFileSync(filePath, "utf8")));
|
|
8275
|
+
if (!heartbeat) return void 0;
|
|
8276
|
+
const now = Date.now();
|
|
8277
|
+
const startedAtMs = Date.parse(heartbeat.startedAt);
|
|
8278
|
+
const updatedAtMs = Date.parse(heartbeat.updatedAt);
|
|
8279
|
+
const pidRunning = processIsRunning(heartbeat.pid);
|
|
8280
|
+
const lastUpdateAgeSeconds = Number.isFinite(updatedAtMs) ? Math.max(0, Math.floor((now - updatedAtMs) / 1e3)) : 0;
|
|
8281
|
+
return {
|
|
8282
|
+
...heartbeat,
|
|
8283
|
+
pidRunning,
|
|
8284
|
+
stale: !pidRunning || !Number.isFinite(updatedAtMs) || now - updatedAtMs > HEARTBEAT_STALE_AFTER_MS,
|
|
8285
|
+
elapsedSeconds: Number.isFinite(startedAtMs) ? Math.max(0, Math.floor((now - startedAtMs) / 1e3)) : 0,
|
|
8286
|
+
lastUpdateAgeSeconds
|
|
8287
|
+
};
|
|
8288
|
+
} catch {
|
|
8289
|
+
return void 0;
|
|
8290
|
+
}
|
|
8291
|
+
}
|
|
8292
|
+
|
|
8293
|
+
// src/org/clone.ts
|
|
8294
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
8295
|
+
import fs12 from "fs";
|
|
8296
|
+
import path22 from "path";
|
|
7820
8297
|
function defaultGitCommandRunner(command, args, options = {}) {
|
|
7821
8298
|
return execFileSync4(command, args, {
|
|
7822
8299
|
cwd: options.cwd,
|
|
@@ -7832,7 +8309,7 @@ function currentCommit(runner, localPath) {
|
|
|
7832
8309
|
}
|
|
7833
8310
|
}
|
|
7834
8311
|
function plannedOrgCloneCommands(repo, localPath) {
|
|
7835
|
-
if (!
|
|
8312
|
+
if (!fs12.existsSync(path22.join(localPath, ".git"))) {
|
|
7836
8313
|
return [
|
|
7837
8314
|
{
|
|
7838
8315
|
command: "git",
|
|
@@ -7861,8 +8338,8 @@ function plannedOrgCloneCommands(repo, localPath) {
|
|
|
7861
8338
|
function cloneOrPullOrgRepo(input) {
|
|
7862
8339
|
const runner = input.runner ?? defaultGitCommandRunner;
|
|
7863
8340
|
const localPath = orgRepoLocalPath(input.org, input.repo, input.baseDir);
|
|
7864
|
-
const existed =
|
|
7865
|
-
|
|
8341
|
+
const existed = fs12.existsSync(path22.join(localPath, ".git"));
|
|
8342
|
+
fs12.mkdirSync(path22.dirname(localPath), { recursive: true });
|
|
7866
8343
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7867
8344
|
try {
|
|
7868
8345
|
const commands = plannedOrgCloneCommands(input.repo, localPath);
|
|
@@ -7963,8 +8440,8 @@ function orgCloneStateFromResult(org, repo, result) {
|
|
|
7963
8440
|
|
|
7964
8441
|
// src/org/graph.ts
|
|
7965
8442
|
import crypto9 from "crypto";
|
|
7966
|
-
import
|
|
7967
|
-
import
|
|
8443
|
+
import fs13 from "fs";
|
|
8444
|
+
import path23 from "path";
|
|
7968
8445
|
function stableId(parts) {
|
|
7969
8446
|
return crypto9.createHash("sha256").update(parts.join("\0")).digest("hex").slice(0, 32);
|
|
7970
8447
|
}
|
|
@@ -7978,10 +8455,10 @@ function fileEvidence(repo, filePath, note) {
|
|
|
7978
8455
|
};
|
|
7979
8456
|
}
|
|
7980
8457
|
function readPackageManifest(repoPath) {
|
|
7981
|
-
const packagePath =
|
|
7982
|
-
if (!
|
|
8458
|
+
const packagePath = path23.join(repoPath, "package.json");
|
|
8459
|
+
if (!fs13.existsSync(packagePath)) return void 0;
|
|
7983
8460
|
try {
|
|
7984
|
-
return JSON.parse(
|
|
8461
|
+
return JSON.parse(fs13.readFileSync(packagePath, "utf8"));
|
|
7985
8462
|
} catch {
|
|
7986
8463
|
return void 0;
|
|
7987
8464
|
}
|
|
@@ -8041,7 +8518,7 @@ function isApiConsumerText(text) {
|
|
|
8041
8518
|
function evidenceJson(evidence) {
|
|
8042
8519
|
return JSON.stringify(evidence);
|
|
8043
8520
|
}
|
|
8044
|
-
function
|
|
8521
|
+
function shouldEmitProgress2(current, total, interval = 100) {
|
|
8045
8522
|
return current === 1 || current === total || current % interval === 0;
|
|
8046
8523
|
}
|
|
8047
8524
|
function resolveOptions(baseDirOrOptions) {
|
|
@@ -8147,7 +8624,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
|
|
|
8147
8624
|
});
|
|
8148
8625
|
break;
|
|
8149
8626
|
}
|
|
8150
|
-
if (
|
|
8627
|
+
if (shouldEmitProgress2(index + 1, imports.length)) {
|
|
8151
8628
|
options.onProgress?.({
|
|
8152
8629
|
stage: "building_import_edges",
|
|
8153
8630
|
org: config.org,
|
|
@@ -8188,7 +8665,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
|
|
|
8188
8665
|
bucket.push(apiContract);
|
|
8189
8666
|
contractsByToken.set(sanitizedContract, bucket);
|
|
8190
8667
|
}
|
|
8191
|
-
if (
|
|
8668
|
+
if (shouldEmitProgress2(index + 1, providerChunks.length)) {
|
|
8192
8669
|
options.onProgress?.({
|
|
8193
8670
|
stage: "extracting_api_contracts",
|
|
8194
8671
|
org: config.org,
|
|
@@ -8248,7 +8725,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
|
|
|
8248
8725
|
});
|
|
8249
8726
|
}
|
|
8250
8727
|
}
|
|
8251
|
-
if (
|
|
8728
|
+
if (shouldEmitProgress2(index + 1, consumerChunks.length)) {
|
|
8252
8729
|
options.onProgress?.({
|
|
8253
8730
|
stage: "matching_api_consumers",
|
|
8254
8731
|
org: config.org,
|
|
@@ -8274,7 +8751,11 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
|
|
|
8274
8751
|
const insertEdge = db.prepare(
|
|
8275
8752
|
`INSERT INTO org_cross_repo_edges
|
|
8276
8753
|
(id, org, source_repo, source_path, target_repo, target_path, relationship, evidence_json, confidence, created_at)
|
|
8277
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
8754
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
8755
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
8756
|
+
evidence_json = excluded.evidence_json,
|
|
8757
|
+
confidence = excluded.confidence,
|
|
8758
|
+
created_at = excluded.created_at`
|
|
8278
8759
|
);
|
|
8279
8760
|
for (const edge of edges) {
|
|
8280
8761
|
insertEdge.run(
|
|
@@ -8293,7 +8774,12 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
|
|
|
8293
8774
|
const insertContract = db.prepare(
|
|
8294
8775
|
`INSERT INTO org_api_contracts
|
|
8295
8776
|
(id, org, repo, file_path, contract, evidence_json, confidence, created_at)
|
|
8296
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
8777
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
8778
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
8779
|
+
contract = excluded.contract,
|
|
8780
|
+
evidence_json = excluded.evidence_json,
|
|
8781
|
+
confidence = excluded.confidence,
|
|
8782
|
+
created_at = excluded.created_at`
|
|
8297
8783
|
);
|
|
8298
8784
|
for (const contract of apiContracts) {
|
|
8299
8785
|
insertContract.run(
|
|
@@ -8310,7 +8796,12 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
|
|
|
8310
8796
|
const insertConsumer = db.prepare(
|
|
8311
8797
|
`INSERT INTO org_api_consumers
|
|
8312
8798
|
(id, org, provider_repo, provider_path, consumer_repo, consumer_path, contract, evidence_json, confidence, created_at)
|
|
8313
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
8799
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
8800
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
8801
|
+
contract = excluded.contract,
|
|
8802
|
+
evidence_json = excluded.evidence_json,
|
|
8803
|
+
confidence = excluded.confidence,
|
|
8804
|
+
created_at = excluded.created_at`
|
|
8314
8805
|
);
|
|
8315
8806
|
for (const consumer of apiConsumers) {
|
|
8316
8807
|
insertConsumer.run(
|
|
@@ -8373,7 +8864,7 @@ function rebuildOrgGraph(db, config, baseDirOrOptions) {
|
|
|
8373
8864
|
}
|
|
8374
8865
|
|
|
8375
8866
|
// src/org/index.ts
|
|
8376
|
-
import
|
|
8867
|
+
import fs14 from "fs";
|
|
8377
8868
|
var ORG_SYNC_RESUME_WINDOW_MS = 12 * 60 * 60 * 1e3;
|
|
8378
8869
|
function readCommit(runner, cwd) {
|
|
8379
8870
|
try {
|
|
@@ -8417,14 +8908,42 @@ async function indexOrgRepos(db, config, options = {}) {
|
|
|
8417
8908
|
const auth = options.token ? { token: options.token } : resolveGitHubToken();
|
|
8418
8909
|
const results = [];
|
|
8419
8910
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8911
|
+
const startedAtMs = Date.now();
|
|
8420
8912
|
const graphState = getOrgGraphState(db, config.org);
|
|
8421
|
-
|
|
8913
|
+
const command = options.command ?? "org index";
|
|
8914
|
+
const emit = (progress) => options.onLifecycleProgress?.(progress);
|
|
8915
|
+
emit({
|
|
8916
|
+
stage: "org_sync_started",
|
|
8917
|
+
org: config.org,
|
|
8918
|
+
command,
|
|
8919
|
+
totalRepos: repos.length
|
|
8920
|
+
});
|
|
8921
|
+
for (const [repoIndex, repo] of repos.entries()) {
|
|
8922
|
+
const repoPosition = repoIndex + 1;
|
|
8422
8923
|
const localPath = orgRepoLocalPath(config.org, repo, options.baseDir);
|
|
8423
8924
|
const repoStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8925
|
+
const repoStartedAtMs = Date.now();
|
|
8424
8926
|
let prsIndexed = 0;
|
|
8425
8927
|
let codeFilesIndexed = 0;
|
|
8426
8928
|
try {
|
|
8427
|
-
|
|
8929
|
+
emit({
|
|
8930
|
+
stage: "org_repo_started",
|
|
8931
|
+
org: config.org,
|
|
8932
|
+
command,
|
|
8933
|
+
repo: repo.fullName,
|
|
8934
|
+
current: repoPosition,
|
|
8935
|
+
total: repos.length
|
|
8936
|
+
});
|
|
8937
|
+
if (!fs14.existsSync(localPath)) throw new Error(missingCloneError(repo.fullName, localPath));
|
|
8938
|
+
emit({
|
|
8939
|
+
stage: "org_repo_phase",
|
|
8940
|
+
org: config.org,
|
|
8941
|
+
command,
|
|
8942
|
+
repo: repo.fullName,
|
|
8943
|
+
current: repoPosition,
|
|
8944
|
+
total: repos.length,
|
|
8945
|
+
phase: "Reading current commit"
|
|
8946
|
+
});
|
|
8428
8947
|
const currentCommit2 = readCommit(runner, localPath);
|
|
8429
8948
|
const state = getOrgRepoState(db, config.org, repo.fullName);
|
|
8430
8949
|
let history;
|
|
@@ -8442,6 +8961,15 @@ async function indexOrgRepos(db, config, options = {}) {
|
|
|
8442
8961
|
})) {
|
|
8443
8962
|
skippedHistory = true;
|
|
8444
8963
|
historySkippedReason = "PR history already synced; resuming unfinished org graph/index work.";
|
|
8964
|
+
emit({
|
|
8965
|
+
stage: "org_repo_skipped_history",
|
|
8966
|
+
org: config.org,
|
|
8967
|
+
command,
|
|
8968
|
+
repo: repo.fullName,
|
|
8969
|
+
current: repoPosition,
|
|
8970
|
+
total: repos.length,
|
|
8971
|
+
reason: historySkippedReason
|
|
8972
|
+
});
|
|
8445
8973
|
options.onFetchProgress?.({
|
|
8446
8974
|
stage: "skipped_pull_request_fetch",
|
|
8447
8975
|
repo: repo.fullName,
|
|
@@ -8453,6 +8981,15 @@ async function indexOrgRepos(db, config, options = {}) {
|
|
|
8453
8981
|
);
|
|
8454
8982
|
} else {
|
|
8455
8983
|
try {
|
|
8984
|
+
emit({
|
|
8985
|
+
stage: "org_repo_phase",
|
|
8986
|
+
org: config.org,
|
|
8987
|
+
command,
|
|
8988
|
+
repo: repo.fullName,
|
|
8989
|
+
current: repoPosition,
|
|
8990
|
+
total: repos.length,
|
|
8991
|
+
phase: "Fetching PR history"
|
|
8992
|
+
});
|
|
8456
8993
|
const since = options.since ?? (options.command === "org sync" ? state?.lastPrSyncAt ?? getLastSyncTime(db, repo.fullName) : void 0);
|
|
8457
8994
|
const pullRequests = await fetchPullRequests({
|
|
8458
8995
|
token: auth.token,
|
|
@@ -8462,6 +8999,16 @@ async function indexOrgRepos(db, config, options = {}) {
|
|
|
8462
8999
|
detailConcurrency: options.concurrency,
|
|
8463
9000
|
onProgress: options.onFetchProgress
|
|
8464
9001
|
});
|
|
9002
|
+
emit({
|
|
9003
|
+
stage: "org_repo_phase",
|
|
9004
|
+
org: config.org,
|
|
9005
|
+
command,
|
|
9006
|
+
repo: repo.fullName,
|
|
9007
|
+
current: repoPosition,
|
|
9008
|
+
total: repos.length,
|
|
9009
|
+
phase: "Indexing PR history into SQLite",
|
|
9010
|
+
detail: `${pullRequests.length} PR(s)`
|
|
9011
|
+
});
|
|
8465
9012
|
history = indexPullRequests(db, pullRequests, {
|
|
8466
9013
|
cwd: localPath,
|
|
8467
9014
|
repo: repo.fullName,
|
|
@@ -8486,6 +9033,15 @@ async function indexOrgRepos(db, config, options = {}) {
|
|
|
8486
9033
|
}
|
|
8487
9034
|
const codeUnchanged = !options.force && currentCommit2 && state?.lastCodeIndexedCommit && currentCommit2 === state.lastCodeIndexedCommit;
|
|
8488
9035
|
if (!options.prsOnly && !codeUnchanged) {
|
|
9036
|
+
emit({
|
|
9037
|
+
stage: "org_repo_phase",
|
|
9038
|
+
org: config.org,
|
|
9039
|
+
command,
|
|
9040
|
+
repo: repo.fullName,
|
|
9041
|
+
current: repoPosition,
|
|
9042
|
+
total: repos.length,
|
|
9043
|
+
phase: "Indexing code and architecture"
|
|
9044
|
+
});
|
|
8489
9045
|
code = indexCodebase(db, {
|
|
8490
9046
|
cwd: localPath,
|
|
8491
9047
|
repo: repo.fullName,
|
|
@@ -8501,6 +9057,26 @@ async function indexOrgRepos(db, config, options = {}) {
|
|
|
8501
9057
|
lastCodeIndexedCommit: currentCommit2,
|
|
8502
9058
|
lastCodeIndexedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8503
9059
|
});
|
|
9060
|
+
} else if (!options.prsOnly && codeUnchanged) {
|
|
9061
|
+
emit({
|
|
9062
|
+
stage: "org_repo_skipped_code",
|
|
9063
|
+
org: config.org,
|
|
9064
|
+
command,
|
|
9065
|
+
repo: repo.fullName,
|
|
9066
|
+
current: repoPosition,
|
|
9067
|
+
total: repos.length,
|
|
9068
|
+
reason: "Code skipped: current commit already indexed."
|
|
9069
|
+
});
|
|
9070
|
+
} else if (options.prsOnly) {
|
|
9071
|
+
emit({
|
|
9072
|
+
stage: "org_repo_skipped_code",
|
|
9073
|
+
org: config.org,
|
|
9074
|
+
command,
|
|
9075
|
+
repo: repo.fullName,
|
|
9076
|
+
current: repoPosition,
|
|
9077
|
+
total: repos.length,
|
|
9078
|
+
reason: "Code skipped because --prs-only was passed."
|
|
9079
|
+
});
|
|
8504
9080
|
}
|
|
8505
9081
|
if (repoFailures.length > 0) {
|
|
8506
9082
|
updateOrgRepoState(db, {
|
|
@@ -8512,6 +9088,14 @@ async function indexOrgRepos(db, config, options = {}) {
|
|
|
8512
9088
|
lastError: repoFailures.join("; ")
|
|
8513
9089
|
});
|
|
8514
9090
|
}
|
|
9091
|
+
emit({
|
|
9092
|
+
stage: "org_repo_finalizing",
|
|
9093
|
+
org: config.org,
|
|
9094
|
+
command,
|
|
9095
|
+
repo: repo.fullName,
|
|
9096
|
+
current: repoPosition,
|
|
9097
|
+
total: repos.length
|
|
9098
|
+
});
|
|
8515
9099
|
results.push({
|
|
8516
9100
|
repo: repo.fullName,
|
|
8517
9101
|
skippedCode: Boolean(codeUnchanged || options.prsOnly),
|
|
@@ -8525,7 +9109,7 @@ async function indexOrgRepos(db, config, options = {}) {
|
|
|
8525
9109
|
recordOrgIndexRun(db, {
|
|
8526
9110
|
org: config.org,
|
|
8527
9111
|
repo: repo.fullName,
|
|
8528
|
-
command
|
|
9112
|
+
command,
|
|
8529
9113
|
startedAt: repoStartedAt,
|
|
8530
9114
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8531
9115
|
status: repoFailures.length > 0 ? "partial" : "success",
|
|
@@ -8533,6 +9117,20 @@ async function indexOrgRepos(db, config, options = {}) {
|
|
|
8533
9117
|
codeFilesIndexed,
|
|
8534
9118
|
failures: repoFailures
|
|
8535
9119
|
});
|
|
9120
|
+
emit({
|
|
9121
|
+
stage: "org_repo_completed",
|
|
9122
|
+
org: config.org,
|
|
9123
|
+
command,
|
|
9124
|
+
repo: repo.fullName,
|
|
9125
|
+
current: repoPosition,
|
|
9126
|
+
total: repos.length,
|
|
9127
|
+
skippedHistory,
|
|
9128
|
+
skippedCode: Boolean(codeUnchanged || options.prsOnly),
|
|
9129
|
+
prsIndexed,
|
|
9130
|
+
codeFilesIndexed,
|
|
9131
|
+
durationMs: Date.now() - repoStartedAtMs,
|
|
9132
|
+
error: repoFailures.join("; ") || void 0
|
|
9133
|
+
});
|
|
8536
9134
|
} catch (error) {
|
|
8537
9135
|
const message = error instanceof Error ? error.message : String(error);
|
|
8538
9136
|
updateOrgRepoState(db, {
|
|
@@ -8545,7 +9143,7 @@ async function indexOrgRepos(db, config, options = {}) {
|
|
|
8545
9143
|
recordOrgIndexRun(db, {
|
|
8546
9144
|
org: config.org,
|
|
8547
9145
|
repo: repo.fullName,
|
|
8548
|
-
command
|
|
9146
|
+
command,
|
|
8549
9147
|
startedAt: repoStartedAt,
|
|
8550
9148
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8551
9149
|
status: "failed",
|
|
@@ -8556,6 +9154,20 @@ async function indexOrgRepos(db, config, options = {}) {
|
|
|
8556
9154
|
skippedCode: false,
|
|
8557
9155
|
error: message
|
|
8558
9156
|
});
|
|
9157
|
+
emit({
|
|
9158
|
+
stage: "org_repo_completed",
|
|
9159
|
+
org: config.org,
|
|
9160
|
+
command,
|
|
9161
|
+
repo: repo.fullName,
|
|
9162
|
+
current: repoPosition,
|
|
9163
|
+
total: repos.length,
|
|
9164
|
+
skippedHistory: false,
|
|
9165
|
+
skippedCode: false,
|
|
9166
|
+
prsIndexed,
|
|
9167
|
+
codeFilesIndexed,
|
|
9168
|
+
durationMs: Date.now() - repoStartedAtMs,
|
|
9169
|
+
error: message
|
|
9170
|
+
});
|
|
8559
9171
|
}
|
|
8560
9172
|
}
|
|
8561
9173
|
let graph;
|
|
@@ -8568,6 +9180,12 @@ async function indexOrgRepos(db, config, options = {}) {
|
|
|
8568
9180
|
apiContractCount: counts.apiContracts,
|
|
8569
9181
|
apiConsumerCount: counts.apiConsumers
|
|
8570
9182
|
});
|
|
9183
|
+
emit({
|
|
9184
|
+
stage: "org_graph_skipped",
|
|
9185
|
+
org: config.org,
|
|
9186
|
+
command,
|
|
9187
|
+
reason: "Graph skipped because --no-graph was passed."
|
|
9188
|
+
});
|
|
8571
9189
|
graph = { ...counts, skipped: true };
|
|
8572
9190
|
} else {
|
|
8573
9191
|
try {
|
|
@@ -8588,7 +9206,7 @@ async function indexOrgRepos(db, config, options = {}) {
|
|
|
8588
9206
|
}
|
|
8589
9207
|
recordOrgIndexRun(db, {
|
|
8590
9208
|
org: config.org,
|
|
8591
|
-
command
|
|
9209
|
+
command,
|
|
8592
9210
|
startedAt,
|
|
8593
9211
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8594
9212
|
status: results.some((result) => result.error) || graph.error ? "partial" : "success",
|
|
@@ -8596,6 +9214,15 @@ async function indexOrgRepos(db, config, options = {}) {
|
|
|
8596
9214
|
codeFilesIndexed: results.reduce((sum, result) => sum + (result.code?.indexedFiles ?? 0), 0),
|
|
8597
9215
|
failures: results.map((result) => result.error).concat(graph.error ? [graph.error] : []).filter((error) => Boolean(error))
|
|
8598
9216
|
});
|
|
9217
|
+
emit({
|
|
9218
|
+
stage: "org_sync_completed",
|
|
9219
|
+
org: config.org,
|
|
9220
|
+
command,
|
|
9221
|
+
totalRepos: repos.length,
|
|
9222
|
+
succeededRepos: results.filter((result) => !result.error).length,
|
|
9223
|
+
failedRepos: results.filter((result) => result.error).length,
|
|
9224
|
+
durationMs: Date.now() - startedAtMs
|
|
9225
|
+
});
|
|
8599
9226
|
return {
|
|
8600
9227
|
org: config.org,
|
|
8601
9228
|
repos: results.sort((a, b) => a.repo.localeCompare(b.repo)),
|
|
@@ -9220,8 +9847,8 @@ function isTestPath2(filePath) {
|
|
|
9220
9847
|
}
|
|
9221
9848
|
|
|
9222
9849
|
// src/doctor.ts
|
|
9223
|
-
import
|
|
9224
|
-
import
|
|
9850
|
+
import fs15 from "fs";
|
|
9851
|
+
import path24 from "path";
|
|
9225
9852
|
function check(name, ok, message, fix) {
|
|
9226
9853
|
return { name, ok, message, fix: ok ? void 0 : fix };
|
|
9227
9854
|
}
|
|
@@ -9325,12 +9952,12 @@ async function runDoctor(options) {
|
|
|
9325
9952
|
)
|
|
9326
9953
|
);
|
|
9327
9954
|
}
|
|
9328
|
-
const cursorConfigPath =
|
|
9955
|
+
const cursorConfigPath = path24.join(gitRoot ?? cwd, ".cursor", "mcp.json");
|
|
9329
9956
|
let cursorConfig;
|
|
9330
9957
|
let cursorConfigValid = false;
|
|
9331
|
-
if (
|
|
9958
|
+
if (fs15.existsSync(cursorConfigPath)) {
|
|
9332
9959
|
try {
|
|
9333
|
-
cursorConfig = JSON.parse(
|
|
9960
|
+
cursorConfig = JSON.parse(fs15.readFileSync(cursorConfigPath, "utf8"));
|
|
9334
9961
|
cursorConfigValid = true;
|
|
9335
9962
|
} catch {
|
|
9336
9963
|
cursorConfigValid = false;
|
|
@@ -9339,7 +9966,7 @@ async function runDoctor(options) {
|
|
|
9339
9966
|
checks.push(
|
|
9340
9967
|
check(
|
|
9341
9968
|
".cursor/mcp.json valid",
|
|
9342
|
-
|
|
9969
|
+
fs15.existsSync(cursorConfigPath) && cursorConfigValid,
|
|
9343
9970
|
cursorConfigValid ? ".cursor/mcp.json exists and is valid JSON." : ".cursor/mcp.json is missing or invalid.",
|
|
9344
9971
|
"Run anchor init. If the file is malformed, fix the JSON and rerun anchor init."
|
|
9345
9972
|
)
|
|
@@ -9356,7 +9983,7 @@ async function runDoctor(options) {
|
|
|
9356
9983
|
)
|
|
9357
9984
|
);
|
|
9358
9985
|
const dbPath = defaultDatabasePath(gitRoot ?? cwd);
|
|
9359
|
-
const dbExists =
|
|
9986
|
+
const dbExists = fs15.existsSync(dbPath);
|
|
9360
9987
|
checks.push(
|
|
9361
9988
|
check(
|
|
9362
9989
|
".anchor/index.sqlite exists",
|
|
@@ -9400,12 +10027,12 @@ async function runDoctor(options) {
|
|
|
9400
10027
|
"Run pnpm build, then try anchor serve from the repository."
|
|
9401
10028
|
)
|
|
9402
10029
|
);
|
|
9403
|
-
const rulePath =
|
|
10030
|
+
const rulePath = path24.join(gitRoot ?? cwd, ".cursor", "rules", "anchor.mdc");
|
|
9404
10031
|
checks.push(
|
|
9405
10032
|
check(
|
|
9406
10033
|
"Cursor rule file exists",
|
|
9407
|
-
|
|
9408
|
-
|
|
10034
|
+
fs15.existsSync(rulePath),
|
|
10035
|
+
fs15.existsSync(rulePath) ? "Cursor rule file exists." : "Cursor rule file is missing.",
|
|
9409
10036
|
"Run anchor init to create .cursor/rules/anchor.mdc."
|
|
9410
10037
|
)
|
|
9411
10038
|
);
|
|
@@ -9482,6 +10109,7 @@ export {
|
|
|
9482
10109
|
clampMaxResults,
|
|
9483
10110
|
classifyArchitectureArea,
|
|
9484
10111
|
clearGraphQLFetchCheckpoint,
|
|
10112
|
+
clearOrgHeartbeat,
|
|
9485
10113
|
clipSentence,
|
|
9486
10114
|
cloneOrPullOrgRepo,
|
|
9487
10115
|
cloneOrgRepos,
|
|
@@ -9568,10 +10196,13 @@ export {
|
|
|
9568
10196
|
mergeAnchorMcpConfig,
|
|
9569
10197
|
normalizePullRequest,
|
|
9570
10198
|
openAnchorDatabase,
|
|
10199
|
+
openAnchorDatabaseReadOnly,
|
|
9571
10200
|
openOrgDatabase,
|
|
10201
|
+
openOrgDatabaseReadOnly,
|
|
9572
10202
|
orgCloneStateFromResult,
|
|
9573
10203
|
orgConfigPath,
|
|
9574
10204
|
orgDatabasePath,
|
|
10205
|
+
orgHeartbeatPath,
|
|
9575
10206
|
orgRepoLocalPath,
|
|
9576
10207
|
orgReposRoot,
|
|
9577
10208
|
orgRoot,
|
|
@@ -9585,6 +10216,7 @@ export {
|
|
|
9585
10216
|
rankRelevantTests,
|
|
9586
10217
|
rankTeamRules,
|
|
9587
10218
|
rankWisdomUnits,
|
|
10219
|
+
readOrgHeartbeat,
|
|
9588
10220
|
rebuildOrgGraph,
|
|
9589
10221
|
recordFeedback,
|
|
9590
10222
|
recordIndexRun,
|
|
@@ -9628,6 +10260,7 @@ export {
|
|
|
9628
10260
|
validateOrgRepoFullName,
|
|
9629
10261
|
validateOrgRepoGroup,
|
|
9630
10262
|
validateTeamRulesFile,
|
|
9631
|
-
watchCodebase
|
|
10263
|
+
watchCodebase,
|
|
10264
|
+
writeOrgHeartbeat
|
|
9632
10265
|
};
|
|
9633
10266
|
//# sourceMappingURL=index.js.map
|