@oss-autopilot/core 3.10.0 → 3.11.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.d.ts +7 -0
- package/dist/cli-registry.js +29 -5
- package/dist/cli.bundle.cjs +114 -114
- package/dist/cli.js +11 -3
- package/dist/commands/comments.js +31 -15
- package/dist/commands/compliance-score.js +12 -4
- package/dist/commands/daily.js +47 -2
- package/dist/commands/dashboard-data.d.ts +17 -0
- package/dist/commands/dashboard-data.js +49 -0
- package/dist/commands/dashboard-server.js +93 -24
- package/dist/commands/dismiss.d.ts +4 -0
- package/dist/commands/dismiss.js +4 -4
- package/dist/commands/guidelines.d.ts +19 -0
- package/dist/commands/guidelines.js +23 -4
- package/dist/commands/index.d.ts +3 -1
- package/dist/commands/index.js +2 -0
- package/dist/commands/move.d.ts +2 -0
- package/dist/commands/move.js +12 -8
- package/dist/commands/repo-vet.js +30 -8
- package/dist/commands/shelve.d.ts +4 -0
- package/dist/commands/shelve.js +4 -4
- package/dist/core/gist-state-store.js +42 -7
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/issue-conversation.js +15 -2
- package/dist/core/paths.d.ts +12 -0
- package/dist/core/paths.js +16 -0
- package/dist/core/pr-comments-fetcher.d.ts +10 -2
- package/dist/core/pr-comments-fetcher.js +22 -4
- package/dist/core/state-persistence.d.ts +31 -9
- package/dist/core/state-persistence.js +51 -16
- package/dist/core/state.d.ts +18 -1
- package/dist/core/state.js +35 -3
- package/dist/core/untrusted-content.d.ts +24 -3
- package/dist/core/untrusted-content.js +31 -3
- package/dist/formatters/json.d.ts +9 -1
- package/dist/formatters/json.js +12 -0
- package/package.json +7 -7
|
@@ -6,15 +6,12 @@
|
|
|
6
6
|
import * as fs from 'node:fs';
|
|
7
7
|
import * as path from 'node:path';
|
|
8
8
|
import { AgentStateSchema } from './state-schema.js';
|
|
9
|
-
import { getStatePath, getBackupDir, getDataDir } from './paths.js';
|
|
9
|
+
import { getStatePath, getBackupDir, getDataDir, getLegacyStatePath, getLegacyBackupDir } from './paths.js';
|
|
10
10
|
import { errorMessage, ConcurrencyError } from './errors.js';
|
|
11
11
|
import { debug, warn } from './logger.js';
|
|
12
12
|
const MODULE = 'state';
|
|
13
13
|
// Lock file timeout: if a lock is older than this, it is considered stale
|
|
14
14
|
const LOCK_TIMEOUT_MS = 30_000; // 30 seconds
|
|
15
|
-
// Legacy path for migration
|
|
16
|
-
const LEGACY_STATE_FILE = path.join(process.cwd(), 'data', 'state.json');
|
|
17
|
-
const LEGACY_BACKUP_DIR = path.join(process.cwd(), 'data', 'backups');
|
|
18
15
|
/**
|
|
19
16
|
* Check whether an existing lock file is stale (expired or corrupt).
|
|
20
17
|
* Returns true if the lock should be considered stale and can be removed.
|
|
@@ -182,6 +179,10 @@ export function createFreshState() {
|
|
|
182
179
|
*/
|
|
183
180
|
function migrateFromLegacyLocation() {
|
|
184
181
|
const newStatePath = getStatePath();
|
|
182
|
+
// Resolved via paths.js (not module constants) so tests can mock the
|
|
183
|
+
// legacy location into per-test temp dirs — see #1382.
|
|
184
|
+
const LEGACY_STATE_FILE = getLegacyStatePath();
|
|
185
|
+
const LEGACY_BACKUP_DIR = getLegacyBackupDir();
|
|
185
186
|
// If new state already exists, no migration needed
|
|
186
187
|
if (fs.existsSync(newStatePath)) {
|
|
187
188
|
return false;
|
|
@@ -316,11 +317,33 @@ function tryRestoreFromBackup() {
|
|
|
316
317
|
}
|
|
317
318
|
return null;
|
|
318
319
|
}
|
|
320
|
+
/**
|
|
321
|
+
* Copy a rejected state file to `<statePath>.rejected-<timestamp>` so the
|
|
322
|
+
* user can recover data manually before the file is overwritten by a backup
|
|
323
|
+
* restore or fresh state. Returns the preserved path, or null if the copy
|
|
324
|
+
* failed (preservation is best-effort and never throws).
|
|
325
|
+
*/
|
|
326
|
+
function preserveRejectedStateFile(statePath) {
|
|
327
|
+
try {
|
|
328
|
+
const rejectedPath = statePath + '.rejected-' + Date.now();
|
|
329
|
+
fs.copyFileSync(statePath, rejectedPath);
|
|
330
|
+
warn(MODULE, `Previous state preserved at: ${rejectedPath}`);
|
|
331
|
+
return rejectedPath;
|
|
332
|
+
}
|
|
333
|
+
catch (preserveErr) {
|
|
334
|
+
warn(MODULE, `Could not preserve rejected state file: ${errorMessage(preserveErr)}`);
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
319
338
|
/**
|
|
320
339
|
* Load state from file, or create initial state if none exists.
|
|
321
340
|
* If the main state file is corrupted, attempts to restore from the most recent backup.
|
|
322
341
|
* Performs migration from legacy ./data/ location if needed.
|
|
323
|
-
* @returns Object with the loaded state
|
|
342
|
+
* @returns Object with the loaded state, the file's mtime (for change detection),
|
|
343
|
+
* and recovery details when the main file was rejected.
|
|
344
|
+
* @throws Error when the state file exists but cannot be read due to a
|
|
345
|
+
* permission error (EACCES/EPERM) — that is an environment problem, not
|
|
346
|
+
* corruption, so restore-or-fresh would mask it and risk clobbering data.
|
|
324
347
|
*/
|
|
325
348
|
export function loadState() {
|
|
326
349
|
// Try to migrate from legacy location first
|
|
@@ -355,21 +378,16 @@ export function loadState() {
|
|
|
355
378
|
warn(MODULE, 'Attempting to restore from backup...');
|
|
356
379
|
debug(MODULE, 'Full validation errors:', parsed.error.issues);
|
|
357
380
|
// Preserve the rejected state file so the user can recover
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
fs.copyFileSync(statePath, rejectedPath);
|
|
361
|
-
warn(MODULE, `Previous state preserved at: ${rejectedPath}`);
|
|
362
|
-
}
|
|
363
|
-
catch (preserveErr) {
|
|
364
|
-
warn(MODULE, `Could not preserve rejected state file: ${errorMessage(preserveErr)}`);
|
|
365
|
-
}
|
|
381
|
+
const rejectedPath = preserveRejectedStateFile(statePath);
|
|
382
|
+
const reason = `Invalid state file structure: ${summary}`;
|
|
366
383
|
const restoredState = tryRestoreFromBackup();
|
|
367
384
|
if (restoredState) {
|
|
368
385
|
const mtimeMs = safeGetMtimeMs(statePath);
|
|
369
|
-
|
|
386
|
+
const recovery = { source: 'backup', rejectedPath, reason };
|
|
387
|
+
return { state: restoredState, mtimeMs, recovery };
|
|
370
388
|
}
|
|
371
389
|
warn(MODULE, 'No valid backup found, starting fresh');
|
|
372
|
-
return { state: createFreshState(), mtimeMs: 0 };
|
|
390
|
+
return { state: createFreshState(), mtimeMs: 0, recovery: { source: 'fresh', rejectedPath, reason } };
|
|
373
391
|
}
|
|
374
392
|
// Save migrated state only after validation succeeds
|
|
375
393
|
if (wasMigrated) {
|
|
@@ -408,14 +426,31 @@ export function loadState() {
|
|
|
408
426
|
}
|
|
409
427
|
}
|
|
410
428
|
catch (error) {
|
|
429
|
+
// Permission errors are environmental, not corruption — restoring a
|
|
430
|
+
// backup or starting fresh here would mask the real problem and risk
|
|
431
|
+
// clobbering a perfectly valid state file. Rethrow with guidance (#1371).
|
|
432
|
+
const code = error?.code;
|
|
433
|
+
if (code === 'EACCES' || code === 'EPERM') {
|
|
434
|
+
throw new Error(`Permission denied reading state file (${code}): ${errorMessage(error)}. ` +
|
|
435
|
+
'This is an environment problem, not state corruption. Check the ownership and ' +
|
|
436
|
+
'permissions of ~/.oss-autopilot/state.json (e.g. run `ls -l ~/.oss-autopilot/state.json` ' +
|
|
437
|
+
'and fix with chown/chmod), then retry.', { cause: error });
|
|
438
|
+
}
|
|
411
439
|
warn(MODULE, 'Error loading state:', error);
|
|
412
440
|
warn(MODULE, 'Attempting to restore from backup...');
|
|
441
|
+
// Preserve the unparseable file before tryRestoreFromBackup() overwrites
|
|
442
|
+
// it with restored backup contents (#1371). Only attempt when the file
|
|
443
|
+
// actually exists — the error may have come from elsewhere in the block.
|
|
444
|
+
const rejectedPath = fs.existsSync(statePath) ? preserveRejectedStateFile(statePath) : null;
|
|
445
|
+
const reason = `Error loading state: ${errorMessage(error)}`;
|
|
413
446
|
const restoredState = tryRestoreFromBackup();
|
|
414
447
|
if (restoredState) {
|
|
415
448
|
const mtimeMs = safeGetMtimeMs(statePath);
|
|
416
|
-
|
|
449
|
+
const recovery = { source: 'backup', rejectedPath, reason };
|
|
450
|
+
return { state: restoredState, mtimeMs, recovery };
|
|
417
451
|
}
|
|
418
452
|
warn(MODULE, 'No valid backup found, starting fresh');
|
|
453
|
+
return { state: createFreshState(), mtimeMs: 0, recovery: { source: 'fresh', rejectedPath, reason } };
|
|
419
454
|
}
|
|
420
455
|
debug(MODULE, 'No existing state found, initializing...');
|
|
421
456
|
return { state: createFreshState(), mtimeMs: 0 };
|
package/dist/core/state.d.ts
CHANGED
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
* and scoring logic to repo-score-manager.ts.
|
|
5
5
|
*/
|
|
6
6
|
import { AgentState, TrackedIssue, RepoScore, RepoScoreUpdate, DailyDigest, LocalRepoCache, StatusOverride, FetchedPRStatus, StoredMergedPR, StoredClosedPR } from './types.js';
|
|
7
|
+
import { type LoadRecoveryInfo } from './state-persistence.js';
|
|
7
8
|
import type { Stats } from './repo-score-manager.js';
|
|
8
9
|
import { GistStateStore } from './gist-state-store.js';
|
|
9
10
|
export { acquireLock, releaseLock, atomicWriteFileSync } from './state-persistence.js';
|
|
11
|
+
export type { LoadRecoveryInfo } from './state-persistence.js';
|
|
10
12
|
export type { Stats } from './repo-score-manager.js';
|
|
11
13
|
/**
|
|
12
14
|
* Push state to the backing Gist when Gist mode is active. Best-effort:
|
|
@@ -17,8 +19,15 @@ export type { Stats } from './repo-score-manager.js';
|
|
|
17
19
|
* Intended for state-mutating PR-flow commands (shelve / unshelve / move /
|
|
18
20
|
* dismiss / undismiss / claim) so Gist-sync users don't see day-long drift
|
|
19
21
|
* between machines waiting for the next `daily` checkpoint. See issue #1036.
|
|
22
|
+
*
|
|
23
|
+
* @returns `null` when the push succeeded (or Gist mode is off); otherwise a
|
|
24
|
+
* human-readable warning the caller should surface in its structured output.
|
|
25
|
+
* `checkpoint()` resolves `false` without throwing when the push failed
|
|
26
|
+
* after its retry — previously that was discarded everywhere except
|
|
27
|
+
* `state --sync`, so a failed cross-machine sync reported clean success
|
|
28
|
+
* (#1370). stderr `warn()` alone is invisible to MCP/dashboard consumers.
|
|
20
29
|
*/
|
|
21
|
-
export declare function maybeCheckpoint(stateManager: StateManager, callerModule: string): Promise<
|
|
30
|
+
export declare function maybeCheckpoint(stateManager: StateManager, callerModule: string): Promise<string | null>;
|
|
22
31
|
/**
|
|
23
32
|
* Singleton manager for persistent agent state stored in ~/.oss-autopilot/state.json.
|
|
24
33
|
*
|
|
@@ -54,6 +63,7 @@ export declare class StateManager {
|
|
|
54
63
|
protected gistDegraded: boolean;
|
|
55
64
|
private staleness;
|
|
56
65
|
private lastSuccessfulRefreshAt;
|
|
66
|
+
private loadRecovery;
|
|
57
67
|
/**
|
|
58
68
|
* Create a new StateManager instance.
|
|
59
69
|
* @param inMemoryOnly - When true, state is held only in memory and never read from or
|
|
@@ -157,6 +167,13 @@ export declare class StateManager {
|
|
|
157
167
|
* Returns true if state was reloaded, false if unchanged or in-memory mode.
|
|
158
168
|
*/
|
|
159
169
|
reloadIfChanged(): boolean;
|
|
170
|
+
/**
|
|
171
|
+
* Recovery details from the most recent state load, or null when the last
|
|
172
|
+
* load read the state file cleanly (or no load from disk has happened,
|
|
173
|
+
* e.g. in-memory mode). Set when loadState() fell back to a backup or
|
|
174
|
+
* fresh state because the main file was corrupt or invalid (#1371).
|
|
175
|
+
*/
|
|
176
|
+
getLoadRecovery(): LoadRecoveryInfo | null;
|
|
160
177
|
/**
|
|
161
178
|
* Re-fetch state from the backing Gist (if in Gist mode).
|
|
162
179
|
* Throttled to once per 30 seconds by GistStateStore. Returns true if state was refreshed.
|
package/dist/core/state.js
CHANGED
|
@@ -42,15 +42,30 @@ function filterValidUrlEntries(entries, kind) {
|
|
|
42
42
|
* Intended for state-mutating PR-flow commands (shelve / unshelve / move /
|
|
43
43
|
* dismiss / undismiss / claim) so Gist-sync users don't see day-long drift
|
|
44
44
|
* between machines waiting for the next `daily` checkpoint. See issue #1036.
|
|
45
|
+
*
|
|
46
|
+
* @returns `null` when the push succeeded (or Gist mode is off); otherwise a
|
|
47
|
+
* human-readable warning the caller should surface in its structured output.
|
|
48
|
+
* `checkpoint()` resolves `false` without throwing when the push failed
|
|
49
|
+
* after its retry — previously that was discarded everywhere except
|
|
50
|
+
* `state --sync`, so a failed cross-machine sync reported clean success
|
|
51
|
+
* (#1370). stderr `warn()` alone is invisible to MCP/dashboard consumers.
|
|
45
52
|
*/
|
|
46
53
|
export async function maybeCheckpoint(stateManager, callerModule) {
|
|
47
54
|
if (!stateManager.isGistMode())
|
|
48
|
-
return;
|
|
55
|
+
return null;
|
|
49
56
|
try {
|
|
50
|
-
await stateManager.checkpoint();
|
|
57
|
+
const pushed = await stateManager.checkpoint();
|
|
58
|
+
if (!pushed) {
|
|
59
|
+
const msg = 'Gist checkpoint push failed after retry; the local mutation is saved and will sync on the next successful push';
|
|
60
|
+
warn(callerModule, msg);
|
|
61
|
+
return msg;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
51
64
|
}
|
|
52
65
|
catch (err) {
|
|
53
|
-
|
|
66
|
+
const msg = `Gist checkpoint failed (local mutation succeeded, will retry on next push): ${errorMessage(err)}`;
|
|
67
|
+
warn(callerModule, msg);
|
|
68
|
+
return msg;
|
|
54
69
|
}
|
|
55
70
|
}
|
|
56
71
|
export class StateManager {
|
|
@@ -63,6 +78,7 @@ export class StateManager {
|
|
|
63
78
|
gistDegraded = false;
|
|
64
79
|
staleness = null;
|
|
65
80
|
lastSuccessfulRefreshAt = null;
|
|
81
|
+
loadRecovery = null;
|
|
66
82
|
/**
|
|
67
83
|
* Create a new StateManager instance.
|
|
68
84
|
* @param inMemoryOnly - When true, state is held only in memory and never read from or
|
|
@@ -78,6 +94,7 @@ export class StateManager {
|
|
|
78
94
|
const result = loadState();
|
|
79
95
|
this.state = result.state;
|
|
80
96
|
this.lastLoadedMtimeMs = result.mtimeMs;
|
|
97
|
+
this.loadRecovery = result.recovery ?? null;
|
|
81
98
|
this.tryReconcilePRCounts();
|
|
82
99
|
}
|
|
83
100
|
}
|
|
@@ -97,9 +114,11 @@ export class StateManager {
|
|
|
97
114
|
// Check if local state exists for migration
|
|
98
115
|
const statePath = getStatePath();
|
|
99
116
|
let result;
|
|
117
|
+
let localRecovery = null;
|
|
100
118
|
if (fs.existsSync(statePath)) {
|
|
101
119
|
// Existing user: load local state and migrate it into the Gist if no Gist exists yet.
|
|
102
120
|
const localStateResult = loadState();
|
|
121
|
+
localRecovery = localStateResult.recovery ?? null;
|
|
103
122
|
const migrationResult = await gistStore.bootstrapWithMigration(localStateResult.state);
|
|
104
123
|
result = migrationResult;
|
|
105
124
|
// If a new Gist was just created from local state, rename the local file
|
|
@@ -126,6 +145,9 @@ export class StateManager {
|
|
|
126
145
|
manager.gistStore = gistStore;
|
|
127
146
|
manager.gistDegraded = result.degraded ?? false;
|
|
128
147
|
manager.inMemoryOnly = false; // re-enable persistence
|
|
148
|
+
// Surface a recovery that happened while loading the local file for Gist
|
|
149
|
+
// migration — the recovered (or fresh) state is what got migrated.
|
|
150
|
+
manager.loadRecovery = localRecovery;
|
|
129
151
|
// Seed the staleness marker if bootstrap fell back to the local cache —
|
|
130
152
|
// a `daily` running on a cron right after this start needs to know.
|
|
131
153
|
if (result.degraded) {
|
|
@@ -335,9 +357,19 @@ export class StateManager {
|
|
|
335
357
|
return false;
|
|
336
358
|
this.state = result.state;
|
|
337
359
|
this.lastLoadedMtimeMs = result.mtimeMs;
|
|
360
|
+
this.loadRecovery = result.recovery ?? null;
|
|
338
361
|
this.tryReconcilePRCounts();
|
|
339
362
|
return true;
|
|
340
363
|
}
|
|
364
|
+
/**
|
|
365
|
+
* Recovery details from the most recent state load, or null when the last
|
|
366
|
+
* load read the state file cleanly (or no load from disk has happened,
|
|
367
|
+
* e.g. in-memory mode). Set when loadState() fell back to a backup or
|
|
368
|
+
* fresh state because the main file was corrupt or invalid (#1371).
|
|
369
|
+
*/
|
|
370
|
+
getLoadRecovery() {
|
|
371
|
+
return this.loadRecovery;
|
|
372
|
+
}
|
|
341
373
|
/**
|
|
342
374
|
* Re-fetch state from the backing Gist (if in Gist mode).
|
|
343
375
|
* Throttled to once per 30 seconds by GistStateStore. Returns true if state was refreshed.
|
|
@@ -13,9 +13,22 @@
|
|
|
13
13
|
* 3. `extractFromFence(wrapUntrustedContent(x, label))` returns `x`
|
|
14
14
|
* unchanged for any input — the wrapping is lossless.
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* Runtime wiring (#1372) — body fields are fenced at the last serialization
|
|
17
|
+
* boundary into agent-facing output:
|
|
18
|
+
*
|
|
19
|
+
* - `runComments` (commands/comments.ts): review / inline / discussion
|
|
20
|
+
* comment bodies in the `comments` CLI `--json` + MCP tool output.
|
|
21
|
+
* - `fetchPRCommentBundle` (core/pr-comments-fetcher.ts): bundle bodies
|
|
22
|
+
* feeding `guidelines fetch-corpus` (agent-only consumer).
|
|
23
|
+
* - `toDailyOutput` (commands/daily.ts): `commentedIssues[].lastResponseBody`
|
|
24
|
+
* / `userLastCommentBody` in the daily/startup JSON. The producer
|
|
25
|
+
* (`issue-conversation.ts`) stays raw because the dashboard SPA and the
|
|
26
|
+
* CLI text renderers consume the same objects.
|
|
27
|
+
*
|
|
28
|
+
* Human-facing display paths unwrap with {@link safeExtractFromFence}.
|
|
29
|
+
* This pairs with the agent-side guidance in `workflows/reference.md`
|
|
30
|
+
* ("Prompt Injection Awareness"), which tells the LLM to treat anything
|
|
31
|
+
* inside `<github-content>` blocks as data, not instructions.
|
|
19
32
|
*
|
|
20
33
|
* Non-goals:
|
|
21
34
|
* - This is NOT a content filter. We do not detect or strip prompt-
|
|
@@ -46,3 +59,11 @@ export declare function wrapUntrustedContent(text: string, label: string, meta?:
|
|
|
46
59
|
* markdown with this; it's for tests + symmetric reasoning only.
|
|
47
60
|
*/
|
|
48
61
|
export declare function extractFromFence(fenced: string): string;
|
|
62
|
+
/**
|
|
63
|
+
* Tolerant unwrap for human-facing display paths (CLI text mode). Returns
|
|
64
|
+
* the original body when `text` is a well-formed fence, and `text` unchanged
|
|
65
|
+
* otherwise. Display code must not crash on unfenced input (older state,
|
|
66
|
+
* fields that were never wrapped), so unlike {@link extractFromFence} this
|
|
67
|
+
* never throws.
|
|
68
|
+
*/
|
|
69
|
+
export declare function safeExtractFromFence(text: string): string;
|
|
@@ -13,9 +13,22 @@
|
|
|
13
13
|
* 3. `extractFromFence(wrapUntrustedContent(x, label))` returns `x`
|
|
14
14
|
* unchanged for any input — the wrapping is lossless.
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* Runtime wiring (#1372) — body fields are fenced at the last serialization
|
|
17
|
+
* boundary into agent-facing output:
|
|
18
|
+
*
|
|
19
|
+
* - `runComments` (commands/comments.ts): review / inline / discussion
|
|
20
|
+
* comment bodies in the `comments` CLI `--json` + MCP tool output.
|
|
21
|
+
* - `fetchPRCommentBundle` (core/pr-comments-fetcher.ts): bundle bodies
|
|
22
|
+
* feeding `guidelines fetch-corpus` (agent-only consumer).
|
|
23
|
+
* - `toDailyOutput` (commands/daily.ts): `commentedIssues[].lastResponseBody`
|
|
24
|
+
* / `userLastCommentBody` in the daily/startup JSON. The producer
|
|
25
|
+
* (`issue-conversation.ts`) stays raw because the dashboard SPA and the
|
|
26
|
+
* CLI text renderers consume the same objects.
|
|
27
|
+
*
|
|
28
|
+
* Human-facing display paths unwrap with {@link safeExtractFromFence}.
|
|
29
|
+
* This pairs with the agent-side guidance in `workflows/reference.md`
|
|
30
|
+
* ("Prompt Injection Awareness"), which tells the LLM to treat anything
|
|
31
|
+
* inside `<github-content>` blocks as data, not instructions.
|
|
19
32
|
*
|
|
20
33
|
* Non-goals:
|
|
21
34
|
* - This is NOT a content filter. We do not detect or strip prompt-
|
|
@@ -104,3 +117,18 @@ export function extractFromFence(fenced) {
|
|
|
104
117
|
.split(AMP_ESCAPE)
|
|
105
118
|
.join('&');
|
|
106
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Tolerant unwrap for human-facing display paths (CLI text mode). Returns
|
|
122
|
+
* the original body when `text` is a well-formed fence, and `text` unchanged
|
|
123
|
+
* otherwise. Display code must not crash on unfenced input (older state,
|
|
124
|
+
* fields that were never wrapped), so unlike {@link extractFromFence} this
|
|
125
|
+
* never throws.
|
|
126
|
+
*/
|
|
127
|
+
export function safeExtractFromFence(text) {
|
|
128
|
+
try {
|
|
129
|
+
return extractFromFence(text);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return text;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -50,7 +50,7 @@ export interface CompactRepoGroup {
|
|
|
50
50
|
* See `DailyWarning` and issue #1042 for the rationale — keeping this a
|
|
51
51
|
* fixed union so downstream consumers can switch on it without drift.
|
|
52
52
|
*/
|
|
53
|
-
export type DailyWarningPhase = 'fetch' | 'repo-scores' | 'analytics' | 'scout-sync' | 'partition' | 'dismiss-filter' | 'gist-checkpoint' | 'gist-staleness';
|
|
53
|
+
export type DailyWarningPhase = 'fetch' | 'repo-scores' | 'analytics' | 'scout-sync' | 'partition' | 'dismiss-filter' | 'gist-checkpoint' | 'gist-staleness' | 'state-load';
|
|
54
54
|
/**
|
|
55
55
|
* A single non-fatal failure surfaced from the `daily` pipeline. Unlike
|
|
56
56
|
* `PRCheckFailure` (which is scoped to per-PR fetch errors), this covers
|
|
@@ -176,6 +176,7 @@ export declare const StatusOutputSchema: z.ZodObject<{
|
|
|
176
176
|
"dismiss-filter": "dismiss-filter";
|
|
177
177
|
"gist-checkpoint": "gist-checkpoint";
|
|
178
178
|
"gist-staleness": "gist-staleness";
|
|
179
|
+
"state-load": "state-load";
|
|
179
180
|
}>;
|
|
180
181
|
operation: z.ZodString;
|
|
181
182
|
message: z.ZodString;
|
|
@@ -378,6 +379,7 @@ export declare const DailyOutputSchema: z.ZodObject<{
|
|
|
378
379
|
"dismiss-filter": "dismiss-filter";
|
|
379
380
|
"gist-checkpoint": "gist-checkpoint";
|
|
380
381
|
"gist-staleness": "gist-staleness";
|
|
382
|
+
"state-load": "state-load";
|
|
381
383
|
}>;
|
|
382
384
|
operation: z.ZodString;
|
|
383
385
|
message: z.ZodString;
|
|
@@ -518,6 +520,7 @@ export declare const CompactDailyOutputSchema: z.ZodObject<{
|
|
|
518
520
|
"dismiss-filter": "dismiss-filter";
|
|
519
521
|
"gist-checkpoint": "gist-checkpoint";
|
|
520
522
|
"gist-staleness": "gist-staleness";
|
|
523
|
+
"state-load": "state-load";
|
|
521
524
|
}>;
|
|
522
525
|
operation: z.ZodString;
|
|
523
526
|
message: z.ZodString;
|
|
@@ -798,6 +801,7 @@ export declare const PostOutputSchema: z.ZodObject<{
|
|
|
798
801
|
export declare const ClaimOutputSchema: z.ZodObject<{
|
|
799
802
|
commentUrl: z.ZodString;
|
|
800
803
|
issueUrl: z.ZodString;
|
|
804
|
+
gistSyncWarning: z.ZodOptional<z.ZodString>;
|
|
801
805
|
}, z.core.$strip>;
|
|
802
806
|
export declare const InitOutputSchema: z.ZodObject<{
|
|
803
807
|
username: z.ZodString;
|
|
@@ -865,6 +869,7 @@ export declare const MoveOutputSchema: z.ZodObject<{
|
|
|
865
869
|
shelved: "shelved";
|
|
866
870
|
}>;
|
|
867
871
|
description: z.ZodString;
|
|
872
|
+
gistSyncWarning: z.ZodOptional<z.ZodString>;
|
|
868
873
|
}, z.core.$strip>;
|
|
869
874
|
export declare const PRTemplateOutputSchema: z.ZodObject<{
|
|
870
875
|
template: z.ZodNullable<z.ZodString>;
|
|
@@ -899,6 +904,7 @@ export declare const RepoVetOutputSchema: z.ZodObject<{
|
|
|
899
904
|
lastCommitISO: z.ZodNullable<z.ZodString>;
|
|
900
905
|
contributorsLast90d: z.ZodNumber;
|
|
901
906
|
lastReleaseISO: z.ZodNullable<z.ZodString>;
|
|
907
|
+
releasesIncomplete: z.ZodOptional<z.ZodBoolean>;
|
|
902
908
|
}, z.core.$strip>;
|
|
903
909
|
communityHealth: z.ZodObject<{
|
|
904
910
|
contributing: z.ZodBoolean;
|
|
@@ -1398,6 +1404,8 @@ export interface PostOutput {
|
|
|
1398
1404
|
export interface ClaimOutput {
|
|
1399
1405
|
commentUrl: string;
|
|
1400
1406
|
issueUrl: string;
|
|
1407
|
+
/** Set when the post-mutation Gist checkpoint failed; the local mutation succeeded (#1370). */
|
|
1408
|
+
gistSyncWarning?: string;
|
|
1401
1409
|
}
|
|
1402
1410
|
/** Info about a local git clone (#84) */
|
|
1403
1411
|
export interface LocalRepoInfo {
|
package/dist/formatters/json.js
CHANGED
|
@@ -87,6 +87,7 @@ const DailyWarningPhaseSchema = z.enum([
|
|
|
87
87
|
'dismiss-filter',
|
|
88
88
|
'gist-checkpoint',
|
|
89
89
|
'gist-staleness',
|
|
90
|
+
'state-load',
|
|
90
91
|
]);
|
|
91
92
|
const DailyWarningSchema = z.object({
|
|
92
93
|
phase: DailyWarningPhaseSchema,
|
|
@@ -417,6 +418,8 @@ export const PostOutputSchema = z.object({
|
|
|
417
418
|
export const ClaimOutputSchema = z.object({
|
|
418
419
|
commentUrl: z.string(),
|
|
419
420
|
issueUrl: z.string(),
|
|
421
|
+
// Post-mutation Gist checkpoint failure (#1370). Optional: absent on clean runs.
|
|
422
|
+
gistSyncWarning: z.string().optional(),
|
|
420
423
|
});
|
|
421
424
|
export const InitOutputSchema = z.object({
|
|
422
425
|
username: z.string(),
|
|
@@ -492,6 +495,8 @@ export const MoveOutputSchema = z.object({
|
|
|
492
495
|
url: z.string(),
|
|
493
496
|
target: z.enum(['attention', 'waiting', 'shelved', 'auto']),
|
|
494
497
|
description: z.string(),
|
|
498
|
+
// Post-mutation Gist checkpoint failure (#1370). Optional: absent on clean runs.
|
|
499
|
+
gistSyncWarning: z.string().optional(),
|
|
495
500
|
});
|
|
496
501
|
export const PRTemplateOutputSchema = z.object({
|
|
497
502
|
template: z.string().nullable(),
|
|
@@ -535,6 +540,13 @@ export const RepoVetOutputSchema = z.object({
|
|
|
535
540
|
lastCommitISO: z.string().nullable(),
|
|
536
541
|
contributorsLast90d: z.number().int().nonnegative(),
|
|
537
542
|
lastReleaseISO: z.string().nullable(),
|
|
543
|
+
/**
|
|
544
|
+
* True when the release listing failed for a non-404 reason (5xx,
|
|
545
|
+
* network), so a `null` lastReleaseISO means "could not determine"
|
|
546
|
+
* rather than "confirmed no releases". The release-list analogue of
|
|
547
|
+
* `communityHealth.incomplete` (#1373).
|
|
548
|
+
*/
|
|
549
|
+
releasesIncomplete: z.boolean().optional(),
|
|
538
550
|
}),
|
|
539
551
|
communityHealth: z.object({
|
|
540
552
|
contributing: z.boolean(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oss-autopilot/core",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.11.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.
|
|
58
|
-
"commander": "^
|
|
57
|
+
"@oss-scout/core": "^0.11.0",
|
|
58
|
+
"commander": "^15.0.0",
|
|
59
59
|
"zod": "^4.4.3"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@types/node": "^25.
|
|
63
|
-
"@vitest/coverage-v8": "^4.1.
|
|
62
|
+
"@types/node": "^25.9.1",
|
|
63
|
+
"@vitest/coverage-v8": "^4.1.8",
|
|
64
64
|
"esbuild": "^0.28.0",
|
|
65
|
-
"tsx": "^4.
|
|
65
|
+
"tsx": "^4.22.4",
|
|
66
66
|
"typedoc": "^0.28.19",
|
|
67
67
|
"typescript": "^5.9.3",
|
|
68
|
-
"vitest": "^4.1.
|
|
68
|
+
"vitest": "^4.1.8"
|
|
69
69
|
},
|
|
70
70
|
"scripts": {
|
|
71
71
|
"build": "tsc",
|