@oss-autopilot/core 1.16.1 → 1.17.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 +53 -11
- package/dist/cli.bundle.cjs +82 -69
- package/dist/cli.js +22 -10
- package/dist/commands/comments.js +38 -20
- package/dist/commands/config.d.ts +9 -2
- package/dist/commands/config.js +12 -3
- package/dist/commands/daily.d.ts +3 -1
- package/dist/commands/daily.js +126 -37
- package/dist/commands/dashboard-data.d.ts +26 -2
- package/dist/commands/dashboard-data.js +45 -19
- package/dist/commands/dashboard-server.d.ts +1 -1
- package/dist/commands/dashboard-server.js +109 -20
- package/dist/commands/dismiss.js +4 -1
- package/dist/commands/doctor.d.ts +49 -0
- package/dist/commands/doctor.js +358 -0
- package/dist/commands/index.d.ts +2 -0
- package/dist/commands/index.js +2 -0
- package/dist/commands/move.d.ts +1 -2
- package/dist/commands/move.js +8 -4
- package/dist/commands/read.js +2 -1
- package/dist/commands/search.d.ts +0 -18
- package/dist/commands/search.js +38 -1
- package/dist/commands/setup.js +42 -2
- package/dist/commands/shelve.js +4 -1
- package/dist/commands/skip-add.js +1 -1
- package/dist/commands/startup.js +7 -3
- package/dist/commands/track.js +2 -1
- package/dist/commands/vet-list.d.ts +23 -2
- package/dist/commands/vet-list.js +57 -10
- package/dist/core/anti-llm-policy.d.ts +5 -0
- package/dist/core/anti-llm-policy.js +5 -0
- package/dist/core/ci-analysis.js +6 -1
- package/dist/core/config-registry.d.ts +44 -0
- package/dist/core/config-registry.js +286 -0
- package/dist/core/dashboard-data-schema.d.ts +78 -0
- package/dist/core/dashboard-data-schema.js +80 -0
- package/dist/core/errors.d.ts +14 -0
- package/dist/core/errors.js +22 -0
- package/dist/core/http-cache.d.ts +8 -1
- package/dist/core/http-cache.js +59 -1
- package/dist/core/index.d.ts +3 -1
- package/dist/core/index.js +3 -1
- package/dist/core/maintainer-analysis.js +9 -3
- package/dist/core/pr-monitor.d.ts +7 -0
- package/dist/core/pr-monitor.js +16 -3
- package/dist/core/repo-score-manager.d.ts +17 -3
- package/dist/core/repo-score-manager.js +48 -19
- package/dist/core/state-persistence.d.ts +14 -1
- package/dist/core/state-persistence.js +24 -2
- package/dist/core/state-schema.d.ts +2 -0
- package/dist/core/state-schema.js +5 -0
- package/dist/core/state.d.ts +26 -2
- package/dist/core/state.js +50 -5
- package/dist/core/status-determination.d.ts +16 -0
- package/dist/core/status-determination.js +44 -11
- package/dist/formatters/json.d.ts +40 -2
- package/dist/formatters/json.js +1 -0
- package/package.json +1 -1
package/dist/core/state.js
CHANGED
|
@@ -8,11 +8,31 @@ import { AgentStateSchema } from './state-schema.js';
|
|
|
8
8
|
import { loadState, saveState, reloadStateIfChanged, createFreshState, atomicWriteFileSync, } from './state-persistence.js';
|
|
9
9
|
import * as repoScoring from './repo-score-manager.js';
|
|
10
10
|
import { debug, warn } from './logger.js';
|
|
11
|
-
import { errorMessage, ConfigurationError } from './errors.js';
|
|
11
|
+
import { errorMessage, ConfigurationError, ConcurrencyError } from './errors.js';
|
|
12
12
|
import { GistStateStore } from './gist-state-store.js';
|
|
13
13
|
import { getStatePath, getStateCachePath } from './utils.js';
|
|
14
14
|
export { acquireLock, releaseLock, atomicWriteFileSync } from './state-persistence.js';
|
|
15
15
|
const MODULE = 'state';
|
|
16
|
+
/**
|
|
17
|
+
* Push state to the backing Gist when Gist mode is active. Best-effort:
|
|
18
|
+
* network/auth failures are logged via `warn()` but never propagated —
|
|
19
|
+
* the caller's primary mutation has already succeeded locally and the
|
|
20
|
+
* next successful push (or a daily run) will re-sync.
|
|
21
|
+
*
|
|
22
|
+
* Intended for state-mutating PR-flow commands (shelve / unshelve / move /
|
|
23
|
+
* dismiss / undismiss / claim) so Gist-sync users don't see day-long drift
|
|
24
|
+
* between machines waiting for the next `daily` checkpoint. See issue #1036.
|
|
25
|
+
*/
|
|
26
|
+
export async function maybeCheckpoint(stateManager, callerModule) {
|
|
27
|
+
if (!stateManager.isGistMode())
|
|
28
|
+
return;
|
|
29
|
+
try {
|
|
30
|
+
await stateManager.checkpoint();
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
warn(callerModule, `Gist checkpoint failed (local mutation succeeded, will retry on next push): ${errorMessage(err)}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
16
36
|
/**
|
|
17
37
|
* Singleton manager for persistent agent state stored in ~/.oss-autopilot/state.json.
|
|
18
38
|
*
|
|
@@ -173,8 +193,19 @@ export class StateManager {
|
|
|
173
193
|
*
|
|
174
194
|
* In Gist mode, writes to a local cache file (not the main state file) so the Gist
|
|
175
195
|
* remains the source of truth. Use `checkpoint()` to push state to the Gist.
|
|
176
|
-
|
|
177
|
-
|
|
196
|
+
*
|
|
197
|
+
* Local-file mode uses optimistic compare-and-swap: if another process has
|
|
198
|
+
* written `state.json` since we last loaded/saved, `saveState` throws
|
|
199
|
+
* `ConcurrencyError`. See issue #1030.
|
|
200
|
+
*
|
|
201
|
+
* @throws ConcurrencyError (local file mode only) when the on-disk state
|
|
202
|
+
* was modified externally between this StateManager's last load/save
|
|
203
|
+
* and now. Callers that tolerate silent merge can set
|
|
204
|
+
* `{ allowReloadAndLoseMutation: true }` to downgrade the error into a
|
|
205
|
+
* warning — but note that doing so abandons any in-memory mutation
|
|
206
|
+
* made since the last load. Fail-loud (the default) is preferred.
|
|
207
|
+
*/
|
|
208
|
+
save(options = {}) {
|
|
178
209
|
this.state.lastRunAt = new Date().toISOString();
|
|
179
210
|
if (this.inMemoryOnly) {
|
|
180
211
|
return;
|
|
@@ -182,6 +213,10 @@ export class StateManager {
|
|
|
182
213
|
if (this.gistStore) {
|
|
183
214
|
// In Gist mode, write to local cache (not main state file).
|
|
184
215
|
// The Gist is the source of truth; local cache is for fallback.
|
|
216
|
+
// Intentionally NO compare-and-swap on this cache path — two processes
|
|
217
|
+
// racing on state-cache.json can clobber each other, but the next
|
|
218
|
+
// `refreshFromGist()` or `checkpoint()` re-syncs from the authoritative
|
|
219
|
+
// Gist. The trade-off is a transiently-stale offline bootstrap (#1030).
|
|
185
220
|
try {
|
|
186
221
|
atomicWriteFileSync(getStateCachePath(), JSON.stringify(this.state, null, 2), 0o600);
|
|
187
222
|
}
|
|
@@ -192,8 +227,18 @@ export class StateManager {
|
|
|
192
227
|
}
|
|
193
228
|
return;
|
|
194
229
|
}
|
|
195
|
-
// Local file mode
|
|
196
|
-
|
|
230
|
+
// Local file mode with optimistic compare-and-swap.
|
|
231
|
+
try {
|
|
232
|
+
this.lastLoadedMtimeMs = saveState(this.state, this.lastLoadedMtimeMs);
|
|
233
|
+
}
|
|
234
|
+
catch (err) {
|
|
235
|
+
if (err instanceof ConcurrencyError && options.allowReloadAndLoseMutation) {
|
|
236
|
+
warn(MODULE, `Concurrent external write detected; reloading and discarding in-memory mutation. ${err.message}`);
|
|
237
|
+
this.reloadIfChanged();
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
throw err;
|
|
241
|
+
}
|
|
197
242
|
}
|
|
198
243
|
/** Push current state to Gist (async). Call at well-defined moments (end of daily, after claim). */
|
|
199
244
|
async checkpoint() {
|
|
@@ -26,9 +26,25 @@ export declare const MIN_RESPONSE_GAP_MS: number;
|
|
|
26
26
|
* bot (#568), or when author info is unavailable (graceful degradation).
|
|
27
27
|
*/
|
|
28
28
|
export declare function isContributorCommit(commitAuthor?: string, contributorUsername?: string): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Parse an ISO-8601-ish date string into epoch milliseconds.
|
|
31
|
+
*
|
|
32
|
+
* Central helper so the whole module compares dates numerically via `getTime()`
|
|
33
|
+
* rather than relying on the lexicographic property of UTC ISO strings — the
|
|
34
|
+
* latter happens to sort correctly today but silently breaks on any other date
|
|
35
|
+
* format (local-time ISO, `Date.toString()`, non-standard). See #1044.
|
|
36
|
+
*
|
|
37
|
+
* Returns `NaN` for `undefined`, empty, or unparseable inputs so callers can
|
|
38
|
+
* make an explicit fail-closed decision; never throws.
|
|
39
|
+
*/
|
|
40
|
+
export declare function normalizeToEpochMs(date: string | undefined | null): number;
|
|
29
41
|
/**
|
|
30
42
|
* Check whether the contributor's commit is meaningfully after the maintainer's
|
|
31
43
|
* comment — i.e. the commit timestamp is at least MIN_RESPONSE_GAP_MS later (#547).
|
|
44
|
+
*
|
|
45
|
+
* Fails closed (returns `false`) if either date is unparseable, avoiding the
|
|
46
|
+
* silent lexicographic fallback that would produce wrong results for non-UTC
|
|
47
|
+
* ISO inputs. See #1044.
|
|
32
48
|
*/
|
|
33
49
|
export declare function isCommitAfterComment(commitDate: string, commentDate: string): boolean;
|
|
34
50
|
/**
|
|
@@ -32,17 +32,35 @@ export function isContributorCommit(commitAuthor, contributorUsername) {
|
|
|
32
32
|
return true; // CI-fix bots act on behalf of the contributor (#568)
|
|
33
33
|
return author === contributorUsername.toLowerCase();
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Parse an ISO-8601-ish date string into epoch milliseconds.
|
|
37
|
+
*
|
|
38
|
+
* Central helper so the whole module compares dates numerically via `getTime()`
|
|
39
|
+
* rather than relying on the lexicographic property of UTC ISO strings — the
|
|
40
|
+
* latter happens to sort correctly today but silently breaks on any other date
|
|
41
|
+
* format (local-time ISO, `Date.toString()`, non-standard). See #1044.
|
|
42
|
+
*
|
|
43
|
+
* Returns `NaN` for `undefined`, empty, or unparseable inputs so callers can
|
|
44
|
+
* make an explicit fail-closed decision; never throws.
|
|
45
|
+
*/
|
|
46
|
+
export function normalizeToEpochMs(date) {
|
|
47
|
+
if (!date)
|
|
48
|
+
return Number.NaN;
|
|
49
|
+
return new Date(date).getTime();
|
|
50
|
+
}
|
|
35
51
|
/**
|
|
36
52
|
* Check whether the contributor's commit is meaningfully after the maintainer's
|
|
37
53
|
* comment — i.e. the commit timestamp is at least MIN_RESPONSE_GAP_MS later (#547).
|
|
54
|
+
*
|
|
55
|
+
* Fails closed (returns `false`) if either date is unparseable, avoiding the
|
|
56
|
+
* silent lexicographic fallback that would produce wrong results for non-UTC
|
|
57
|
+
* ISO inputs. See #1044.
|
|
38
58
|
*/
|
|
39
59
|
export function isCommitAfterComment(commitDate, commentDate) {
|
|
40
|
-
const commitMs =
|
|
41
|
-
const commentMs =
|
|
42
|
-
if (Number.
|
|
43
|
-
|
|
44
|
-
return commitDate > commentDate;
|
|
45
|
-
}
|
|
60
|
+
const commitMs = normalizeToEpochMs(commitDate);
|
|
61
|
+
const commentMs = normalizeToEpochMs(commentDate);
|
|
62
|
+
if (!Number.isFinite(commitMs) || !Number.isFinite(commentMs))
|
|
63
|
+
return false;
|
|
46
64
|
return commitMs - commentMs >= MIN_RESPONSE_GAP_MS;
|
|
47
65
|
}
|
|
48
66
|
/**
|
|
@@ -61,16 +79,31 @@ function isCommentAddressedByCommit(commitDate, commentDate, changesRequestedDat
|
|
|
61
79
|
return false;
|
|
62
80
|
if (!isCommitAfterComment(commitDate, commentDate))
|
|
63
81
|
return false;
|
|
64
|
-
// Safety net (#431): if a CHANGES_REQUESTED review came after the commit, it's not addressed
|
|
65
|
-
|
|
66
|
-
|
|
82
|
+
// Safety net (#431): if a CHANGES_REQUESTED review came after the commit, it's not addressed.
|
|
83
|
+
// Fail-closed on unparseable `changesRequestedDate` (#1044): treat unknown ordering as "not addressed".
|
|
84
|
+
if (changesRequestedDate) {
|
|
85
|
+
const commitMs = normalizeToEpochMs(commitDate);
|
|
86
|
+
const changesRequestedMs = normalizeToEpochMs(changesRequestedDate);
|
|
87
|
+
if (!Number.isFinite(commitMs) || !Number.isFinite(changesRequestedMs))
|
|
88
|
+
return false;
|
|
89
|
+
if (commitMs < changesRequestedMs)
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
67
92
|
return true;
|
|
68
93
|
}
|
|
69
|
-
/**
|
|
94
|
+
/**
|
|
95
|
+
* Check whether a changes_requested review has been addressed by a subsequent contributor commit.
|
|
96
|
+
*
|
|
97
|
+
* Fails closed (returns `false`) on unparseable inputs (#1044).
|
|
98
|
+
*/
|
|
70
99
|
function isChangesAddressedByCommit(commitDate, changesRequestedDate) {
|
|
71
100
|
if (!commitDate || !changesRequestedDate)
|
|
72
101
|
return false;
|
|
73
|
-
|
|
102
|
+
const commitMs = normalizeToEpochMs(commitDate);
|
|
103
|
+
const changesRequestedMs = normalizeToEpochMs(changesRequestedDate);
|
|
104
|
+
if (!Number.isFinite(commitMs) || !Number.isFinite(changesRequestedMs))
|
|
105
|
+
return false;
|
|
106
|
+
return commitMs >= changesRequestedMs;
|
|
74
107
|
}
|
|
75
108
|
/**
|
|
76
109
|
* Collect all applicable action reasons independently, without short-circuiting (#675).
|
|
@@ -7,7 +7,7 @@ import type { ContributionStats } from '../core/stats.js';
|
|
|
7
7
|
import type { PRCheckFailure } from '../core/pr-monitor.js';
|
|
8
8
|
import type { SearchPriority } from '../core/types.js';
|
|
9
9
|
import type { CIFormatterDiagnosis, FormatterDetectionResult } from '../core/formatter-detection.js';
|
|
10
|
-
export type ErrorCode = 'AUTH_REQUIRED' | 'RATE_LIMITED' | 'VALIDATION' | 'CONFIGURATION' | 'NETWORK' | 'NOT_FOUND' | 'STATE_CORRUPTED' | 'UNKNOWN';
|
|
10
|
+
export type ErrorCode = 'AUTH_REQUIRED' | 'RATE_LIMITED' | 'VALIDATION' | 'CONFIGURATION' | 'NETWORK' | 'NOT_FOUND' | 'STATE_CORRUPTED' | 'CONCURRENCY' | 'UNKNOWN';
|
|
11
11
|
export interface JsonOutput<T = unknown> {
|
|
12
12
|
success: boolean;
|
|
13
13
|
data?: T;
|
|
@@ -104,6 +104,23 @@ export interface CompactRepoGroup {
|
|
|
104
104
|
repo: string;
|
|
105
105
|
prUrls: string[];
|
|
106
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Phase tags for non-fatal warnings emitted during a `daily` run.
|
|
109
|
+
* See `DailyWarning` and issue #1042 for the rationale — keeping this a
|
|
110
|
+
* fixed union so downstream consumers can switch on it without drift.
|
|
111
|
+
*/
|
|
112
|
+
export type DailyWarningPhase = 'fetch' | 'repo-scores' | 'analytics' | 'scout-sync' | 'partition' | 'dismiss-filter' | 'gist-checkpoint';
|
|
113
|
+
/**
|
|
114
|
+
* A single non-fatal failure surfaced from the `daily` pipeline. Unlike
|
|
115
|
+
* `PRCheckFailure` (which is scoped to per-PR fetch errors), this covers
|
|
116
|
+
* ancillary fetches that previously demoted to a log-only `warn()` — repo
|
|
117
|
+
* metadata, monthly analytics, scout sync, Gist checkpoint, etc.
|
|
118
|
+
*/
|
|
119
|
+
export interface DailyWarning {
|
|
120
|
+
phase: DailyWarningPhase;
|
|
121
|
+
operation: string;
|
|
122
|
+
message: string;
|
|
123
|
+
}
|
|
107
124
|
export interface DailyOutput {
|
|
108
125
|
digest: DailyDigestCompact;
|
|
109
126
|
capacity: CapacityAssessment;
|
|
@@ -114,6 +131,12 @@ export interface DailyOutput {
|
|
|
114
131
|
commentedIssues: CommentedIssue[];
|
|
115
132
|
repoGroups: CompactRepoGroup[];
|
|
116
133
|
failures: PRCheckFailure[];
|
|
134
|
+
/**
|
|
135
|
+
* Non-fatal warnings from ancillary pipeline phases (repo metadata,
|
|
136
|
+
* analytics, scout sync, Gist checkpoint, etc.). Always an array — empty
|
|
137
|
+
* on clean runs. See #1042.
|
|
138
|
+
*/
|
|
139
|
+
warnings: DailyWarning[];
|
|
117
140
|
}
|
|
118
141
|
/**
|
|
119
142
|
* Compact version of DailyOutput for reduced JSON payload size (#763).
|
|
@@ -133,6 +156,12 @@ export interface CompactDailyOutput {
|
|
|
133
156
|
commentedIssues: CommentedIssue[];
|
|
134
157
|
/** Number of PRs that failed to fetch. Non-zero indicates partial results. */
|
|
135
158
|
failureCount: number;
|
|
159
|
+
/**
|
|
160
|
+
* Non-fatal warnings from ancillary pipeline phases — full list retained so
|
|
161
|
+
* downstream consumers (dashboard, MCP) can surface degradation even under
|
|
162
|
+
* the `--compact` payload. See #1042.
|
|
163
|
+
*/
|
|
164
|
+
warnings: DailyWarning[];
|
|
136
165
|
}
|
|
137
166
|
/**
|
|
138
167
|
* Strip a full DailyOutput down to the compact subset (#763).
|
|
@@ -183,8 +212,17 @@ export interface SearchOutput {
|
|
|
183
212
|
reasonsToApprove: string[];
|
|
184
213
|
reasonsToSkip: string[];
|
|
185
214
|
searchPriority: SearchPriority;
|
|
186
|
-
/** 0-100 scale composite viability score */
|
|
215
|
+
/** 0-100 scale composite viability score. Sanitized on the boundary (#1043): out-of-contract values are coerced to 0 and logged. */
|
|
187
216
|
viabilityScore: number;
|
|
217
|
+
/**
|
|
218
|
+
* Letter grade (A/B/C/F) computed from the autopilot-tracked repoScore.
|
|
219
|
+
* Scout's `search` does not emit per-candidate projectHealth, so scout-side
|
|
220
|
+
* signals are treated as unknown; unscored repos grade 'F'. See #1043.
|
|
221
|
+
*/
|
|
222
|
+
grade: {
|
|
223
|
+
letter: 'A' | 'B' | 'C' | 'F';
|
|
224
|
+
reason: string;
|
|
225
|
+
};
|
|
188
226
|
repoScore?: {
|
|
189
227
|
/** 1-10 scale repository quality score */
|
|
190
228
|
score: number;
|
package/dist/formatters/json.js
CHANGED