@oss-autopilot/core 3.13.4 → 3.14.1
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/README.md +3 -3
- package/dist/cli-registry.js +59 -84
- package/dist/cli.bundle.cjs +112 -109
- package/dist/cli.js +5 -4
- package/dist/commands/comments.js +44 -10
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +50 -2
- package/dist/commands/curated-list.d.ts +17 -0
- package/dist/commands/curated-list.js +25 -0
- package/dist/commands/daily.d.ts +7 -1
- package/dist/commands/daily.js +136 -57
- package/dist/commands/dashboard-cache.d.ts +69 -0
- package/dist/commands/dashboard-cache.js +219 -0
- package/dist/commands/dashboard-data.d.ts +18 -10
- package/dist/commands/dashboard-data.js +58 -8
- package/dist/commands/dashboard-gist-sync.d.ts +93 -0
- package/dist/commands/dashboard-gist-sync.js +237 -0
- package/dist/commands/dashboard-server.d.ts +6 -10
- package/dist/commands/dashboard-server.js +181 -347
- package/dist/commands/features.js +6 -0
- package/dist/commands/guidelines.d.ts +6 -0
- package/dist/commands/guidelines.js +7 -0
- package/dist/commands/index.d.ts +2 -5
- package/dist/commands/index.js +2 -4
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +7 -1
- package/dist/commands/list-mark-done.js +6 -21
- package/dist/commands/list-move-tier.js +3 -5
- package/dist/commands/locate-issue-list.d.ts +25 -0
- package/dist/commands/locate-issue-list.js +67 -0
- package/dist/commands/merge-loop.d.ts +63 -0
- package/dist/commands/merge-loop.js +157 -0
- package/dist/commands/repo-vet.js +40 -1
- package/dist/commands/scout-bridge.d.ts +35 -2
- package/dist/commands/scout-bridge.js +65 -13
- package/dist/commands/search.d.ts +4 -6
- package/dist/commands/search.js +58 -11
- package/dist/commands/setup.d.ts +2 -0
- package/dist/commands/setup.js +56 -2
- package/dist/commands/skip-file-parser.d.ts +23 -0
- package/dist/commands/skip-file-parser.js +23 -10
- package/dist/commands/startup.d.ts +1 -6
- package/dist/commands/startup.js +25 -59
- package/dist/commands/track.d.ts +2 -2
- package/dist/commands/track.js +2 -2
- package/dist/commands/vet-list.d.ts +6 -6
- package/dist/commands/vet-list.js +194 -65
- package/dist/core/config-registry.js +36 -0
- package/dist/core/daily-logic.d.ts +25 -2
- package/dist/core/daily-logic.js +58 -3
- package/dist/core/gist-health.d.ts +81 -0
- package/dist/core/gist-health.js +39 -0
- package/dist/core/gist-state-store.d.ts +3 -1
- package/dist/core/gist-state-store.js +7 -2
- package/dist/core/github-stats.d.ts +2 -2
- package/dist/core/github-stats.js +20 -4
- package/dist/core/index.d.ts +5 -4
- package/dist/core/index.js +5 -4
- package/dist/core/issue-conversation.js +8 -2
- package/dist/core/issue-grading.d.ts +9 -0
- package/dist/core/issue-grading.js +9 -0
- package/dist/core/issue-verification.d.ts +39 -0
- package/dist/core/issue-verification.js +48 -0
- package/dist/core/pagination.d.ts +27 -0
- package/dist/core/pagination.js +23 -5
- package/dist/core/pr-comments-fetcher.d.ts +7 -0
- package/dist/core/pr-comments-fetcher.js +19 -8
- package/dist/core/pr-monitor.d.ts +2 -0
- package/dist/core/pr-monitor.js +26 -9
- package/dist/core/repo-score-manager.d.ts +2 -2
- package/dist/core/repo-score-manager.js +3 -3
- package/dist/core/repo-vet.d.ts +2 -2
- package/dist/core/repo-vet.js +1 -1
- package/dist/core/review-analysis.d.ts +19 -0
- package/dist/core/review-analysis.js +28 -0
- package/dist/core/state-schema.d.ts +43 -6
- package/dist/core/state-schema.js +81 -4
- package/dist/core/state.d.ts +36 -5
- package/dist/core/state.js +177 -28
- package/dist/core/strategy.js +6 -5
- package/dist/core/types.d.ts +8 -0
- package/dist/core/untrusted-content.d.ts +45 -0
- package/dist/core/untrusted-content.js +54 -0
- package/dist/formatters/json.d.ts +120 -12
- package/dist/formatters/json.js +55 -2
- package/package.json +2 -2
- package/dist/commands/shelve.d.ts +0 -45
- package/dist/commands/shelve.js +0 -54
|
@@ -4,7 +4,20 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { createScout } from '@oss-scout/core';
|
|
6
6
|
import { getStateManager, isLinkedPRStalled, requireGitHubToken } from '../core/index.js';
|
|
7
|
-
import {
|
|
7
|
+
import { computeStrategy } from '../core/strategy.js';
|
|
8
|
+
import { loadSkippedIssuesDetailed } from './skip-file-parser.js';
|
|
9
|
+
/**
|
|
10
|
+
* Fraction of search slots reserved for candidates that matched neither
|
|
11
|
+
* strategy-preferred languages nor repos (#1244). Counterweight against
|
|
12
|
+
* echo-chamber bias: without it, strategy-boosted searches return more of
|
|
13
|
+
* what already merged, which merges more of the same, and the profile
|
|
14
|
+
* narrows over time. Scout clamps to [0, 1]; 0.2 is the issue's proposal.
|
|
15
|
+
*
|
|
16
|
+
* Lives here (not `search.ts`, which re-exports it) since #1464: the same
|
|
17
|
+
* ratio is baked into the `ScoutPreferences` built by {@link buildScoutState}
|
|
18
|
+
* so every scout surface shares one policy.
|
|
19
|
+
*/
|
|
20
|
+
export const SEARCH_DIVERSITY_RATIO = 0.2;
|
|
8
21
|
/**
|
|
9
22
|
* Convert scout 0.6.0's `LinkedPR` (separate `state` + `merged`) into the
|
|
10
23
|
* shape `classifyLinkedPR` expects (`state` already folded with `merged`).
|
|
@@ -56,10 +69,30 @@ export function buildCandidateLinkedPR(scoutLinkedPR) {
|
|
|
56
69
|
/**
|
|
57
70
|
* Build a ScoutState from the current AgentState.
|
|
58
71
|
* Maps oss-autopilot's config and state fields to oss-scout's state format.
|
|
72
|
+
*
|
|
73
|
+
* @param diagnostics - Optional collector for degradation signals (#1448);
|
|
74
|
+
* `skipListUnavailable` is set when the skipped-issues file exists but
|
|
75
|
+
* could not be read.
|
|
59
76
|
*/
|
|
60
|
-
export function buildScoutState() {
|
|
77
|
+
export function buildScoutState(diagnostics) {
|
|
61
78
|
const state = getStateManager().getState();
|
|
62
79
|
const { config } = state;
|
|
80
|
+
// Read failure (not absence) of the skip file means scout will see an empty
|
|
81
|
+
// skip list and may resurface explicitly-skipped issues — flag it so the
|
|
82
|
+
// search envelope can carry the signal instead of only stderr (#1448).
|
|
83
|
+
const skippedIssuesLoad = loadSkippedIssuesDetailed(config.skippedIssuesPath);
|
|
84
|
+
if (skippedIssuesLoad.readError !== undefined && diagnostics) {
|
|
85
|
+
diagnostics.skipListUnavailable = true;
|
|
86
|
+
}
|
|
87
|
+
// Strategy-derived personalization (#1464). `computeStrategy` returns null
|
|
88
|
+
// below the merged-PR floor — empty lists then read as "no bias" on scout's
|
|
89
|
+
// side. Baking the bias into the preferences (rather than only per-call
|
|
90
|
+
// search options, #1244) makes every scout surface share it: `search()`
|
|
91
|
+
// falls back to these when no per-call override is passed, and `features()`
|
|
92
|
+
// exposes no per-call bias knobs at all (scout 1.1.0 accepts only
|
|
93
|
+
// count/anchorThreshold/splitRatio/broad), so preferences are the only
|
|
94
|
+
// channel by which bias can reach the features path.
|
|
95
|
+
const strategy = computeStrategy(state);
|
|
63
96
|
return {
|
|
64
97
|
version: 1,
|
|
65
98
|
preferences: {
|
|
@@ -90,15 +123,31 @@ export function buildScoutState() {
|
|
|
90
123
|
// `--split-ratio` flags for overrides.
|
|
91
124
|
featuresAnchorThreshold: 3,
|
|
92
125
|
featuresSplitRatio: 0.6,
|
|
93
|
-
// Personalization-bias prefs
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
126
|
+
// Personalization-bias prefs (scout #1244 / #168 / #1464).
|
|
127
|
+
// preferLanguages/preferRepos mirror the strategy derivation the search
|
|
128
|
+
// command uses per-call; avoidRepos/boostIssueTypes are user settings
|
|
129
|
+
// (config-registry keys); diversityRatio matches the search policy.
|
|
130
|
+
preferLanguages: strategy?.recommendations.languages ?? [],
|
|
131
|
+
preferRepos: strategy?.recommendations.repos ?? [],
|
|
132
|
+
diversityRatio: SEARCH_DIVERSITY_RATIO,
|
|
133
|
+
avoidRepos: config.avoidRepos ?? [],
|
|
134
|
+
boostIssueTypes: config.boostIssueTypes ?? [],
|
|
101
135
|
},
|
|
136
|
+
// CONTRACT (#1458): repoScores is passed BY REFERENCE, not copied. Scout's
|
|
137
|
+
// updateRepoScore mutates entries of this shared object in place, so scout
|
|
138
|
+
// calls that touch scores (e.g. search's hasActiveMaintainers feedback,
|
|
139
|
+
// scout #167) write straight into the in-memory AgentState. Those writes
|
|
140
|
+
// reach disk only if a later StateManager mutation triggers a save —
|
|
141
|
+
// scout's own checkpoint() persists nothing under persistence:'provided'.
|
|
142
|
+
// Nothing may RELY on this alias for persistence: daily's former Phase 3.5
|
|
143
|
+
// did, and was deleted in #1458 (its record* calls were also dedup no-ops
|
|
144
|
+
// against the ledger Phase 3 had just written; see
|
|
145
|
+
// scout-bridge.repo-scores.test.ts for the pinning tests). Also note scout
|
|
146
|
+
// recalculates `score` with its own formula (linear merge bonus, no
|
|
147
|
+
// recency term), which differs from autopilot's repo-score-manager
|
|
148
|
+
// formula; daily Phase 2 recomputes every repo it touches with the
|
|
149
|
+
// autopilot formula each run, so the persisted history scores reflect
|
|
150
|
+
// autopilot's model.
|
|
102
151
|
repoScores: state.repoScores,
|
|
103
152
|
// Scout #117 added tombstones (deletion records for gist merge). Autopilot
|
|
104
153
|
// synthesizes a fresh ScoutState per operation and tracks no deletions, so
|
|
@@ -124,19 +173,22 @@ export function buildScoutState() {
|
|
|
124
173
|
openedAt: pr.createdAt,
|
|
125
174
|
})),
|
|
126
175
|
savedResults: [],
|
|
127
|
-
skippedIssues:
|
|
176
|
+
skippedIssues: skippedIssuesLoad.issues,
|
|
128
177
|
lastRunAt: state.lastRunAt,
|
|
129
178
|
};
|
|
130
179
|
}
|
|
131
180
|
/**
|
|
132
181
|
* Create an OssScout instance backed by the current AgentState.
|
|
133
182
|
* Uses 'provided' persistence so scout reads from oss-autopilot's state.
|
|
183
|
+
*
|
|
184
|
+
* @param diagnostics - Optional collector for degradation signals (#1448),
|
|
185
|
+
* forwarded to {@link buildScoutState}.
|
|
134
186
|
*/
|
|
135
|
-
export async function createAutopilotScout() {
|
|
187
|
+
export async function createAutopilotScout(diagnostics) {
|
|
136
188
|
const token = requireGitHubToken();
|
|
137
189
|
return createScout({
|
|
138
190
|
githubToken: token,
|
|
139
191
|
persistence: 'provided',
|
|
140
|
-
initialState: buildScoutState(),
|
|
192
|
+
initialState: buildScoutState(diagnostics),
|
|
141
193
|
});
|
|
142
194
|
}
|
|
@@ -11,13 +11,11 @@ export { type SearchOutput } from '../formatters/json.js';
|
|
|
11
11
|
*/
|
|
12
12
|
export declare const MAX_SEARCH_RESULTS = 100;
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* what already merged, which merges more of the same, and the profile
|
|
18
|
-
* narrows over time. Scout clamps to [0, 1]; 0.2 is the issue's proposal.
|
|
14
|
+
* Diversity-counterweight ratio (#1244). Moved to `scout-bridge.ts` in #1464
|
|
15
|
+
* (it is now also baked into the bridge-built `ScoutPreferences`); re-exported
|
|
16
|
+
* here so existing importers keep working.
|
|
19
17
|
*/
|
|
20
|
-
export
|
|
18
|
+
export { SEARCH_DIVERSITY_RATIO } from './scout-bridge.js';
|
|
21
19
|
interface SearchOptions {
|
|
22
20
|
maxResults: number;
|
|
23
21
|
}
|
package/dist/commands/search.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Search command
|
|
3
3
|
* Searches for new issues to work on via @oss-scout/core
|
|
4
4
|
*/
|
|
5
|
-
import { adaptScoutLinkedPR, buildCandidateLinkedPR, createAutopilotScout } from './scout-bridge.js';
|
|
5
|
+
import { adaptScoutLinkedPR, buildCandidateLinkedPR, createAutopilotScout, SEARCH_DIVERSITY_RATIO, } from './scout-bridge.js';
|
|
6
6
|
import { classifyLinkedPR, getStateManager } from '../core/index.js';
|
|
7
7
|
import { gradeFromCandidate } from '../core/issue-grading.js';
|
|
8
8
|
import { computeStrategy } from '../core/strategy.js';
|
|
@@ -15,13 +15,11 @@ const MODULE = 'search';
|
|
|
15
15
|
*/
|
|
16
16
|
export const MAX_SEARCH_RESULTS = 100;
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* what already merged, which merges more of the same, and the profile
|
|
22
|
-
* narrows over time. Scout clamps to [0, 1]; 0.2 is the issue's proposal.
|
|
18
|
+
* Diversity-counterweight ratio (#1244). Moved to `scout-bridge.ts` in #1464
|
|
19
|
+
* (it is now also baked into the bridge-built `ScoutPreferences`); re-exported
|
|
20
|
+
* here so existing importers keep working.
|
|
23
21
|
*/
|
|
24
|
-
export
|
|
22
|
+
export { SEARCH_DIVERSITY_RATIO } from './scout-bridge.js';
|
|
25
23
|
/**
|
|
26
24
|
* Search GitHub for contributable issues using multi-phase discovery.
|
|
27
25
|
*
|
|
@@ -53,25 +51,66 @@ function sanitizeViabilityScore(raw) {
|
|
|
53
51
|
}
|
|
54
52
|
return raw;
|
|
55
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Read a non-negative integer scout-delay override from an env var.
|
|
56
|
+
*
|
|
57
|
+
* Scout spaces its multi-phase `/search/issues` calls with an inter-phase
|
|
58
|
+
* delay (default 30s) and a broad-phase delay (default 90s) to stay under
|
|
59
|
+
* GitHub's secondary rate limit (see `buildScoutState`). Those delays make a
|
|
60
|
+
* single `search` invocation take ~100s, which is fine in normal use but makes
|
|
61
|
+
* the live-API e2e suite (`search.e2e.test.ts`, run only in the nightly
|
|
62
|
+
* workflow — see #1452) impractically slow. These env vars let that suite
|
|
63
|
+
* collapse the delays so the test completes in a realistic CI window.
|
|
64
|
+
*
|
|
65
|
+
* Returns `undefined` when the var is unset/empty or not a parseable
|
|
66
|
+
* non-negative integer, so scout falls back to its preference value and
|
|
67
|
+
* production behavior is unchanged. Only the live test/nightly run sets them.
|
|
68
|
+
*/
|
|
69
|
+
function readScoutDelayOverride(envVar) {
|
|
70
|
+
const raw = process.env[envVar];
|
|
71
|
+
if (raw === undefined || raw === '') {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
const parsed = Number(raw);
|
|
75
|
+
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
76
|
+
warn(MODULE, `Ignoring invalid ${envVar}="${raw}" (expected a non-negative integer); using scout's default delay`);
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
return parsed;
|
|
80
|
+
}
|
|
56
81
|
export async function runSearch(options) {
|
|
57
|
-
|
|
82
|
+
// Collect bridge-level degradation signals (#1448): an unreadable skip file
|
|
83
|
+
// means scout searched with an empty skip list, so explicitly-skipped
|
|
84
|
+
// issues may resurface — the envelope must say so, not just stderr.
|
|
85
|
+
const bridgeDiagnostics = {};
|
|
86
|
+
const scout = await createAutopilotScout(bridgeDiagnostics);
|
|
58
87
|
const stateManager = getStateManager();
|
|
59
88
|
// Derive personalization from local history (#1244). `computeStrategy`
|
|
60
89
|
// returns null below the merged-PR floor — in that case we pass nothing
|
|
61
|
-
// and scout
|
|
62
|
-
//
|
|
63
|
-
//
|
|
90
|
+
// and scout falls back to the bridge-built preferences, which derive the
|
|
91
|
+
// same values from the same state (#1464), so the sort is identical either
|
|
92
|
+
// way. The explicit per-call pass is kept for self-documentation. Once
|
|
93
|
+
// strategy data is available, scout boosts language/repo matches into a
|
|
94
|
+
// separate sort tier (still no filtering). avoidRepos/boostIssueTypes are
|
|
95
|
+
// not passed per-call: scout reads them from the bridge-built preferences.
|
|
64
96
|
const strategy = computeStrategy(stateManager.getState());
|
|
65
97
|
const preferLanguages = strategy?.recommendations.languages ?? undefined;
|
|
66
98
|
const preferRepos = strategy?.recommendations.repos ?? undefined;
|
|
67
99
|
if (preferLanguages?.length || preferRepos?.length) {
|
|
68
100
|
debug(MODULE, `Applying strategy bias to search: preferLanguages=${JSON.stringify(preferLanguages ?? [])}, preferRepos=${JSON.stringify(preferRepos ?? [])}`);
|
|
69
101
|
}
|
|
102
|
+
// Live-API test/nightly affordance (#1452): collapse scout's inter-phase
|
|
103
|
+
// delays so the e2e suite finishes in a realistic window. Unset in normal
|
|
104
|
+
// use → undefined → scout falls back to the 30s/90s preference defaults.
|
|
105
|
+
const interPhaseDelayMs = readScoutDelayOverride('OSS_AUTOPILOT_SCOUT_INTER_PHASE_DELAY_MS');
|
|
106
|
+
const broadPhaseDelayMs = readScoutDelayOverride('OSS_AUTOPILOT_SCOUT_BROAD_PHASE_DELAY_MS');
|
|
70
107
|
const result = await scout.search({
|
|
71
108
|
maxResults: options.maxResults,
|
|
72
109
|
preferLanguages,
|
|
73
110
|
preferRepos,
|
|
74
111
|
diversityRatio: SEARCH_DIVERSITY_RATIO,
|
|
112
|
+
...(interPhaseDelayMs !== undefined ? { interPhaseDelayMs } : {}),
|
|
113
|
+
...(broadPhaseDelayMs !== undefined ? { broadPhaseDelayMs } : {}),
|
|
75
114
|
});
|
|
76
115
|
// #1354: never surface issues the user already has an open PR for. Uses
|
|
77
116
|
// scout's structured linked-PR metadata when present; candidates without it
|
|
@@ -95,6 +134,11 @@ export async function runSearch(options) {
|
|
|
95
134
|
// the autopilot-tracked repoScore. Candidates without a repoScore
|
|
96
135
|
// receive 'F' — that's an honest signal for "we haven't seen this repo
|
|
97
136
|
// before" rather than a fabricated score.
|
|
137
|
+
//
|
|
138
|
+
// Note (#1465): repoScore here is the cached HISTORY score (the user's
|
|
139
|
+
// own merge outcomes — docs/repo-scores.md §History score), so this
|
|
140
|
+
// grade reflects history only; `vet` later re-grades the same issue
|
|
141
|
+
// with freshly fetched repo health and can legitimately disagree.
|
|
98
142
|
const grade = gradeFromCandidate({
|
|
99
143
|
repo: c.issue.repo,
|
|
100
144
|
projectHealth: {
|
|
@@ -156,5 +200,8 @@ export async function runSearch(options) {
|
|
|
156
200
|
if (result.rateLimitWarning) {
|
|
157
201
|
searchOutput.rateLimitWarning = result.rateLimitWarning;
|
|
158
202
|
}
|
|
203
|
+
if (bridgeDiagnostics.skipListUnavailable) {
|
|
204
|
+
searchOutput.skipListUnavailable = true;
|
|
205
|
+
}
|
|
159
206
|
return searchOutput;
|
|
160
207
|
}
|
package/dist/commands/setup.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export interface SetupSetOutput {
|
|
|
11
11
|
success: true;
|
|
12
12
|
settings: Record<string, string>;
|
|
13
13
|
warnings?: string[];
|
|
14
|
+
/** Set when the post-mutation Gist checkpoint failed; the local mutation succeeded (#1440). */
|
|
15
|
+
gistSyncWarning?: string;
|
|
14
16
|
}
|
|
15
17
|
export interface SetupCompleteOutput {
|
|
16
18
|
setupComplete: true;
|
package/dist/commands/setup.js
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
* Setup command
|
|
3
3
|
* Interactive setup / configuration
|
|
4
4
|
*/
|
|
5
|
-
import { getStateManager, DEFAULT_CONFIG, formatUnknownKeyError, getSetupKeys } from '../core/index.js';
|
|
5
|
+
import { getStateManager, DEFAULT_CONFIG, formatUnknownKeyError, getSetupKeys, maybeCheckpoint, } from '../core/index.js';
|
|
6
6
|
import { ValidationError } from '../core/errors.js';
|
|
7
7
|
import { validateGitHubUsername } from './validation.js';
|
|
8
8
|
import { PROJECT_CATEGORIES, ISSUE_SCOPES, DIFF_TOOLS, } from '../core/types.js';
|
|
9
|
+
const MODULE = 'setup';
|
|
9
10
|
/** Parse and validate a positive integer setting value. */
|
|
10
11
|
function parsePositiveInt(value, settingName) {
|
|
11
12
|
const parsed = Number(value);
|
|
@@ -168,6 +169,50 @@ export async function runSetup(options) {
|
|
|
168
169
|
results[key] = valid.length > 0 ? valid.join(', ') : '(empty)';
|
|
169
170
|
break;
|
|
170
171
|
}
|
|
172
|
+
case 'avoidRepos': {
|
|
173
|
+
// Same owner/repo validation as aiPolicyBlocklist: skip (and warn
|
|
174
|
+
// about) malformed entries instead of failing the whole set.
|
|
175
|
+
const entries = value
|
|
176
|
+
.split(',')
|
|
177
|
+
.map((r) => r.trim())
|
|
178
|
+
.filter(Boolean);
|
|
179
|
+
const valid = [];
|
|
180
|
+
const invalid = [];
|
|
181
|
+
for (const entry of entries) {
|
|
182
|
+
const normalized = entry.replace(/\s+/g, '');
|
|
183
|
+
if (/^[\w.-]+\/[\w.-]+$/.test(normalized)) {
|
|
184
|
+
valid.push(normalized);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
invalid.push(entry);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (invalid.length > 0) {
|
|
191
|
+
warnings.push(`Warning: Skipping invalid entries (expected "owner/repo" format): ${invalid.join(', ')}`);
|
|
192
|
+
}
|
|
193
|
+
if (valid.length === 0 && entries.length > 0) {
|
|
194
|
+
warnings.push('Warning: All entries were invalid. avoidRepos not updated.');
|
|
195
|
+
results[key] = '(all entries invalid)';
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
const dedupedRepos = [...new Set(valid)];
|
|
199
|
+
stateManager.updateConfig({ avoidRepos: dedupedRepos });
|
|
200
|
+
results[key] = dedupedRepos.length > 0 ? dedupedRepos.join(', ') : '(empty)';
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
case 'boostIssueTypes': {
|
|
204
|
+
// Free-form issue labels (scout matches them case-insensitively);
|
|
205
|
+
// an empty value clears the list.
|
|
206
|
+
const types = [
|
|
207
|
+
...new Set(value
|
|
208
|
+
.split(',')
|
|
209
|
+
.map((t) => t.trim())
|
|
210
|
+
.filter(Boolean)),
|
|
211
|
+
];
|
|
212
|
+
stateManager.updateConfig({ boostIssueTypes: types });
|
|
213
|
+
results[key] = types.length > 0 ? types.join(', ') : '(empty)';
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
171
216
|
case 'projectCategories': {
|
|
172
217
|
const categories = value
|
|
173
218
|
.split(',')
|
|
@@ -276,7 +321,16 @@ export async function runSetup(options) {
|
|
|
276
321
|
}
|
|
277
322
|
}
|
|
278
323
|
});
|
|
279
|
-
|
|
324
|
+
// Push the settings mutation to the Gist in gist mode (no-op locally).
|
|
325
|
+
// Without this the change only hits the local cache and the next
|
|
326
|
+
// bootstrap reverts it from the Gist (#1440).
|
|
327
|
+
const gistSyncWarning = await maybeCheckpoint(stateManager, MODULE);
|
|
328
|
+
return {
|
|
329
|
+
success: true,
|
|
330
|
+
settings: results,
|
|
331
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
332
|
+
...(gistSyncWarning ? { gistSyncWarning } : {}),
|
|
333
|
+
};
|
|
280
334
|
}
|
|
281
335
|
// Show setup status
|
|
282
336
|
if (config.setupComplete && !options.reset) {
|
|
@@ -15,11 +15,34 @@ import type { SkippedIssue } from '@oss-scout/core';
|
|
|
15
15
|
* pass through unchanged.
|
|
16
16
|
*/
|
|
17
17
|
export declare function parseSkippedIssuesContent(content: string): SkippedIssue[];
|
|
18
|
+
/** Result of {@link loadSkippedIssuesDetailed}: parsed entries plus read-failure signal (#1448). */
|
|
19
|
+
export interface SkippedIssuesLoadResult {
|
|
20
|
+
issues: SkippedIssue[];
|
|
21
|
+
/**
|
|
22
|
+
* Set when the file exists but could not be read (`issues` is `[]` in that
|
|
23
|
+
* case). Distinguishes "no skips configured" from "skip list unavailable" —
|
|
24
|
+
* without it, a read failure silently resurfaces explicitly-skipped issues
|
|
25
|
+
* in search results. Absent when the path is unset, the file does not
|
|
26
|
+
* exist, or the read succeeded.
|
|
27
|
+
*/
|
|
28
|
+
readError?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Read the skipped-issues file from disk and parse it, surfacing read
|
|
32
|
+
* failures to the caller (#1448).
|
|
33
|
+
* Returns `{ issues: [] }` (no `readError`) when `path` is undefined/empty or
|
|
34
|
+
* the file does not exist; `{ issues: [], readError }` when the read throws
|
|
35
|
+
* (a warning is also logged).
|
|
36
|
+
*/
|
|
37
|
+
export declare function loadSkippedIssuesDetailed(path: string | undefined): SkippedIssuesLoadResult;
|
|
18
38
|
/**
|
|
19
39
|
* Read the skipped-issues file from disk and parse it.
|
|
20
40
|
* Returns `[]` when:
|
|
21
41
|
* - `path` is undefined or empty,
|
|
22
42
|
* - the file does not exist,
|
|
23
43
|
* - the file cannot be read (a warning is logged).
|
|
44
|
+
*
|
|
45
|
+
* Callers that need to distinguish "unreadable" from "no skips" should use
|
|
46
|
+
* {@link loadSkippedIssuesDetailed} instead.
|
|
24
47
|
*/
|
|
25
48
|
export declare function loadSkippedIssues(path: string | undefined): SkippedIssue[];
|
|
@@ -69,24 +69,37 @@ export function parseSkippedIssuesContent(content) {
|
|
|
69
69
|
return results;
|
|
70
70
|
}
|
|
71
71
|
/**
|
|
72
|
-
* Read the skipped-issues file from disk and parse it
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
72
|
+
* Read the skipped-issues file from disk and parse it, surfacing read
|
|
73
|
+
* failures to the caller (#1448).
|
|
74
|
+
* Returns `{ issues: [] }` (no `readError`) when `path` is undefined/empty or
|
|
75
|
+
* the file does not exist; `{ issues: [], readError }` when the read throws
|
|
76
|
+
* (a warning is also logged).
|
|
77
77
|
*/
|
|
78
|
-
export function
|
|
78
|
+
export function loadSkippedIssuesDetailed(path) {
|
|
79
79
|
if (!path)
|
|
80
|
-
return [];
|
|
80
|
+
return { issues: [] };
|
|
81
81
|
if (!fs.existsSync(path))
|
|
82
|
-
return [];
|
|
82
|
+
return { issues: [] };
|
|
83
83
|
let content;
|
|
84
84
|
try {
|
|
85
85
|
content = fs.readFileSync(path, 'utf8');
|
|
86
86
|
}
|
|
87
87
|
catch (err) {
|
|
88
88
|
warn('skip-file-parser', `Failed to read skipped-issues file at ${path}: ${errorMessage(err)}`);
|
|
89
|
-
return [];
|
|
89
|
+
return { issues: [], readError: errorMessage(err) };
|
|
90
90
|
}
|
|
91
|
-
return parseSkippedIssuesContent(content);
|
|
91
|
+
return { issues: parseSkippedIssuesContent(content) };
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Read the skipped-issues file from disk and parse it.
|
|
95
|
+
* Returns `[]` when:
|
|
96
|
+
* - `path` is undefined or empty,
|
|
97
|
+
* - the file does not exist,
|
|
98
|
+
* - the file cannot be read (a warning is logged).
|
|
99
|
+
*
|
|
100
|
+
* Callers that need to distinguish "unreadable" from "no skips" should use
|
|
101
|
+
* {@link loadSkippedIssuesDetailed} instead.
|
|
102
|
+
*/
|
|
103
|
+
export function loadSkippedIssues(path) {
|
|
104
|
+
return loadSkippedIssuesDetailed(path).issues;
|
|
92
105
|
}
|
|
@@ -7,12 +7,7 @@
|
|
|
7
7
|
* `node cli.bundle.cjs startup --json` call, reducing UI noise in Claude Code.
|
|
8
8
|
*/
|
|
9
9
|
import { type StartupOutput, type IssueListInfo } from '../formatters/json.js';
|
|
10
|
-
|
|
11
|
-
* Parse issueListPath from a config file's YAML frontmatter.
|
|
12
|
-
* @param configContent - Raw content of the config.md file
|
|
13
|
-
* @returns The path string or undefined if not found
|
|
14
|
-
*/
|
|
15
|
-
export declare function parseIssueListPathFromConfig(configContent: string): string | undefined;
|
|
10
|
+
export { parseIssueListPathFromConfig } from './locate-issue-list.js';
|
|
16
11
|
/**
|
|
17
12
|
* Count available and completed items in an issue list file.
|
|
18
13
|
* Available items exclude any vetting status that moves an item out of the
|
package/dist/commands/startup.js
CHANGED
|
@@ -16,19 +16,11 @@ import { executeDailyCheck } from './daily.js';
|
|
|
16
16
|
import { launchDashboardServer } from './dashboard-lifecycle.js';
|
|
17
17
|
import { recordBrowserOpened } from './dashboard-process.js';
|
|
18
18
|
import { parseIssueList } from './parse-list.js';
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
export function parseIssueListPathFromConfig(configContent) {
|
|
25
|
-
const match = configContent.match(/^---\n([\s\S]*?)\n---/);
|
|
26
|
-
if (!match)
|
|
27
|
-
return undefined;
|
|
28
|
-
const frontmatter = match[1];
|
|
29
|
-
const pathMatch = frontmatter.match(/issueListPath:\s*["']?([^"'\n]+)["']?/);
|
|
30
|
-
return pathMatch ? pathMatch[1].trim() : undefined;
|
|
31
|
-
}
|
|
19
|
+
import { detectIssueListPath } from './locate-issue-list.js';
|
|
20
|
+
// Path detection moved to locate-issue-list.ts (#1463) so the daily
|
|
21
|
+
// merge-loop can use it without an import cycle through this module.
|
|
22
|
+
// Re-exported here for back-compat with existing consumers/tests.
|
|
23
|
+
export { parseIssueListPathFromConfig } from './locate-issue-list.js';
|
|
32
24
|
/**
|
|
33
25
|
* Count available and completed items in an issue list file.
|
|
34
26
|
* Available items exclude any vetting status that moves an item out of the
|
|
@@ -45,60 +37,27 @@ export function countIssueListItems(content) {
|
|
|
45
37
|
* @returns Issue list info with path and item counts, or undefined if not found
|
|
46
38
|
*/
|
|
47
39
|
export function detectIssueList() {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const stateManager = getStateManager();
|
|
53
|
-
const configuredPath = stateManager.getState().config.issueListPath;
|
|
54
|
-
if (configuredPath && fs.existsSync(configuredPath)) {
|
|
55
|
-
issueListPath = configuredPath;
|
|
56
|
-
source = 'configured';
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
catch (error) {
|
|
60
|
-
// State manager may not be initialized yet — fall through to legacy config.md
|
|
61
|
-
warn('startup', `Could not read issueListPath from state: ${errorMessage(error)}`);
|
|
62
|
-
}
|
|
63
|
-
// 2. Fallback: config.md (legacy — will be removed in future)
|
|
64
|
-
if (!issueListPath) {
|
|
65
|
-
const configPath = '.claude/oss-autopilot/config.md';
|
|
66
|
-
if (fs.existsSync(configPath)) {
|
|
67
|
-
try {
|
|
68
|
-
const configContent = fs.readFileSync(configPath, 'utf8');
|
|
69
|
-
const configuredPath = parseIssueListPathFromConfig(configContent);
|
|
70
|
-
if (configuredPath && fs.existsSync(configuredPath)) {
|
|
71
|
-
issueListPath = configuredPath;
|
|
72
|
-
source = 'configured';
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
catch (error) {
|
|
76
|
-
console.error('[STARTUP] Failed to read config:', errorMessage(error));
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
// 3. Probe known paths
|
|
81
|
-
if (!issueListPath) {
|
|
82
|
-
const probes = ['open-source/potential-issue-list.md', 'oss/issue-list.md', 'issues.md'];
|
|
83
|
-
for (const probe of probes) {
|
|
84
|
-
if (fs.existsSync(probe)) {
|
|
85
|
-
issueListPath = probe;
|
|
86
|
-
source = 'auto-detected';
|
|
87
|
-
break;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
if (!issueListPath)
|
|
40
|
+
// Steps 1-3 (state config → legacy config.md → known-path probes) live in
|
|
41
|
+
// locate-issue-list.ts (#1463), shared with the daily merge-loop.
|
|
42
|
+
const located = detectIssueListPath();
|
|
43
|
+
if (!located)
|
|
92
44
|
return undefined;
|
|
45
|
+
const issueListPath = located.path;
|
|
46
|
+
const source = located.source;
|
|
93
47
|
// 4. Count available/completed items
|
|
94
48
|
let availableCount = 0;
|
|
95
49
|
let completedCount = 0;
|
|
50
|
+
let readError;
|
|
96
51
|
try {
|
|
97
52
|
const content = fs.readFileSync(issueListPath, 'utf8');
|
|
98
53
|
({ availableCount, completedCount } = countIssueListItems(content));
|
|
99
54
|
}
|
|
100
55
|
catch (error) {
|
|
101
|
-
|
|
56
|
+
// Surface the failure in the envelope (#1448): a 0/0 count from an
|
|
57
|
+
// unreadable list is indistinguishable from a genuinely empty list, and
|
|
58
|
+
// an agent seeing 0 available may trigger a redundant fresh search.
|
|
59
|
+
readError = errorMessage(error);
|
|
60
|
+
console.error(`[STARTUP] Failed to read issue list at ${issueListPath}:`, readError);
|
|
102
61
|
}
|
|
103
62
|
// 5. Detect skipped issues file
|
|
104
63
|
let skippedIssuesPath;
|
|
@@ -145,7 +104,14 @@ export function detectIssueList() {
|
|
|
145
104
|
}
|
|
146
105
|
}
|
|
147
106
|
}
|
|
148
|
-
return {
|
|
107
|
+
return {
|
|
108
|
+
path: issueListPath,
|
|
109
|
+
source,
|
|
110
|
+
availableCount,
|
|
111
|
+
completedCount,
|
|
112
|
+
skippedIssuesPath,
|
|
113
|
+
...(readError !== undefined ? { readError } : {}),
|
|
114
|
+
};
|
|
149
115
|
}
|
|
150
116
|
/**
|
|
151
117
|
* Open a URL in the default system browser.
|
package/dist/commands/track.d.ts
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* metadata from GitHub and returns it; useful for inspecting a specific
|
|
8
8
|
* PR's shape without waiting for the next `daily` run. Nothing is persisted.
|
|
9
9
|
*
|
|
10
|
-
* The `runUntrack` v1→v2 stub was removed in v4 (#1133). Use `
|
|
11
|
-
* `
|
|
10
|
+
* The `runUntrack` v1→v2 stub was removed in v4 (#1133). Use `move`
|
|
11
|
+
* (target `shelved`/`auto`) to hide/restore a PR in the daily digest.
|
|
12
12
|
*/
|
|
13
13
|
import type { TrackOutput } from '../formatters/json.js';
|
|
14
14
|
/**
|
package/dist/commands/track.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* metadata from GitHub and returns it; useful for inspecting a specific
|
|
8
8
|
* PR's shape without waiting for the next `daily` run. Nothing is persisted.
|
|
9
9
|
*
|
|
10
|
-
* The `runUntrack` v1→v2 stub was removed in v4 (#1133). Use `
|
|
11
|
-
* `
|
|
10
|
+
* The `runUntrack` v1→v2 stub was removed in v4 (#1133). Use `move`
|
|
11
|
+
* (target `shelved`/`auto`) to hide/restore a PR in the daily digest.
|
|
12
12
|
*/
|
|
13
13
|
import { getOctokit, requireGitHubToken } from '../core/index.js';
|
|
14
14
|
import { ValidationError } from '../core/errors.js';
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Re-vets all available issues in a curated issue list file via @oss-scout/core.
|
|
4
4
|
*/
|
|
5
5
|
import { type VetListOutput, type VetOutput, type VetListItemStatus } from '../formatters/json.js';
|
|
6
|
+
import { type IssueAvailabilityVerdict } from '../core/index.js';
|
|
6
7
|
interface VetListOptions {
|
|
7
8
|
issueListPath?: string;
|
|
8
9
|
concurrency?: number;
|
|
@@ -35,12 +36,11 @@ export declare function extractSkipReason(candidate: unknown): ScoutSkipReason |
|
|
|
35
36
|
*/
|
|
36
37
|
export declare function classifyListStatus(vetResult: VetOutput, skipReason?: ScoutSkipReason): VetListItemStatus;
|
|
37
38
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* @param options - Vet-list options
|
|
43
|
-
* @returns Consolidated vetting results with list status for each issue
|
|
39
|
+
* Map a deterministic verify-issue verdict onto the vet-list status taxonomy
|
|
40
|
+
* (#1494). This is the authoritative path: the GraphQL verdict knows the
|
|
41
|
+
* closing-vs-mention distinction (#1353) that scout's substring heuristic
|
|
42
|
+
* cannot, so `at_risk` (mention only) no longer collapses into `claimed`.
|
|
44
43
|
*/
|
|
44
|
+
export declare function listStatusFromVerdict(verdict: IssueAvailabilityVerdict): VetListItemStatus;
|
|
45
45
|
export declare function runVetList(options?: VetListOptions): Promise<VetListOutput>;
|
|
46
46
|
export {};
|