co-maintainer 0.4.11 → 0.4.13
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/package.json +1 -1
- package/dist/src/github/collect.d.ts +1 -1
- package/dist/src/github/collect.js +78 -18
- package/dist/src/knowledge/types.d.ts +4 -0
- package/dist/src/services/setup.js +15 -1
- package/dist/src/store/app_db.d.ts +3 -1
- package/dist/src/store/app_db.js +5 -1
- package/package.json +1 -1
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { GitHubClient, Options } from "../types.ts";
|
|
2
2
|
import type { PullRequest, Source, State } from "../knowledge/types.ts";
|
|
3
|
-
export declare function collectSource(client: GitHubClient, options: Options, previous?: State, progress?: (items: PullRequest[]) => Promise<void>, codebaseProgress?: (data: {
|
|
3
|
+
export declare function collectSource(client: GitHubClient, options: Options, previous?: State, progress?: (items: PullRequest[], cache: PullRequest[]) => Promise<void>, codebaseProgress?: (data: {
|
|
4
4
|
tree: string[];
|
|
5
5
|
treeSha: Record<string, string>;
|
|
6
6
|
files: Record<string, string>;
|
|
@@ -248,8 +248,10 @@ async function pullRequests(client, options, previous, phase, progress) {
|
|
|
248
248
|
if (phase)
|
|
249
249
|
phase.text = "listing pull requests · page 0 · fetched 0";
|
|
250
250
|
const selected = await listPullRequestPages(client, options, phase);
|
|
251
|
-
const
|
|
251
|
+
const keptByNumber = new Map((previous?.source.pullRequests ?? []).map((pr) => [pr.number, pr]));
|
|
252
|
+
const cacheByNumber = new Map((previous?.source.pullRequestCache ?? []).map((pr) => [pr.number, pr]));
|
|
252
253
|
const result = new Array(selected.length);
|
|
254
|
+
const cache = new Array(selected.length);
|
|
253
255
|
let completed = 0;
|
|
254
256
|
let save = Promise.resolve();
|
|
255
257
|
if (phase) {
|
|
@@ -258,7 +260,8 @@ async function pullRequests(client, options, previous, phase, progress) {
|
|
|
258
260
|
log("fetch", `${selected.length} pull requests · concurrency=${options.ghConcurrent}`);
|
|
259
261
|
await mapPool(selected, options.ghConcurrent, async (pr, index) => {
|
|
260
262
|
const number = Number(pr.number);
|
|
261
|
-
const
|
|
263
|
+
const full = keptByNumber.get(number);
|
|
264
|
+
const cached = full ?? cacheByNumber.get(number);
|
|
262
265
|
const listedAdditions = Number(pr.additions);
|
|
263
266
|
const listedDeletions = Number(pr.deletions);
|
|
264
267
|
const listedStatsAvailable = Number.isFinite(listedAdditions) &&
|
|
@@ -308,10 +311,12 @@ async function pullRequests(client, options, previous, phase, progress) {
|
|
|
308
311
|
current.changesRequested = cached.changesRequested;
|
|
309
312
|
}
|
|
310
313
|
const only = options.onlyRequestChangedPr;
|
|
311
|
-
const discussionUnchanged =
|
|
314
|
+
const discussionUnchanged = full?.updatedAt === current.updatedAt;
|
|
312
315
|
const decisionKnown = typeof cached?.changesRequested === "boolean";
|
|
313
|
-
const diffUnchanged =
|
|
314
|
-
let droppedFromCache = only &&
|
|
316
|
+
const diffUnchanged = full?.headSha === current.headSha && Boolean(full?.diff);
|
|
317
|
+
let droppedFromCache = only &&
|
|
318
|
+
cached?.updatedAt === current.updatedAt &&
|
|
319
|
+
cached.changesRequested === false;
|
|
315
320
|
let dropped = droppedFromCache;
|
|
316
321
|
const loadComments = async () => {
|
|
317
322
|
const comments = await client.pages(`repos/${options.repo}/issues/${number}/comments`);
|
|
@@ -383,6 +388,7 @@ async function pullRequests(client, options, previous, phase, progress) {
|
|
|
383
388
|
current.diff = "";
|
|
384
389
|
}
|
|
385
390
|
}
|
|
391
|
+
cache[index] = current;
|
|
386
392
|
if (!dropped)
|
|
387
393
|
result[index] = current;
|
|
388
394
|
completed++;
|
|
@@ -391,29 +397,82 @@ async function pullRequests(client, options, previous, phase, progress) {
|
|
|
391
397
|
}
|
|
392
398
|
log("fetch", `PR ${completed}/${selected.length} · ${percent(completed, selected.length)} #${number} · ${discussionStatus} · ${diffStatus}`);
|
|
393
399
|
if (progress) {
|
|
394
|
-
save = save.then(() => progress(result.filter((item) => !!item)));
|
|
400
|
+
save = save.then(() => progress(result.filter((item) => !!item), cache.filter((item) => !!item)));
|
|
395
401
|
await save;
|
|
396
402
|
}
|
|
397
403
|
});
|
|
398
404
|
const kept = result.filter((item) => !!item);
|
|
405
|
+
const decided = cache.filter((item) => !!item);
|
|
399
406
|
if (options.onlyRequestChangedPr) {
|
|
400
407
|
log("fetch", `only request-changed pr · kept ${kept.length} · dropped ${selected.length - kept.length}`);
|
|
401
408
|
}
|
|
402
|
-
return kept;
|
|
409
|
+
return { kept, cache: decided };
|
|
403
410
|
}
|
|
404
|
-
|
|
411
|
+
function commitLimit(maxCommits) {
|
|
412
|
+
return maxCommits && maxCommits > 0 ? maxCommits : undefined;
|
|
413
|
+
}
|
|
414
|
+
function commitsCovered(cached, previousMax, limit) {
|
|
415
|
+
if (cached.length === 0)
|
|
416
|
+
return false;
|
|
417
|
+
const previousAll = commitLimit(previousMax) === undefined;
|
|
418
|
+
if (limit === undefined)
|
|
419
|
+
return previousAll;
|
|
420
|
+
return previousAll || cached.length >= limit;
|
|
421
|
+
}
|
|
422
|
+
async function commits(client, options, meta, previous, phase) {
|
|
405
423
|
const branch = String(meta.default_branch ?? "main");
|
|
406
|
-
const limit = options.maxCommits
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
return client.pages(`repos/${options.repo}/commits?sha=${encodeURIComponent(branch)}`, limit, (page, fetched) => {
|
|
424
|
+
const limit = commitLimit(options.maxCommits);
|
|
425
|
+
const cached = previous?.source.commits ?? [];
|
|
426
|
+
const cachedTip = String(cached[0]?.sha ?? "");
|
|
427
|
+
const endpoint = `repos/${options.repo}/commits?sha=${encodeURIComponent(branch)}`;
|
|
428
|
+
const note = (page, fetched) => {
|
|
412
429
|
const message = `commit history · page ${page} · fetched ${fetched} · total unknown`;
|
|
413
430
|
if (phase)
|
|
414
431
|
phase.text = message;
|
|
415
432
|
log("fetch", message);
|
|
416
|
-
}
|
|
433
|
+
};
|
|
434
|
+
if (phase)
|
|
435
|
+
phase.text = "commit history · page 0 · fetched 0";
|
|
436
|
+
if (!cachedTip) {
|
|
437
|
+
return client.pages(endpoint, limit, note);
|
|
438
|
+
}
|
|
439
|
+
const fresh = [];
|
|
440
|
+
let found = false;
|
|
441
|
+
for (let page = 1;; page++) {
|
|
442
|
+
const pageItems = await client.request(`${endpoint}&per_page=100&page=${page}`);
|
|
443
|
+
const items = Array.isArray(pageItems) ? pageItems : [];
|
|
444
|
+
for (const commit of items) {
|
|
445
|
+
if (String(commit.sha ?? "") === cachedTip) {
|
|
446
|
+
found = true;
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
449
|
+
fresh.push(commit);
|
|
450
|
+
if (limit !== undefined && fresh.length >= limit)
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
note(page, fresh.length);
|
|
454
|
+
if (found ||
|
|
455
|
+
items.length < 100 ||
|
|
456
|
+
(limit !== undefined && fresh.length >= limit)) {
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (!found || !commitsCovered(cached, previous?.options.maxCommits, limit)) {
|
|
461
|
+
if (found)
|
|
462
|
+
return client.pages(endpoint, limit, note);
|
|
463
|
+
return limit === undefined ? fresh : fresh.slice(0, limit);
|
|
464
|
+
}
|
|
465
|
+
const selected = [...fresh];
|
|
466
|
+
for (const commit of cached) {
|
|
467
|
+
if (limit !== undefined && selected.length >= limit)
|
|
468
|
+
break;
|
|
469
|
+
selected.push(commit);
|
|
470
|
+
}
|
|
471
|
+
const reused = selected.length - fresh.length;
|
|
472
|
+
if (reused > 0) {
|
|
473
|
+
log("fetch", `commit history · reused ${reused} cached commits · ${selected.length} total`);
|
|
474
|
+
}
|
|
475
|
+
return selected;
|
|
417
476
|
}
|
|
418
477
|
export async function collectSource(client, options, previous, progress, codebaseProgress) {
|
|
419
478
|
const phase = { text: "reading repository metadata" };
|
|
@@ -426,16 +485,17 @@ export async function collectSource(client, options, previous, progress, codebas
|
|
|
426
485
|
: { tree: [], treeSha: {}, files: {} };
|
|
427
486
|
const pullRequestData = options.includePullRequests
|
|
428
487
|
? await pullRequests(client, options, previous, phase, progress)
|
|
429
|
-
: [];
|
|
488
|
+
: { kept: [], cache: previous?.source.pullRequestCache ?? [] };
|
|
430
489
|
const commitData = options.includeCommitHistory
|
|
431
|
-
? await commits(client, options, repo, phase)
|
|
490
|
+
? await commits(client, options, repo, previous, phase)
|
|
432
491
|
: [];
|
|
433
492
|
return {
|
|
434
493
|
repo,
|
|
435
494
|
tree: files.tree,
|
|
436
495
|
treeSha: files.treeSha,
|
|
437
496
|
files: files.files,
|
|
438
|
-
pullRequests: pullRequestData,
|
|
497
|
+
pullRequests: pullRequestData.kept,
|
|
498
|
+
pullRequestCache: pullRequestData.cache,
|
|
439
499
|
commits: commitData,
|
|
440
500
|
};
|
|
441
501
|
}
|
|
@@ -23,7 +23,11 @@ export type Source = {
|
|
|
23
23
|
tree: string[];
|
|
24
24
|
treeSha: Record<string, string>;
|
|
25
25
|
files: Record<string, string>;
|
|
26
|
+
/** Pull requests selected for facts and the skill. */
|
|
26
27
|
pullRequests: PullRequest[];
|
|
28
|
+
/** Every pull request already decided, including ones left out of the skill.
|
|
29
|
+
* The next fetch reads this so a `changesRequested: false` result is not downloaded again. */
|
|
30
|
+
pullRequestCache?: PullRequest[];
|
|
27
31
|
commits: Json[];
|
|
28
32
|
};
|
|
29
33
|
export type Fact = {
|
|
@@ -15,6 +15,7 @@ import { readState, writeState } from "../store/skill_state.js";
|
|
|
15
15
|
import { cacheSet } from "../store/cache_db.js";
|
|
16
16
|
import { log, timed, withLogSink } from "../util/log.js";
|
|
17
17
|
import { enqueue, registerHandler } from "./jobs.js";
|
|
18
|
+
import { closeAppDb, isAppDbOpen, openAppDb } from "../store/app_db.js";
|
|
18
19
|
import { getRepo, markKnowledgeBuilt } from "../store/repos.js";
|
|
19
20
|
import { nowIso } from "../util/time.js";
|
|
20
21
|
import { mkdir, readTextFile, remove } from "../util/runtime.js";
|
|
@@ -116,6 +117,18 @@ function addReviewLink(markdown, documents) {
|
|
|
116
117
|
return `${markdown.trimEnd()}\n\n## Pull request review guides\n\n- Read [PR_REVIEW_GUIDE.md](PR_REVIEW_GUIDE.md).${detailedLink}\n`;
|
|
117
118
|
}
|
|
118
119
|
export async function runInitOrRemake(options) {
|
|
120
|
+
const openedAppDb = !isAppDbOpen();
|
|
121
|
+
if (openedAppDb)
|
|
122
|
+
await openAppDb();
|
|
123
|
+
try {
|
|
124
|
+
await initOrRemake(options);
|
|
125
|
+
}
|
|
126
|
+
finally {
|
|
127
|
+
if (openedAppDb)
|
|
128
|
+
await closeAppDb();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async function initOrRemake(options) {
|
|
119
132
|
const operationStarted = performance.now();
|
|
120
133
|
const aiMetrics = emptyAiMetrics();
|
|
121
134
|
const previous = await readState(options.repo);
|
|
@@ -136,8 +149,9 @@ export async function runInitOrRemake(options) {
|
|
|
136
149
|
log("ai", `${options.ai} providers configured · low=${options.lowModel} · high=${options.highModel}`);
|
|
137
150
|
}
|
|
138
151
|
log("fetch", `${options.repo} via ${options.auth}`);
|
|
139
|
-
const source = await timed("fetch repository data", options.logTime, () => collectSource(client, options, previous, async (pullRequests) => {
|
|
152
|
+
const source = await timed("fetch repository data", options.logTime, () => collectSource(client, options, previous, async (pullRequests, cache) => {
|
|
140
153
|
checkpoint.source.pullRequests = pullRequests;
|
|
154
|
+
checkpoint.source.pullRequestCache = cache;
|
|
141
155
|
checkpoint.scanDone = {
|
|
142
156
|
...checkpoint.scanDone,
|
|
143
157
|
pullRequests: pullRequests.length,
|
|
@@ -3,8 +3,10 @@ export declare function appDbDir(): string;
|
|
|
3
3
|
/** `CM_APP_DB` overrides the path — tests point it at a temp file. */
|
|
4
4
|
export declare function appDbPath(): string;
|
|
5
5
|
export declare function releaseLock(): Promise<void>;
|
|
6
|
+
export declare function isAppDbOpen(): boolean;
|
|
6
7
|
/** Opens (creating and migrating if needed) the single shared `app.db`
|
|
7
|
-
* connection every store module reuses.
|
|
8
|
+
* connection every store module reuses. `serve` opens it for the process.
|
|
9
|
+
* `init` and `remake` open it for the run when it is not already open. */
|
|
8
10
|
export declare function openAppDb(): Promise<Database>;
|
|
9
11
|
export declare function getAppDb(): Database;
|
|
10
12
|
export declare function closeAppDb(): Promise<void>;
|
package/dist/src/store/app_db.js
CHANGED
|
@@ -59,8 +59,12 @@ function migrate(database) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
let db;
|
|
62
|
+
export function isAppDbOpen() {
|
|
63
|
+
return db !== undefined;
|
|
64
|
+
}
|
|
62
65
|
/** Opens (creating and migrating if needed) the single shared `app.db`
|
|
63
|
-
* connection every store module reuses.
|
|
66
|
+
* connection every store module reuses. `serve` opens it for the process.
|
|
67
|
+
* `init` and `remake` open it for the run when it is not already open. */
|
|
64
68
|
export async function openAppDb() {
|
|
65
69
|
if (db)
|
|
66
70
|
return db;
|