@oss-autopilot/core 0.58.0 → 0.60.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 +54 -0
- package/dist/cli.bundle.cjs +151 -108
- package/dist/commands/comments.d.ts +28 -0
- package/dist/commands/comments.js +28 -0
- package/dist/commands/config.d.ts +11 -0
- package/dist/commands/config.js +11 -0
- package/dist/commands/daily.d.ts +26 -2
- package/dist/commands/daily.js +26 -2
- package/dist/commands/detect-formatters.d.ts +11 -0
- package/dist/commands/detect-formatters.js +24 -0
- package/dist/commands/dismiss.d.ts +17 -0
- package/dist/commands/dismiss.js +17 -0
- package/dist/commands/index.d.ts +3 -1
- package/dist/commands/index.js +2 -0
- package/dist/commands/init.d.ts +8 -0
- package/dist/commands/init.js +8 -0
- package/dist/commands/move.d.ts +10 -0
- package/dist/commands/move.js +10 -0
- package/dist/commands/search.d.ts +18 -0
- package/dist/commands/search.js +18 -0
- package/dist/commands/setup.d.ts +17 -0
- package/dist/commands/setup.js +17 -0
- package/dist/commands/shelve.d.ts +16 -0
- package/dist/commands/shelve.js +16 -0
- package/dist/commands/startup.d.ts +16 -7
- package/dist/commands/startup.js +16 -7
- package/dist/commands/status.d.ts +8 -0
- package/dist/commands/status.js +8 -0
- package/dist/commands/track.d.ts +16 -0
- package/dist/commands/track.js +16 -0
- package/dist/commands/vet.d.ts +8 -0
- package/dist/commands/vet.js +8 -0
- package/dist/core/daily-logic.d.ts +60 -7
- package/dist/core/daily-logic.js +52 -7
- package/dist/core/formatter-detection.d.ts +61 -0
- package/dist/core/formatter-detection.js +360 -0
- package/dist/core/github.d.ts +25 -2
- package/dist/core/github.js +25 -2
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/core/issue-discovery.d.ts +46 -6
- package/dist/core/issue-discovery.js +46 -6
- package/dist/core/logger.d.ts +13 -0
- package/dist/core/logger.js +13 -0
- package/dist/core/pr-monitor.d.ts +43 -8
- package/dist/core/pr-monitor.js +43 -8
- package/dist/core/state-persistence.d.ts +1 -0
- package/dist/core/state-persistence.js +46 -84
- package/dist/core/state-schema.d.ts +539 -0
- package/dist/core/state-schema.js +214 -0
- package/dist/core/state.d.ts +167 -0
- package/dist/core/state.js +167 -0
- package/dist/core/types.d.ts +4 -318
- package/dist/core/types.js +7 -41
- package/dist/formatters/json.d.ts +5 -0
- package/package.json +8 -4
package/dist/core/logger.d.ts
CHANGED
|
@@ -9,18 +9,31 @@ export declare function enableDebug(): void;
|
|
|
9
9
|
export declare function isDebugEnabled(): boolean;
|
|
10
10
|
/**
|
|
11
11
|
* Log a debug message. Only outputs when --debug is enabled.
|
|
12
|
+
* @param module - Module name for log prefix
|
|
13
|
+
* @param message - Log message
|
|
14
|
+
* @param args - Additional values to log
|
|
12
15
|
*/
|
|
13
16
|
export declare function debug(module: string, message: string, ...args: unknown[]): void;
|
|
14
17
|
/**
|
|
15
18
|
* Log an informational message. Always outputs to stderr.
|
|
16
19
|
* Use for user-facing progress indicators during long-running operations.
|
|
20
|
+
* @param module - Module name for log prefix
|
|
21
|
+
* @param message - Log message
|
|
22
|
+
* @param args - Additional values to log
|
|
17
23
|
*/
|
|
18
24
|
export declare function info(module: string, message: string, ...args: unknown[]): void;
|
|
19
25
|
/**
|
|
20
26
|
* Log a warning. Always outputs.
|
|
27
|
+
* @param module - Module name for log prefix
|
|
28
|
+
* @param message - Warning message
|
|
29
|
+
* @param args - Additional values to log
|
|
21
30
|
*/
|
|
22
31
|
export declare function warn(module: string, message: string, ...args: unknown[]): void;
|
|
23
32
|
/**
|
|
24
33
|
* Time an async operation and log duration in debug mode.
|
|
34
|
+
* @param module - Module name for log prefix
|
|
35
|
+
* @param label - Operation label for the timing log
|
|
36
|
+
* @param fn - Async function to time
|
|
37
|
+
* @returns The result of the async function
|
|
25
38
|
*/
|
|
26
39
|
export declare function timed<T>(module: string, label: string, fn: () => Promise<T>): Promise<T>;
|
package/dist/core/logger.js
CHANGED
|
@@ -14,6 +14,9 @@ export function isDebugEnabled() {
|
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
16
|
* Log a debug message. Only outputs when --debug is enabled.
|
|
17
|
+
* @param module - Module name for log prefix
|
|
18
|
+
* @param message - Log message
|
|
19
|
+
* @param args - Additional values to log
|
|
17
20
|
*/
|
|
18
21
|
export function debug(module, message, ...args) {
|
|
19
22
|
if (!debugEnabled)
|
|
@@ -24,6 +27,9 @@ export function debug(module, message, ...args) {
|
|
|
24
27
|
/**
|
|
25
28
|
* Log an informational message. Always outputs to stderr.
|
|
26
29
|
* Use for user-facing progress indicators during long-running operations.
|
|
30
|
+
* @param module - Module name for log prefix
|
|
31
|
+
* @param message - Log message
|
|
32
|
+
* @param args - Additional values to log
|
|
27
33
|
*/
|
|
28
34
|
export function info(module, message, ...args) {
|
|
29
35
|
const timestamp = new Date().toISOString();
|
|
@@ -31,6 +37,9 @@ export function info(module, message, ...args) {
|
|
|
31
37
|
}
|
|
32
38
|
/**
|
|
33
39
|
* Log a warning. Always outputs.
|
|
40
|
+
* @param module - Module name for log prefix
|
|
41
|
+
* @param message - Warning message
|
|
42
|
+
* @param args - Additional values to log
|
|
34
43
|
*/
|
|
35
44
|
export function warn(module, message, ...args) {
|
|
36
45
|
const timestamp = new Date().toISOString();
|
|
@@ -38,6 +47,10 @@ export function warn(module, message, ...args) {
|
|
|
38
47
|
}
|
|
39
48
|
/**
|
|
40
49
|
* Time an async operation and log duration in debug mode.
|
|
50
|
+
* @param module - Module name for log prefix
|
|
51
|
+
* @param label - Operation label for the timing log
|
|
52
|
+
* @param fn - Async function to time
|
|
53
|
+
* @returns The result of the async function
|
|
41
54
|
*/
|
|
42
55
|
export async function timed(module, label, fn) {
|
|
43
56
|
if (!debugEnabled)
|
|
@@ -21,6 +21,9 @@ export { determineStatus } from './status-determination.js';
|
|
|
21
21
|
/**
|
|
22
22
|
* Check if a PR has a merge conflict based on GitHub's mergeable flag and mergeable_state.
|
|
23
23
|
* Returns true when mergeable is explicitly false or the mergeable_state is 'dirty'.
|
|
24
|
+
*
|
|
25
|
+
* @param mergeable - GitHub's mergeable flag (null when not yet computed)
|
|
26
|
+
* @param mergeableState - GitHub's mergeable_state string
|
|
24
27
|
*/
|
|
25
28
|
export declare function hasMergeConflict(mergeable: boolean | null, mergeableState: string | null): boolean;
|
|
26
29
|
export interface PRCheckFailure {
|
|
@@ -31,13 +34,35 @@ export interface FetchPRsResult {
|
|
|
31
34
|
prs: FetchedPR[];
|
|
32
35
|
failures: PRCheckFailure[];
|
|
33
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Fetches and enriches open PRs from GitHub for the configured user.
|
|
39
|
+
*
|
|
40
|
+
* In v2, all PR data is fetched fresh on each run — no local PR tracking.
|
|
41
|
+
* CI status, reviews, merge conflicts, and maintainer comments are enriched
|
|
42
|
+
* for each PR to compute a {@link FetchedPRStatus}.
|
|
43
|
+
*/
|
|
34
44
|
export declare class PRMonitor {
|
|
35
45
|
private octokit;
|
|
36
46
|
private stateManager;
|
|
47
|
+
/**
|
|
48
|
+
* @param githubToken - GitHub personal access token or token from `gh auth token`
|
|
49
|
+
*/
|
|
37
50
|
constructor(githubToken: string);
|
|
38
51
|
/**
|
|
39
|
-
* Fetch all open PRs for the configured user fresh from GitHub
|
|
40
|
-
* This is the main entry point for the v2 architecture
|
|
52
|
+
* Fetch all open PRs for the configured user fresh from GitHub.
|
|
53
|
+
* This is the main entry point for the v2 architecture.
|
|
54
|
+
*
|
|
55
|
+
* @returns All open PRs enriched with status, plus any failures
|
|
56
|
+
* @throws {ConfigurationError} If no GitHub username is configured
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* import { PRMonitor, requireGitHubToken } from '@oss-autopilot/core';
|
|
61
|
+
*
|
|
62
|
+
* const monitor = new PRMonitor(requireGitHubToken());
|
|
63
|
+
* const { prs, failures } = await monitor.fetchUserOpenPRs();
|
|
64
|
+
* console.log(`Found ${prs.length} open PRs, ${failures.length} failures`);
|
|
65
|
+
* ```
|
|
41
66
|
*/
|
|
42
67
|
fetchUserOpenPRs(): Promise<FetchPRsResult>;
|
|
43
68
|
/**
|
|
@@ -51,7 +76,8 @@ export declare class PRMonitor {
|
|
|
51
76
|
private buildFetchedPR;
|
|
52
77
|
/**
|
|
53
78
|
* Fetch merged PR counts and latest merge dates per repository for the configured user.
|
|
54
|
-
*
|
|
79
|
+
* @param starFilter - Optional filter to exclude low-star repos
|
|
80
|
+
* @returns Per-repo merged counts with monthly breakdowns
|
|
55
81
|
*/
|
|
56
82
|
fetchUserMergedPRCounts(starFilter?: StarFilter): Promise<PRCountsResult<{
|
|
57
83
|
count: number;
|
|
@@ -59,7 +85,8 @@ export declare class PRMonitor {
|
|
|
59
85
|
}>>;
|
|
60
86
|
/**
|
|
61
87
|
* Fetch closed-without-merge PR counts per repository for the configured user.
|
|
62
|
-
*
|
|
88
|
+
* @param starFilter - Optional filter to exclude low-star repos
|
|
89
|
+
* @returns Per-repo closed counts with monthly breakdowns
|
|
63
90
|
*/
|
|
64
91
|
fetchUserClosedPRCounts(starFilter?: StarFilter): Promise<PRCountsResult<number>>;
|
|
65
92
|
/**
|
|
@@ -72,20 +99,28 @@ export declare class PRMonitor {
|
|
|
72
99
|
}>>;
|
|
73
100
|
/**
|
|
74
101
|
* Fetch PRs closed without merge in the last N days.
|
|
75
|
-
*
|
|
102
|
+
* @param days - Lookback window in days (default: 7)
|
|
103
|
+
* @returns Recently closed PRs
|
|
76
104
|
*/
|
|
77
105
|
fetchRecentlyClosedPRs(days?: number): Promise<ClosedPR[]>;
|
|
78
106
|
/**
|
|
79
107
|
* Fetch PRs merged in the last N days.
|
|
80
|
-
*
|
|
108
|
+
* @param days - Lookback window in days (default: 7)
|
|
109
|
+
* @returns Recently merged PRs
|
|
81
110
|
*/
|
|
82
111
|
fetchRecentlyMergedPRs(days?: number): Promise<MergedPR[]>;
|
|
83
112
|
/**
|
|
84
|
-
* Generate a daily digest from fetched PRs
|
|
113
|
+
* Generate a daily digest from fetched PRs.
|
|
114
|
+
* @param prs - All open PRs (active + shelved)
|
|
115
|
+
* @param recentlyClosedPRs - PRs closed without merge in the last 7 days
|
|
116
|
+
* @param recentlyMergedPRs - PRs merged in the last 7 days
|
|
117
|
+
* @returns Daily digest with categorized PRs and summary stats
|
|
85
118
|
*/
|
|
86
119
|
generateDigest(prs: FetchedPR[], recentlyClosedPRs?: ClosedPR[], recentlyMergedPRs?: MergedPR[]): DailyDigest;
|
|
87
120
|
/**
|
|
88
|
-
* Update repository scores based on observed PR (called when we detect merged/closed PRs)
|
|
121
|
+
* Update repository scores based on observed PR (called when we detect merged/closed PRs).
|
|
122
|
+
* @param repo - Repository in "owner/repo" format
|
|
123
|
+
* @param wasMerged - true if the PR was merged, false if closed without merge
|
|
89
124
|
*/
|
|
90
125
|
updateRepoScoreFromObservedPR(repo: string, wasMerged: boolean): Promise<void>;
|
|
91
126
|
}
|
package/dist/core/pr-monitor.js
CHANGED
|
@@ -35,22 +35,47 @@ export { determineStatus } from './status-determination.js';
|
|
|
35
35
|
/**
|
|
36
36
|
* Check if a PR has a merge conflict based on GitHub's mergeable flag and mergeable_state.
|
|
37
37
|
* Returns true when mergeable is explicitly false or the mergeable_state is 'dirty'.
|
|
38
|
+
*
|
|
39
|
+
* @param mergeable - GitHub's mergeable flag (null when not yet computed)
|
|
40
|
+
* @param mergeableState - GitHub's mergeable_state string
|
|
38
41
|
*/
|
|
39
42
|
export function hasMergeConflict(mergeable, mergeableState) {
|
|
40
43
|
return mergeable === false || mergeableState === 'dirty';
|
|
41
44
|
}
|
|
42
45
|
const MODULE = 'pr-monitor';
|
|
43
46
|
const MAX_CONCURRENT_REQUESTS = DEFAULT_CONCURRENCY;
|
|
47
|
+
/**
|
|
48
|
+
* Fetches and enriches open PRs from GitHub for the configured user.
|
|
49
|
+
*
|
|
50
|
+
* In v2, all PR data is fetched fresh on each run — no local PR tracking.
|
|
51
|
+
* CI status, reviews, merge conflicts, and maintainer comments are enriched
|
|
52
|
+
* for each PR to compute a {@link FetchedPRStatus}.
|
|
53
|
+
*/
|
|
44
54
|
export class PRMonitor {
|
|
45
55
|
octokit;
|
|
46
56
|
stateManager;
|
|
57
|
+
/**
|
|
58
|
+
* @param githubToken - GitHub personal access token or token from `gh auth token`
|
|
59
|
+
*/
|
|
47
60
|
constructor(githubToken) {
|
|
48
61
|
this.octokit = getOctokit(githubToken);
|
|
49
62
|
this.stateManager = getStateManager();
|
|
50
63
|
}
|
|
51
64
|
/**
|
|
52
|
-
* Fetch all open PRs for the configured user fresh from GitHub
|
|
53
|
-
* This is the main entry point for the v2 architecture
|
|
65
|
+
* Fetch all open PRs for the configured user fresh from GitHub.
|
|
66
|
+
* This is the main entry point for the v2 architecture.
|
|
67
|
+
*
|
|
68
|
+
* @returns All open PRs enriched with status, plus any failures
|
|
69
|
+
* @throws {ConfigurationError} If no GitHub username is configured
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* import { PRMonitor, requireGitHubToken } from '@oss-autopilot/core';
|
|
74
|
+
*
|
|
75
|
+
* const monitor = new PRMonitor(requireGitHubToken());
|
|
76
|
+
* const { prs, failures } = await monitor.fetchUserOpenPRs();
|
|
77
|
+
* console.log(`Found ${prs.length} open PRs, ${failures.length} failures`);
|
|
78
|
+
* ```
|
|
54
79
|
*/
|
|
55
80
|
async fetchUserOpenPRs() {
|
|
56
81
|
const config = this.stateManager.getState().config;
|
|
@@ -287,7 +312,8 @@ export class PRMonitor {
|
|
|
287
312
|
}
|
|
288
313
|
/**
|
|
289
314
|
* Fetch merged PR counts and latest merge dates per repository for the configured user.
|
|
290
|
-
*
|
|
315
|
+
* @param starFilter - Optional filter to exclude low-star repos
|
|
316
|
+
* @returns Per-repo merged counts with monthly breakdowns
|
|
291
317
|
*/
|
|
292
318
|
async fetchUserMergedPRCounts(starFilter) {
|
|
293
319
|
const config = this.stateManager.getState().config;
|
|
@@ -295,7 +321,8 @@ export class PRMonitor {
|
|
|
295
321
|
}
|
|
296
322
|
/**
|
|
297
323
|
* Fetch closed-without-merge PR counts per repository for the configured user.
|
|
298
|
-
*
|
|
324
|
+
* @param starFilter - Optional filter to exclude low-star repos
|
|
325
|
+
* @returns Per-repo closed counts with monthly breakdowns
|
|
299
326
|
*/
|
|
300
327
|
async fetchUserClosedPRCounts(starFilter) {
|
|
301
328
|
const config = this.stateManager.getState().config;
|
|
@@ -357,7 +384,8 @@ export class PRMonitor {
|
|
|
357
384
|
}
|
|
358
385
|
/**
|
|
359
386
|
* Fetch PRs closed without merge in the last N days.
|
|
360
|
-
*
|
|
387
|
+
* @param days - Lookback window in days (default: 7)
|
|
388
|
+
* @returns Recently closed PRs
|
|
361
389
|
*/
|
|
362
390
|
async fetchRecentlyClosedPRs(days = 7) {
|
|
363
391
|
const config = this.stateManager.getState().config;
|
|
@@ -365,14 +393,19 @@ export class PRMonitor {
|
|
|
365
393
|
}
|
|
366
394
|
/**
|
|
367
395
|
* Fetch PRs merged in the last N days.
|
|
368
|
-
*
|
|
396
|
+
* @param days - Lookback window in days (default: 7)
|
|
397
|
+
* @returns Recently merged PRs
|
|
369
398
|
*/
|
|
370
399
|
async fetchRecentlyMergedPRs(days = 7) {
|
|
371
400
|
const config = this.stateManager.getState().config;
|
|
372
401
|
return fetchRecentlyMergedPRsImpl(this.octokit, config, days);
|
|
373
402
|
}
|
|
374
403
|
/**
|
|
375
|
-
* Generate a daily digest from fetched PRs
|
|
404
|
+
* Generate a daily digest from fetched PRs.
|
|
405
|
+
* @param prs - All open PRs (active + shelved)
|
|
406
|
+
* @param recentlyClosedPRs - PRs closed without merge in the last 7 days
|
|
407
|
+
* @param recentlyMergedPRs - PRs merged in the last 7 days
|
|
408
|
+
* @returns Daily digest with categorized PRs and summary stats
|
|
376
409
|
*/
|
|
377
410
|
generateDigest(prs, recentlyClosedPRs = [], recentlyMergedPRs = []) {
|
|
378
411
|
const now = new Date().toISOString();
|
|
@@ -399,7 +432,9 @@ export class PRMonitor {
|
|
|
399
432
|
};
|
|
400
433
|
}
|
|
401
434
|
/**
|
|
402
|
-
* Update repository scores based on observed PR (called when we detect merged/closed PRs)
|
|
435
|
+
* Update repository scores based on observed PR (called when we detect merged/closed PRs).
|
|
436
|
+
* @param repo - Repository in "owner/repo" format
|
|
437
|
+
* @param wasMerged - true if the PR was merged, false if closed without merge
|
|
403
438
|
*/
|
|
404
439
|
async updateRepoScoreFromObservedPR(repo, wasMerged) {
|
|
405
440
|
if (wasMerged) {
|
|
@@ -24,6 +24,7 @@ export declare function releaseLock(lockPath: string): void;
|
|
|
24
24
|
export declare function atomicWriteFileSync(filePath: string, data: string, mode?: number): void;
|
|
25
25
|
/**
|
|
26
26
|
* Create a fresh state (v2: fresh GitHub fetching).
|
|
27
|
+
* Leverages Zod schema defaults to produce a complete state.
|
|
27
28
|
*/
|
|
28
29
|
export declare function createFreshState(): AgentState;
|
|
29
30
|
/**
|
|
@@ -5,13 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import * as fs from 'fs';
|
|
7
7
|
import * as path from 'path';
|
|
8
|
-
import {
|
|
8
|
+
import { AgentStateSchema } from './state-schema.js';
|
|
9
9
|
import { getStatePath, getBackupDir, getDataDir } from './utils.js';
|
|
10
10
|
import { errorMessage } from './errors.js';
|
|
11
11
|
import { debug, warn } from './logger.js';
|
|
12
12
|
const MODULE = 'state';
|
|
13
|
-
// Current state version
|
|
14
|
-
const CURRENT_STATE_VERSION = 2;
|
|
15
13
|
// Lock file timeout: if a lock is older than this, it is considered stale
|
|
16
14
|
const LOCK_TIMEOUT_MS = 30_000; // 30 seconds
|
|
17
15
|
// Legacy path for migration
|
|
@@ -139,66 +137,12 @@ function migrateV1ToV2(rawState) {
|
|
|
139
137
|
debug(MODULE, `Migration complete. Preserved ${Object.keys(repoScores).length} repo scores.`);
|
|
140
138
|
return migratedState;
|
|
141
139
|
}
|
|
142
|
-
/**
|
|
143
|
-
* Validate that a loaded state has the required structure.
|
|
144
|
-
* Handles both v1 (with PR arrays) and v2 (without).
|
|
145
|
-
*/
|
|
146
|
-
function isValidState(state) {
|
|
147
|
-
if (!state || typeof state !== 'object')
|
|
148
|
-
return false;
|
|
149
|
-
const s = state;
|
|
150
|
-
// Migrate older states that don't have repoScores
|
|
151
|
-
if (s.repoScores === undefined) {
|
|
152
|
-
s.repoScores = {};
|
|
153
|
-
}
|
|
154
|
-
// Migrate older states that don't have events
|
|
155
|
-
if (s.events === undefined) {
|
|
156
|
-
s.events = [];
|
|
157
|
-
}
|
|
158
|
-
// Migrate older states that don't have mergedPRs
|
|
159
|
-
if (s.mergedPRs === undefined) {
|
|
160
|
-
s.mergedPRs = [];
|
|
161
|
-
}
|
|
162
|
-
// Base requirements for all versions
|
|
163
|
-
const hasBaseFields = typeof s.version === 'number' &&
|
|
164
|
-
typeof s.repoScores === 'object' &&
|
|
165
|
-
s.repoScores !== null &&
|
|
166
|
-
Array.isArray(s.events) &&
|
|
167
|
-
typeof s.config === 'object' &&
|
|
168
|
-
s.config !== null;
|
|
169
|
-
if (!hasBaseFields)
|
|
170
|
-
return false;
|
|
171
|
-
// v1 requires base PR arrays to be present (they will be dropped during migration)
|
|
172
|
-
if (s.version === 1) {
|
|
173
|
-
return (Array.isArray(s.activePRs) &&
|
|
174
|
-
Array.isArray(s.dormantPRs) &&
|
|
175
|
-
Array.isArray(s.mergedPRs) &&
|
|
176
|
-
Array.isArray(s.closedPRs));
|
|
177
|
-
}
|
|
178
|
-
// v2+ doesn't require PR arrays
|
|
179
|
-
return true;
|
|
180
|
-
}
|
|
181
140
|
/**
|
|
182
141
|
* Create a fresh state (v2: fresh GitHub fetching).
|
|
142
|
+
* Leverages Zod schema defaults to produce a complete state.
|
|
183
143
|
*/
|
|
184
144
|
export function createFreshState() {
|
|
185
|
-
return {
|
|
186
|
-
version: CURRENT_STATE_VERSION,
|
|
187
|
-
activeIssues: [],
|
|
188
|
-
repoScores: {},
|
|
189
|
-
config: {
|
|
190
|
-
...INITIAL_STATE.config,
|
|
191
|
-
setupComplete: false,
|
|
192
|
-
languages: [...INITIAL_STATE.config.languages],
|
|
193
|
-
labels: [...INITIAL_STATE.config.labels],
|
|
194
|
-
excludeRepos: [],
|
|
195
|
-
trustedProjects: [],
|
|
196
|
-
shelvedPRUrls: [],
|
|
197
|
-
dismissedIssues: {},
|
|
198
|
-
},
|
|
199
|
-
events: [],
|
|
200
|
-
lastRunAt: new Date().toISOString(),
|
|
201
|
-
};
|
|
145
|
+
return AgentStateSchema.parse({ version: 2 });
|
|
202
146
|
}
|
|
203
147
|
/**
|
|
204
148
|
* Migrate state from legacy ./data/ location to ~/.oss-autopilot/.
|
|
@@ -298,13 +242,15 @@ function tryRestoreFromBackup() {
|
|
|
298
242
|
const backupPath = path.join(backupDir, backupFile);
|
|
299
243
|
try {
|
|
300
244
|
const data = fs.readFileSync(backupPath, 'utf-8');
|
|
301
|
-
let
|
|
302
|
-
if (
|
|
245
|
+
let raw = JSON.parse(data);
|
|
246
|
+
// Migrate from v1 to v2 if needed (before schema validation)
|
|
247
|
+
if (typeof raw === 'object' && raw !== null && raw.version === 1) {
|
|
248
|
+
raw = migrateV1ToV2(raw);
|
|
249
|
+
}
|
|
250
|
+
const parsed = AgentStateSchema.safeParse(raw);
|
|
251
|
+
if (parsed.success) {
|
|
252
|
+
const state = parsed.data;
|
|
303
253
|
debug(MODULE, `Successfully restored state from backup: ${backupFile}`);
|
|
304
|
-
// Migrate from v1 to v2 if needed
|
|
305
|
-
if (state.version === 1) {
|
|
306
|
-
state = migrateV1ToV2(state);
|
|
307
|
-
}
|
|
308
254
|
const repoCount = Object.keys(state.repoScores).length;
|
|
309
255
|
debug(MODULE, `Restored state v${state.version}: ${repoCount} repo scores`);
|
|
310
256
|
// Overwrite the corrupted main state file with the restored backup (atomic write)
|
|
@@ -313,6 +259,10 @@ function tryRestoreFromBackup() {
|
|
|
313
259
|
debug(MODULE, 'Restored backup written to main state file');
|
|
314
260
|
return state;
|
|
315
261
|
}
|
|
262
|
+
// safeParse failed — log and try next backup
|
|
263
|
+
const summary = parsed.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join('; ');
|
|
264
|
+
warn(MODULE, `Backup ${backupFile} failed schema validation: ${summary}`);
|
|
265
|
+
debug(MODULE, `Backup ${backupFile} full validation errors:`, parsed.error.issues);
|
|
316
266
|
}
|
|
317
267
|
catch (backupErr) {
|
|
318
268
|
// This backup is also corrupted, try the next one
|
|
@@ -335,10 +285,29 @@ export function loadState() {
|
|
|
335
285
|
try {
|
|
336
286
|
if (fs.existsSync(statePath)) {
|
|
337
287
|
const data = fs.readFileSync(statePath, 'utf-8');
|
|
338
|
-
let
|
|
339
|
-
//
|
|
340
|
-
|
|
341
|
-
|
|
288
|
+
let raw = JSON.parse(data);
|
|
289
|
+
// Migrate from v1 to v2 if needed (before schema validation)
|
|
290
|
+
let wasMigrated = false;
|
|
291
|
+
if (typeof raw === 'object' && raw !== null && raw.version === 1) {
|
|
292
|
+
raw = migrateV1ToV2(raw);
|
|
293
|
+
wasMigrated = true;
|
|
294
|
+
}
|
|
295
|
+
// Validate through Zod schema (strips unknown keys in memory; stale keys persist on disk until next save)
|
|
296
|
+
const parsed = AgentStateSchema.safeParse(raw);
|
|
297
|
+
if (!parsed.success) {
|
|
298
|
+
const summary = parsed.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join('; ');
|
|
299
|
+
warn(MODULE, `Invalid state file structure: ${summary}`);
|
|
300
|
+
warn(MODULE, 'Attempting to restore from backup...');
|
|
301
|
+
debug(MODULE, 'Full validation errors:', parsed.error.issues);
|
|
302
|
+
// Preserve the rejected state file so the user can recover
|
|
303
|
+
try {
|
|
304
|
+
const rejectedPath = statePath + '.rejected-' + Date.now();
|
|
305
|
+
fs.copyFileSync(statePath, rejectedPath);
|
|
306
|
+
warn(MODULE, `Previous state preserved at: ${rejectedPath}`);
|
|
307
|
+
}
|
|
308
|
+
catch (preserveErr) {
|
|
309
|
+
warn(MODULE, `Could not preserve rejected state file: ${errorMessage(preserveErr)}`);
|
|
310
|
+
}
|
|
342
311
|
const restoredState = tryRestoreFromBackup();
|
|
343
312
|
if (restoredState) {
|
|
344
313
|
const mtimeMs = safeGetMtimeMs(statePath);
|
|
@@ -347,23 +316,16 @@ export function loadState() {
|
|
|
347
316
|
warn(MODULE, 'No valid backup found, starting fresh');
|
|
348
317
|
return { state: createFreshState(), mtimeMs: 0 };
|
|
349
318
|
}
|
|
350
|
-
//
|
|
351
|
-
if (
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
atomicWriteFileSync(statePath, JSON.stringify(state, null, 2), 0o600);
|
|
355
|
-
debug(MODULE, 'Migrated state saved');
|
|
319
|
+
// Save migrated state only after validation succeeds
|
|
320
|
+
if (wasMigrated) {
|
|
321
|
+
atomicWriteFileSync(statePath, JSON.stringify(parsed.data, null, 2), 0o600);
|
|
322
|
+
debug(MODULE, 'Migrated and validated state saved');
|
|
356
323
|
}
|
|
357
|
-
|
|
358
|
-
//
|
|
324
|
+
const state = parsed.data;
|
|
325
|
+
// Strip PR URLs from dismissedIssues (PR dismiss removed).
|
|
326
|
+
// This filters values inside a known field — Zod .strip() only removes unknown keys.
|
|
359
327
|
try {
|
|
360
328
|
let needsCleanupSave = false;
|
|
361
|
-
const rawConfig = state.config;
|
|
362
|
-
if (rawConfig.snoozedPRs) {
|
|
363
|
-
delete rawConfig.snoozedPRs;
|
|
364
|
-
needsCleanupSave = true;
|
|
365
|
-
}
|
|
366
|
-
// Strip PR URLs from dismissedIssues (PR dismiss removed)
|
|
367
329
|
if (state.config.dismissedIssues) {
|
|
368
330
|
const PR_URL_RE = /\/pull\/\d+$/;
|
|
369
331
|
for (const url of Object.keys(state.config.dismissedIssues)) {
|
|
@@ -375,7 +337,7 @@ export function loadState() {
|
|
|
375
337
|
}
|
|
376
338
|
if (needsCleanupSave) {
|
|
377
339
|
atomicWriteFileSync(statePath, JSON.stringify(state, null, 2), 0o600);
|
|
378
|
-
warn(MODULE, 'Cleaned up
|
|
340
|
+
warn(MODULE, 'Cleaned up dismissed PR URLs from persisted state');
|
|
379
341
|
}
|
|
380
342
|
}
|
|
381
343
|
catch (cleanupError) {
|