@oss-autopilot/core 3.0.1 → 3.2.0
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/cli-registry.js +80 -0
- package/dist/cli.bundle.cjs +67 -65
- package/dist/commands/guidelines.d.ts +67 -0
- package/dist/commands/guidelines.js +147 -0
- package/dist/commands/index.d.ts +9 -0
- package/dist/commands/index.js +9 -0
- package/dist/commands/scout-bridge.d.ts +12 -1
- package/dist/commands/scout-bridge.js +19 -0
- package/dist/commands/vet-list.js +10 -2
- package/dist/commands/vet.js +10 -1
- package/dist/core/gist-state-store.d.ts +1 -1
- package/dist/core/gist-state-store.js +8 -2
- package/dist/core/guidelines-store.d.ts +74 -0
- package/dist/core/guidelines-store.js +130 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/core/pr-comments-fetcher.d.ts +67 -0
- package/dist/core/pr-comments-fetcher.js +125 -0
- package/dist/core/state-persistence.d.ts +6 -0
- package/dist/core/state-persistence.js +20 -2
- package/dist/core/state-schema.d.ts +9 -1
- package/dist/core/state-schema.js +20 -1
- package/dist/core/state.d.ts +33 -0
- package/dist/core/state.js +70 -0
- package/dist/core/types.d.ts +1 -1
- package/dist/core/types.js +2 -2
- package/dist/formatters/json.d.ts +28 -0
- package/package.json +7 -7
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetch the raw review-comment bundle for a PR (#867 PR 3).
|
|
3
|
+
*
|
|
4
|
+
* Returns reviews, inline review comments, and issue-level comments for a
|
|
5
|
+
* single PR with the contributor's own comments + bots filtered out. The
|
|
6
|
+
* `authorAssociation` field is preserved on every entry so the host's
|
|
7
|
+
* extraction prompt can weight maintainer voices (OWNER/MEMBER/COLLABORATOR)
|
|
8
|
+
* differently from community feedback (CONTRIBUTOR/NONE).
|
|
9
|
+
*
|
|
10
|
+
* No LLM calls happen here — this is the data layer feeding the host's
|
|
11
|
+
* `extract-learnings` prompt. The bundle structure is the contract; the
|
|
12
|
+
* extraction is the host's responsibility.
|
|
13
|
+
*/
|
|
14
|
+
import type { Octokit } from '@octokit/rest';
|
|
15
|
+
/** A single review (top-level) on a PR. */
|
|
16
|
+
export interface PRReviewEntry {
|
|
17
|
+
author: string;
|
|
18
|
+
authorAssociation: string;
|
|
19
|
+
body: string;
|
|
20
|
+
submittedAt: string;
|
|
21
|
+
}
|
|
22
|
+
/** An inline review comment (anchored to a file/line) on a PR. */
|
|
23
|
+
export interface PRReviewCommentEntry {
|
|
24
|
+
author: string;
|
|
25
|
+
authorAssociation: string;
|
|
26
|
+
body: string;
|
|
27
|
+
path: string;
|
|
28
|
+
createdAt: string;
|
|
29
|
+
}
|
|
30
|
+
/** An issue-level comment posted on the PR thread. */
|
|
31
|
+
export interface PRIssueCommentEntry {
|
|
32
|
+
author: string;
|
|
33
|
+
authorAssociation: string;
|
|
34
|
+
body: string;
|
|
35
|
+
createdAt: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The full comment bundle returned for a single PR. Field order matches
|
|
39
|
+
* the typical narrative arc of a PR review (top-level reviews → inline
|
|
40
|
+
* comments → general thread chatter), so the host's extraction prompt can
|
|
41
|
+
* walk the bundle linearly.
|
|
42
|
+
*/
|
|
43
|
+
export interface PRCommentBundle {
|
|
44
|
+
prUrl: string;
|
|
45
|
+
prTitle: string;
|
|
46
|
+
repo: string;
|
|
47
|
+
/** ISO-8601 timestamp the PR was merged or closed; whichever applies. */
|
|
48
|
+
mergedAt: string;
|
|
49
|
+
reviews: PRReviewEntry[];
|
|
50
|
+
reviewComments: PRReviewCommentEntry[];
|
|
51
|
+
issueComments: PRIssueCommentEntry[];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Fetch a single PR's comment bundle. Filters out the authenticated user's
|
|
55
|
+
* own comments and bots. Throws {@link ValidationError} on a non-PR URL.
|
|
56
|
+
*/
|
|
57
|
+
export declare function fetchPRCommentBundle(octokit: Octokit, prUrl: string, githubUsername: string): Promise<PRCommentBundle>;
|
|
58
|
+
/**
|
|
59
|
+
* Fetch comment bundles for many PRs with a small concurrency cap (default 3).
|
|
60
|
+
*
|
|
61
|
+
* Failures on individual PRs are logged and skipped — the batch returns a
|
|
62
|
+
* shorter array rather than aborting. Rationale: extraction quality is
|
|
63
|
+
* already a partial-information problem (users contribute to many repos and
|
|
64
|
+
* many PRs), so a single 404 / rate limit on one PR should not deny the
|
|
65
|
+
* host the corpus from the other 4.
|
|
66
|
+
*/
|
|
67
|
+
export declare function fetchPRCommentBundlesBatch(octokit: Octokit, prUrls: string[], githubUsername: string, concurrency?: number): Promise<PRCommentBundle[]>;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { paginateAll } from './pagination.js';
|
|
2
|
+
import { isBotAuthor } from './comment-utils.js';
|
|
3
|
+
import { parseGitHubUrl } from './urls.js';
|
|
4
|
+
import { ValidationError, errorMessage } from './errors.js';
|
|
5
|
+
import { debug, warn } from './logger.js';
|
|
6
|
+
const MODULE = 'pr-comments-fetcher';
|
|
7
|
+
/** Default concurrency for {@link fetchPRCommentBundlesBatch}. */
|
|
8
|
+
const DEFAULT_BATCH_CONCURRENCY = 3;
|
|
9
|
+
/**
|
|
10
|
+
* Fetch a single PR's comment bundle. Filters out the authenticated user's
|
|
11
|
+
* own comments and bots. Throws {@link ValidationError} on a non-PR URL.
|
|
12
|
+
*/
|
|
13
|
+
export async function fetchPRCommentBundle(octokit, prUrl, githubUsername) {
|
|
14
|
+
const parsed = parseGitHubUrl(prUrl);
|
|
15
|
+
if (!parsed || parsed.type !== 'pull') {
|
|
16
|
+
throw new ValidationError(`Invalid PR URL: ${prUrl}`);
|
|
17
|
+
}
|
|
18
|
+
const { owner, repo, number: pull_number } = parsed;
|
|
19
|
+
const repoFull = `${owner}/${repo}`;
|
|
20
|
+
// Fetch the PR + all three comment streams in parallel. We always fetch
|
|
21
|
+
// every page — corpus quality depends on having every reviewer voice, not
|
|
22
|
+
// just the first 100 comments.
|
|
23
|
+
const [{ data: pr }, reviews, reviewComments, issueComments] = await Promise.all([
|
|
24
|
+
octokit.pulls.get({ owner, repo, pull_number }),
|
|
25
|
+
paginateAll((page) => octokit.pulls.listReviews({
|
|
26
|
+
owner,
|
|
27
|
+
repo,
|
|
28
|
+
pull_number,
|
|
29
|
+
per_page: 100,
|
|
30
|
+
page,
|
|
31
|
+
})),
|
|
32
|
+
paginateAll((page) => octokit.pulls.listReviewComments({
|
|
33
|
+
owner,
|
|
34
|
+
repo,
|
|
35
|
+
pull_number,
|
|
36
|
+
per_page: 100,
|
|
37
|
+
page,
|
|
38
|
+
})),
|
|
39
|
+
paginateAll((page) => octokit.issues.listComments({
|
|
40
|
+
owner,
|
|
41
|
+
repo,
|
|
42
|
+
issue_number: pull_number,
|
|
43
|
+
per_page: 100,
|
|
44
|
+
page,
|
|
45
|
+
})),
|
|
46
|
+
]);
|
|
47
|
+
const ownLogin = githubUsername.toLowerCase();
|
|
48
|
+
/**
|
|
49
|
+
* Drop entries that aren't useful corpus: the user's own comments, bots,
|
|
50
|
+
* and entries with no author at all (deleted accounts surface as null
|
|
51
|
+
* user from GitHub's REST API).
|
|
52
|
+
*/
|
|
53
|
+
const isWorthKeeping = (login) => {
|
|
54
|
+
if (!login)
|
|
55
|
+
return false;
|
|
56
|
+
if (login.toLowerCase() === ownLogin)
|
|
57
|
+
return false;
|
|
58
|
+
if (isBotAuthor(login))
|
|
59
|
+
return false;
|
|
60
|
+
return true;
|
|
61
|
+
};
|
|
62
|
+
const mergedAt = pr.merged_at ?? pr.closed_at ?? '';
|
|
63
|
+
return {
|
|
64
|
+
prUrl,
|
|
65
|
+
prTitle: pr.title,
|
|
66
|
+
repo: repoFull,
|
|
67
|
+
mergedAt,
|
|
68
|
+
reviews: reviews
|
|
69
|
+
.filter((r) => isWorthKeeping(r.user?.login))
|
|
70
|
+
.map((r) => ({
|
|
71
|
+
author: r.user?.login ?? '',
|
|
72
|
+
authorAssociation: r.author_association ?? 'NONE',
|
|
73
|
+
body: r.body ?? '',
|
|
74
|
+
submittedAt: r.submitted_at ?? '',
|
|
75
|
+
})),
|
|
76
|
+
reviewComments: reviewComments
|
|
77
|
+
.filter((c) => isWorthKeeping(c.user?.login))
|
|
78
|
+
.map((c) => ({
|
|
79
|
+
author: c.user?.login ?? '',
|
|
80
|
+
authorAssociation: c.author_association ?? 'NONE',
|
|
81
|
+
body: c.body ?? '',
|
|
82
|
+
path: c.path ?? '',
|
|
83
|
+
createdAt: c.created_at ?? '',
|
|
84
|
+
})),
|
|
85
|
+
issueComments: issueComments
|
|
86
|
+
.filter((c) => isWorthKeeping(c.user?.login))
|
|
87
|
+
.map((c) => ({
|
|
88
|
+
author: c.user?.login ?? '',
|
|
89
|
+
authorAssociation: c.author_association ?? 'NONE',
|
|
90
|
+
body: c.body ?? '',
|
|
91
|
+
createdAt: c.created_at ?? '',
|
|
92
|
+
})),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Fetch comment bundles for many PRs with a small concurrency cap (default 3).
|
|
97
|
+
*
|
|
98
|
+
* Failures on individual PRs are logged and skipped — the batch returns a
|
|
99
|
+
* shorter array rather than aborting. Rationale: extraction quality is
|
|
100
|
+
* already a partial-information problem (users contribute to many repos and
|
|
101
|
+
* many PRs), so a single 404 / rate limit on one PR should not deny the
|
|
102
|
+
* host the corpus from the other 4.
|
|
103
|
+
*/
|
|
104
|
+
export async function fetchPRCommentBundlesBatch(octokit, prUrls, githubUsername, concurrency = DEFAULT_BATCH_CONCURRENCY) {
|
|
105
|
+
const results = [];
|
|
106
|
+
const queue = [...prUrls];
|
|
107
|
+
async function worker() {
|
|
108
|
+
while (queue.length > 0) {
|
|
109
|
+
const url = queue.shift();
|
|
110
|
+
if (!url)
|
|
111
|
+
return;
|
|
112
|
+
try {
|
|
113
|
+
const bundle = await fetchPRCommentBundle(octokit, url, githubUsername);
|
|
114
|
+
results.push(bundle);
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
warn(MODULE, `Skipping ${url}: ${errorMessage(err)}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const workers = Array.from({ length: Math.min(concurrency, prUrls.length) }, worker);
|
|
122
|
+
await Promise.all(workers);
|
|
123
|
+
debug(MODULE, `Fetched ${results.length}/${prUrls.length} comment bundles`);
|
|
124
|
+
return results;
|
|
125
|
+
}
|
|
@@ -34,6 +34,12 @@ export declare function migrateV1ToV2(rawState: Record<string, unknown>): Record
|
|
|
34
34
|
* New optional fields are handled by Zod defaults (undefined/optional).
|
|
35
35
|
*/
|
|
36
36
|
export declare function migrateV2ToV3(rawState: Record<string, unknown>): Record<string, unknown>;
|
|
37
|
+
/**
|
|
38
|
+
* Migrate state from v3 to v4 (#867).
|
|
39
|
+
* Adds: commentsFetchedAt on StoredMergedPR / StoredClosedPR. The new field is
|
|
40
|
+
* optional, so no data transformation is needed — only the version bump.
|
|
41
|
+
*/
|
|
42
|
+
export declare function migrateV3ToV4(rawState: Record<string, unknown>): Record<string, unknown>;
|
|
37
43
|
/**
|
|
38
44
|
* Create a fresh state (v3).
|
|
39
45
|
* Leverages Zod schema defaults to produce a complete state.
|
|
@@ -158,12 +158,23 @@ export function migrateV2ToV3(rawState) {
|
|
|
158
158
|
debug(MODULE, 'v2 to v3 migration complete.');
|
|
159
159
|
return rawState;
|
|
160
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Migrate state from v3 to v4 (#867).
|
|
163
|
+
* Adds: commentsFetchedAt on StoredMergedPR / StoredClosedPR. The new field is
|
|
164
|
+
* optional, so no data transformation is needed — only the version bump.
|
|
165
|
+
*/
|
|
166
|
+
export function migrateV3ToV4(rawState) {
|
|
167
|
+
debug(MODULE, 'Migrating state from v3 to v4 (add commentsFetchedAt to stored PR records)...');
|
|
168
|
+
rawState.version = 4;
|
|
169
|
+
debug(MODULE, 'v3 to v4 migration complete (no data transformation required).');
|
|
170
|
+
return rawState;
|
|
171
|
+
}
|
|
161
172
|
/**
|
|
162
173
|
* Create a fresh state (v3).
|
|
163
174
|
* Leverages Zod schema defaults to produce a complete state.
|
|
164
175
|
*/
|
|
165
176
|
export function createFreshState() {
|
|
166
|
-
return AgentStateSchema.parse({ version:
|
|
177
|
+
return AgentStateSchema.parse({ version: 4 });
|
|
167
178
|
}
|
|
168
179
|
/**
|
|
169
180
|
* Migrate state from legacy ./data/ location to ~/.oss-autopilot/.
|
|
@@ -273,6 +284,9 @@ function tryRestoreFromBackup() {
|
|
|
273
284
|
if (raw.version === 2) {
|
|
274
285
|
raw = migrateV2ToV3(raw);
|
|
275
286
|
}
|
|
287
|
+
if (raw.version === 3) {
|
|
288
|
+
raw = migrateV3ToV4(raw);
|
|
289
|
+
}
|
|
276
290
|
}
|
|
277
291
|
const parsed = AgentStateSchema.safeParse(raw);
|
|
278
292
|
if (parsed.success) {
|
|
@@ -313,7 +327,7 @@ export function loadState() {
|
|
|
313
327
|
if (fs.existsSync(statePath)) {
|
|
314
328
|
const data = fs.readFileSync(statePath, 'utf-8');
|
|
315
329
|
let raw = JSON.parse(data);
|
|
316
|
-
// Chain migrations: v1 → v2 → v3
|
|
330
|
+
// Chain migrations: v1 → v2 → v3 → v4
|
|
317
331
|
let wasMigrated = false;
|
|
318
332
|
if (typeof raw === 'object' && raw !== null) {
|
|
319
333
|
const rawObj = raw;
|
|
@@ -325,6 +339,10 @@ export function loadState() {
|
|
|
325
339
|
raw = migrateV2ToV3(raw);
|
|
326
340
|
wasMigrated = true;
|
|
327
341
|
}
|
|
342
|
+
if (raw.version === 3) {
|
|
343
|
+
raw = migrateV3ToV4(raw);
|
|
344
|
+
wasMigrated = true;
|
|
345
|
+
}
|
|
328
346
|
}
|
|
329
347
|
// Validate through Zod schema (strips unknown keys in memory; stale keys persist on disk until next save)
|
|
330
348
|
const parsed = AgentStateSchema.safeParse(raw);
|
|
@@ -63,12 +63,14 @@ export declare const StoredMergedPRSchema: z.ZodObject<{
|
|
|
63
63
|
url: z.ZodString;
|
|
64
64
|
title: z.ZodString;
|
|
65
65
|
mergedAt: z.ZodString;
|
|
66
|
+
commentsFetchedAt: z.ZodOptional<z.ZodString>;
|
|
66
67
|
learningsExtractedAt: z.ZodOptional<z.ZodString>;
|
|
67
68
|
}, z.core.$strip>;
|
|
68
69
|
export declare const StoredClosedPRSchema: z.ZodObject<{
|
|
69
70
|
url: z.ZodString;
|
|
70
71
|
title: z.ZodString;
|
|
71
72
|
closedAt: z.ZodString;
|
|
73
|
+
commentsFetchedAt: z.ZodOptional<z.ZodString>;
|
|
72
74
|
learningsExtractedAt: z.ZodOptional<z.ZodString>;
|
|
73
75
|
}, z.core.$strip>;
|
|
74
76
|
export declare const AnalyzedIssueConversationSchema: z.ZodObject<{
|
|
@@ -241,6 +243,8 @@ export declare const AgentConfigSchema: z.ZodObject<{
|
|
|
241
243
|
}>>;
|
|
242
244
|
diffToolCustomCommand: z.ZodOptional<z.ZodString>;
|
|
243
245
|
autoFormatBeforePush: z.ZodDefault<z.ZodBoolean>;
|
|
246
|
+
slmTriageModel: z.ZodDefault<z.ZodString>;
|
|
247
|
+
slmTriageHost: z.ZodDefault<z.ZodString>;
|
|
244
248
|
}, z.core.$strip>;
|
|
245
249
|
export declare const LocalRepoCacheSchema: z.ZodObject<{
|
|
246
250
|
repos: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
@@ -322,7 +326,7 @@ export declare const DailyDigestSchema: z.ZodObject<{
|
|
|
322
326
|
}, z.core.$strip>;
|
|
323
327
|
}, z.core.$strip>;
|
|
324
328
|
export declare const AgentStateSchema: z.ZodObject<{
|
|
325
|
-
version: z.ZodLiteral<
|
|
329
|
+
version: z.ZodLiteral<4>;
|
|
326
330
|
gistId: z.ZodOptional<z.ZodString>;
|
|
327
331
|
repoScores: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
328
332
|
repo: z.ZodString;
|
|
@@ -399,6 +403,8 @@ export declare const AgentStateSchema: z.ZodObject<{
|
|
|
399
403
|
}>>;
|
|
400
404
|
diffToolCustomCommand: z.ZodOptional<z.ZodString>;
|
|
401
405
|
autoFormatBeforePush: z.ZodDefault<z.ZodBoolean>;
|
|
406
|
+
slmTriageModel: z.ZodDefault<z.ZodString>;
|
|
407
|
+
slmTriageHost: z.ZodDefault<z.ZodString>;
|
|
402
408
|
}, z.core.$strip>>;
|
|
403
409
|
lastRunAt: z.ZodDefault<z.ZodString>;
|
|
404
410
|
lastDigestAt: z.ZodOptional<z.ZodString>;
|
|
@@ -467,12 +473,14 @@ export declare const AgentStateSchema: z.ZodObject<{
|
|
|
467
473
|
url: z.ZodString;
|
|
468
474
|
title: z.ZodString;
|
|
469
475
|
mergedAt: z.ZodString;
|
|
476
|
+
commentsFetchedAt: z.ZodOptional<z.ZodString>;
|
|
470
477
|
learningsExtractedAt: z.ZodOptional<z.ZodString>;
|
|
471
478
|
}, z.core.$strip>>>;
|
|
472
479
|
closedPRs: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
473
480
|
url: z.ZodString;
|
|
474
481
|
title: z.ZodString;
|
|
475
482
|
closedAt: z.ZodString;
|
|
483
|
+
commentsFetchedAt: z.ZodOptional<z.ZodString>;
|
|
476
484
|
learningsExtractedAt: z.ZodOptional<z.ZodString>;
|
|
477
485
|
}, z.core.$strip>>>;
|
|
478
486
|
analyzedIssueConversations: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
@@ -44,12 +44,18 @@ export const StoredMergedPRSchema = z.object({
|
|
|
44
44
|
url: z.string(),
|
|
45
45
|
title: z.string(),
|
|
46
46
|
mergedAt: z.string(),
|
|
47
|
+
/** When the raw review-comment bundle for this PR was last fetched (#867). */
|
|
48
|
+
commentsFetchedAt: z.string().optional(),
|
|
49
|
+
/** When the host last ran LLM extraction over this PR's comment bundle (#867). */
|
|
47
50
|
learningsExtractedAt: z.string().optional(),
|
|
48
51
|
});
|
|
49
52
|
export const StoredClosedPRSchema = z.object({
|
|
50
53
|
url: z.string(),
|
|
51
54
|
title: z.string(),
|
|
52
55
|
closedAt: z.string(),
|
|
56
|
+
/** When the raw review-comment bundle for this PR was last fetched (#867). */
|
|
57
|
+
commentsFetchedAt: z.string().optional(),
|
|
58
|
+
/** When the host last ran LLM extraction over this PR's comment bundle (#867). */
|
|
53
59
|
learningsExtractedAt: z.string().optional(),
|
|
54
60
|
});
|
|
55
61
|
export const AnalyzedIssueConversationSchema = z.object({
|
|
@@ -151,6 +157,19 @@ export const AgentConfigSchema = z.object({
|
|
|
151
157
|
* the hook does nothing on every push unless the user explicitly enables it.
|
|
152
158
|
*/
|
|
153
159
|
autoFormatBeforePush: z.boolean().default(false),
|
|
160
|
+
/**
|
|
161
|
+
* Optional Ollama model for SLM pre-triage during issue vetting (#1122).
|
|
162
|
+
* Empty disables the feature. Recommended: `gemma4:e4b` (default for
|
|
163
|
+
* capable hardware), `gemma4:e2b` or `qwen3:1.7b` for low-RAM machines.
|
|
164
|
+
* Threaded through to scout via the bridge in `scout-bridge.ts`.
|
|
165
|
+
*/
|
|
166
|
+
slmTriageModel: z.string().default(''),
|
|
167
|
+
/**
|
|
168
|
+
* Optional Ollama HTTP host override. Defaults to `http://127.0.0.1:11434`
|
|
169
|
+
* when empty. Useful when Ollama runs on a different machine on the
|
|
170
|
+
* local network.
|
|
171
|
+
*/
|
|
172
|
+
slmTriageHost: z.string().default(''),
|
|
154
173
|
});
|
|
155
174
|
// ── 6. Cache schemas ─────────────────────────────────────────────────
|
|
156
175
|
export const LocalRepoCacheSchema = z.object({
|
|
@@ -198,7 +217,7 @@ export const DailyDigestSchema = z.object({
|
|
|
198
217
|
});
|
|
199
218
|
// ── 8. Root schema ───────────────────────────────────────────────────
|
|
200
219
|
export const AgentStateSchema = z.object({
|
|
201
|
-
version: z.literal(
|
|
220
|
+
version: z.literal(4),
|
|
202
221
|
gistId: z.string().optional(),
|
|
203
222
|
repoScores: z.record(z.string(), RepoScoreSchema).default({}),
|
|
204
223
|
config: AgentConfigSchema.default(() => AgentConfigSchema.parse({})),
|
package/dist/core/state.d.ts
CHANGED
|
@@ -105,6 +105,29 @@ export declare class StateManager {
|
|
|
105
105
|
isGistMode(): boolean;
|
|
106
106
|
/** Whether the Gist is in degraded mode (using local cache fallback). */
|
|
107
107
|
isGistDegraded(): boolean;
|
|
108
|
+
/**
|
|
109
|
+
* Whether per-repo guidelines (#867) are available. True iff the Gist store
|
|
110
|
+
* is initialized — in local-only mode, guidelines are unavailable and
|
|
111
|
+
* write operations would throw {@link GuidelinesNotAvailableError}.
|
|
112
|
+
*/
|
|
113
|
+
isGuidelinesAvailable(): boolean;
|
|
114
|
+
/**
|
|
115
|
+
* Read the per-repo guidelines for `repo` (#867). Returns null when in
|
|
116
|
+
* local mode, when no file exists, or when the file is empty (tombstoned).
|
|
117
|
+
*/
|
|
118
|
+
getGuidelines(repo: string): string | null;
|
|
119
|
+
/**
|
|
120
|
+
* Persist per-repo guidelines for `repo`. Throws when not in Gist mode or
|
|
121
|
+
* when content exceeds the byte budget.
|
|
122
|
+
*/
|
|
123
|
+
setGuidelines(repo: string, content: string): void;
|
|
124
|
+
/**
|
|
125
|
+
* Tombstone the guidelines file for `repo` so subsequent reads return null.
|
|
126
|
+
* Throws when not in Gist mode.
|
|
127
|
+
*/
|
|
128
|
+
deleteGuidelines(repo: string): void;
|
|
129
|
+
/** List repos with non-empty guidelines stored in the Gist. */
|
|
130
|
+
listGuidelinesRepos(): string[];
|
|
108
131
|
/**
|
|
109
132
|
* Get the current state as a read-only snapshot.
|
|
110
133
|
*/
|
|
@@ -176,6 +199,16 @@ export declare class StateManager {
|
|
|
176
199
|
};
|
|
177
200
|
/** Returns the most recent close date, used as a watermark for incremental fetching. */
|
|
178
201
|
getClosedPRWatermark(): string | undefined;
|
|
202
|
+
/**
|
|
203
|
+
* Stamp `commentsFetchedAt` on the merged or closed PR matching `url` (#867).
|
|
204
|
+
* No-op when no PR with that URL is stored.
|
|
205
|
+
*/
|
|
206
|
+
markPRCommentsFetched(url: string, fetchedAt: string): void;
|
|
207
|
+
/**
|
|
208
|
+
* Stamp `learningsExtractedAt` on the merged or closed PR matching `url` (#867).
|
|
209
|
+
* No-op when no PR with that URL is stored.
|
|
210
|
+
*/
|
|
211
|
+
markPRLearningsExtracted(url: string, extractedAt: string): void;
|
|
179
212
|
/**
|
|
180
213
|
* Merge partial config updates into the current configuration.
|
|
181
214
|
* @param config - Partial config object to merge
|
package/dist/core/state.js
CHANGED
|
@@ -10,6 +10,7 @@ import * as repoScoring from './repo-score-manager.js';
|
|
|
10
10
|
import { debug, warn } from './logger.js';
|
|
11
11
|
import { errorMessage, ConfigurationError, ConcurrencyError } from './errors.js';
|
|
12
12
|
import { GistStateStore } from './gist-state-store.js';
|
|
13
|
+
import * as guidelinesStoreModule from './guidelines-store.js';
|
|
13
14
|
import { getStatePath, getStateCachePath } from './paths.js';
|
|
14
15
|
import { parseGitHubUrl } from './urls.js';
|
|
15
16
|
export { acquireLock, releaseLock, atomicWriteFileSync } from './state-persistence.js';
|
|
@@ -274,6 +275,41 @@ export class StateManager {
|
|
|
274
275
|
isGistDegraded() {
|
|
275
276
|
return this.gistDegraded;
|
|
276
277
|
}
|
|
278
|
+
/**
|
|
279
|
+
* Whether per-repo guidelines (#867) are available. True iff the Gist store
|
|
280
|
+
* is initialized — in local-only mode, guidelines are unavailable and
|
|
281
|
+
* write operations would throw {@link GuidelinesNotAvailableError}.
|
|
282
|
+
*/
|
|
283
|
+
isGuidelinesAvailable() {
|
|
284
|
+
return this.gistStore !== null;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Read the per-repo guidelines for `repo` (#867). Returns null when in
|
|
288
|
+
* local mode, when no file exists, or when the file is empty (tombstoned).
|
|
289
|
+
*/
|
|
290
|
+
getGuidelines(repo) {
|
|
291
|
+
return guidelinesStoreModule.getGuidelines(this.gistStore, repo);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Persist per-repo guidelines for `repo`. Throws when not in Gist mode or
|
|
295
|
+
* when content exceeds the byte budget.
|
|
296
|
+
*/
|
|
297
|
+
setGuidelines(repo, content) {
|
|
298
|
+
guidelinesStoreModule.setGuidelines(this.gistStore, repo, content);
|
|
299
|
+
this.autoSave();
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Tombstone the guidelines file for `repo` so subsequent reads return null.
|
|
303
|
+
* Throws when not in Gist mode.
|
|
304
|
+
*/
|
|
305
|
+
deleteGuidelines(repo) {
|
|
306
|
+
guidelinesStoreModule.deleteGuidelines(this.gistStore, repo);
|
|
307
|
+
this.autoSave();
|
|
308
|
+
}
|
|
309
|
+
/** List repos with non-empty guidelines stored in the Gist. */
|
|
310
|
+
listGuidelinesRepos() {
|
|
311
|
+
return guidelinesStoreModule.listGuidelinesRepos(this.gistStore);
|
|
312
|
+
}
|
|
277
313
|
/**
|
|
278
314
|
* Get the current state as a read-only snapshot.
|
|
279
315
|
*/
|
|
@@ -434,6 +470,40 @@ export class StateManager {
|
|
|
434
470
|
getClosedPRWatermark() {
|
|
435
471
|
return this.state.closedPRs?.[0]?.closedAt || undefined;
|
|
436
472
|
}
|
|
473
|
+
/**
|
|
474
|
+
* Stamp `commentsFetchedAt` on the merged or closed PR matching `url` (#867).
|
|
475
|
+
* No-op when no PR with that URL is stored.
|
|
476
|
+
*/
|
|
477
|
+
markPRCommentsFetched(url, fetchedAt) {
|
|
478
|
+
const merged = this.state.mergedPRs?.find((pr) => pr.url === url);
|
|
479
|
+
if (merged) {
|
|
480
|
+
merged.commentsFetchedAt = fetchedAt;
|
|
481
|
+
this.autoSave();
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
const closed = this.state.closedPRs?.find((pr) => pr.url === url);
|
|
485
|
+
if (closed) {
|
|
486
|
+
closed.commentsFetchedAt = fetchedAt;
|
|
487
|
+
this.autoSave();
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Stamp `learningsExtractedAt` on the merged or closed PR matching `url` (#867).
|
|
492
|
+
* No-op when no PR with that URL is stored.
|
|
493
|
+
*/
|
|
494
|
+
markPRLearningsExtracted(url, extractedAt) {
|
|
495
|
+
const merged = this.state.mergedPRs?.find((pr) => pr.url === url);
|
|
496
|
+
if (merged) {
|
|
497
|
+
merged.learningsExtractedAt = extractedAt;
|
|
498
|
+
this.autoSave();
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
const closed = this.state.closedPRs?.find((pr) => pr.url === url);
|
|
502
|
+
if (closed) {
|
|
503
|
+
closed.learningsExtractedAt = extractedAt;
|
|
504
|
+
this.autoSave();
|
|
505
|
+
}
|
|
506
|
+
}
|
|
437
507
|
// === Configuration ===
|
|
438
508
|
/**
|
|
439
509
|
* Merge partial config updates into the current configuration.
|
package/dist/core/types.d.ts
CHANGED
|
@@ -233,7 +233,7 @@ interface CommentedIssueWithoutResponse extends CommentedIssueBase {
|
|
|
233
233
|
export type CommentedIssue = CommentedIssueWithResponse | CommentedIssueWithoutResponse;
|
|
234
234
|
/** Default configuration applied to new state files. All fields can be overridden via `/setup-oss`. */
|
|
235
235
|
export declare const DEFAULT_CONFIG: AgentConfig;
|
|
236
|
-
/** Initial state written to `~/.oss-autopilot/state.json` on first run. Uses
|
|
236
|
+
/** Initial state written to `~/.oss-autopilot/state.json` on first run. Uses v4 architecture. */
|
|
237
237
|
export declare const INITIAL_STATE: AgentState;
|
|
238
238
|
export declare const PROJECT_CATEGORIES: ("nonprofit" | "devtools" | "infrastructure" | "web-frameworks" | "data-ml" | "education")[];
|
|
239
239
|
export declare const ISSUE_SCOPES: ("advanced" | "beginner" | "intermediate")[];
|
package/dist/core/types.js
CHANGED
|
@@ -12,8 +12,8 @@ export function isBelowMinStars(stargazersCount, minStars) {
|
|
|
12
12
|
// ── Schema-derived constants ─────────────────────────────────────────
|
|
13
13
|
/** Default configuration applied to new state files. All fields can be overridden via `/setup-oss`. */
|
|
14
14
|
export const DEFAULT_CONFIG = AgentConfigSchema.parse({});
|
|
15
|
-
/** Initial state written to `~/.oss-autopilot/state.json` on first run. Uses
|
|
16
|
-
export const INITIAL_STATE = AgentStateSchema.parse({ version:
|
|
15
|
+
/** Initial state written to `~/.oss-autopilot/state.json` on first run. Uses v4 architecture. */
|
|
16
|
+
export const INITIAL_STATE = AgentStateSchema.parse({ version: 4 });
|
|
17
17
|
// ── Const arrays (derived from Zod schemas for runtime iteration) ────
|
|
18
18
|
export const PROJECT_CATEGORIES = ProjectCategorySchema.options;
|
|
19
19
|
export const ISSUE_SCOPES = IssueScopeSchema.options;
|
|
@@ -725,6 +725,34 @@ export interface VetOutput {
|
|
|
725
725
|
reasonsToSkip: string[];
|
|
726
726
|
projectHealth: unknown;
|
|
727
727
|
vettingResult: unknown;
|
|
728
|
+
/**
|
|
729
|
+
* Result of scout's anti-LLM policy scan over CONTRIBUTING.md /
|
|
730
|
+
* CODE_OF_CONDUCT.md / README.md (#979). When `matched` is true the issue
|
|
731
|
+
* should be skipped — the project explicitly disallows AI-generated
|
|
732
|
+
* contributions.
|
|
733
|
+
*/
|
|
734
|
+
antiLLMPolicy?: {
|
|
735
|
+
matched: boolean;
|
|
736
|
+
matchedKeywords: string[];
|
|
737
|
+
sourceFile: string | null;
|
|
738
|
+
};
|
|
739
|
+
/**
|
|
740
|
+
* Classification of the issue's first linked PR (#978). `'none'` when
|
|
741
|
+
* no linked PR exists. The other buckets distinguish whether the user
|
|
742
|
+
* already has work in flight vs. a competing contributor.
|
|
743
|
+
*/
|
|
744
|
+
linkedPRClassification?: 'none' | 'user_open' | 'user_closed' | 'user_merged' | 'other_open' | 'other_closed' | 'other_merged';
|
|
745
|
+
/**
|
|
746
|
+
* Optional SLM pre-triage classification (#1122). Populated when the
|
|
747
|
+
* user has set `slmTriageModel` and a local Ollama instance answered
|
|
748
|
+
* within the timeout. `null` otherwise.
|
|
749
|
+
*/
|
|
750
|
+
slmTriage?: {
|
|
751
|
+
decision: 'pursue' | 'investigate' | 'skip';
|
|
752
|
+
confidence: 'high' | 'medium' | 'low';
|
|
753
|
+
reasons: string[];
|
|
754
|
+
modelVersion: string;
|
|
755
|
+
} | null;
|
|
728
756
|
/** Success-likelihood grade (#858): predicts whether a PR will merge. */
|
|
729
757
|
grade: {
|
|
730
758
|
letter: 'A' | 'B' | 'C' | 'F';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oss-autopilot/core",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "CLI and core library for managing open source contributions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -54,18 +54,18 @@
|
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@octokit/plugin-throttling": "^11.0.3",
|
|
56
56
|
"@octokit/rest": "^22.0.1",
|
|
57
|
-
"@oss-scout/core": "^0.
|
|
57
|
+
"@oss-scout/core": "^0.7.1",
|
|
58
58
|
"commander": "^14.0.3",
|
|
59
59
|
"zod": "^4.3.6"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@types/node": "^25.
|
|
63
|
-
"@vitest/coverage-v8": "^4.1.
|
|
64
|
-
"esbuild": "^0.
|
|
62
|
+
"@types/node": "^25.6.0",
|
|
63
|
+
"@vitest/coverage-v8": "^4.1.5",
|
|
64
|
+
"esbuild": "^0.28.0",
|
|
65
65
|
"tsx": "^4.21.0",
|
|
66
|
-
"typedoc": "^0.28.
|
|
66
|
+
"typedoc": "^0.28.19",
|
|
67
67
|
"typescript": "^5.9.3",
|
|
68
|
-
"vitest": "^4.1.
|
|
68
|
+
"vitest": "^4.1.5"
|
|
69
69
|
},
|
|
70
70
|
"scripts": {
|
|
71
71
|
"build": "tsc",
|