@oss-autopilot/core 3.2.0 → 3.3.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 +33 -3
- package/dist/cli.bundle.cjs +96 -93
- package/dist/commands/check-integration.js +8 -8
- package/dist/commands/comments.js +3 -0
- package/dist/commands/config.js +14 -7
- package/dist/commands/daily-render.js +10 -5
- package/dist/commands/daily.js +6 -1
- package/dist/commands/dashboard-lifecycle.js +1 -1
- package/dist/commands/dashboard-process.js +4 -4
- package/dist/commands/dashboard-server.js +7 -6
- package/dist/commands/dashboard.js +2 -2
- package/dist/commands/detect-formatters.js +3 -3
- package/dist/commands/doctor.js +5 -5
- package/dist/commands/guidelines.js +15 -3
- package/dist/commands/list-move-tier.js +5 -5
- package/dist/commands/local-repos.js +9 -9
- package/dist/commands/parse-list.js +10 -10
- package/dist/commands/scout-bridge.js +2 -2
- package/dist/commands/setup.js +24 -13
- package/dist/commands/skip-add.js +6 -3
- package/dist/commands/skip-file-parser.js +3 -3
- package/dist/commands/startup.js +11 -8
- package/dist/commands/state-cmd.js +1 -1
- package/dist/commands/status.js +7 -0
- package/dist/commands/validation.js +3 -3
- package/dist/commands/vet-list.js +12 -8
- package/dist/commands/vet.js +1 -2
- package/dist/core/__fixtures__/prompt-injection-payloads.d.ts +22 -0
- package/dist/core/__fixtures__/prompt-injection-payloads.js +109 -0
- package/dist/core/anti-llm-policy.js +5 -5
- package/dist/core/auth.js +5 -5
- package/dist/core/daily-logic.js +8 -4
- package/dist/core/dates.js +3 -3
- package/dist/core/errors.d.ts +29 -0
- package/dist/core/errors.js +63 -0
- package/dist/core/formatter-detection.js +9 -9
- package/dist/core/gist-state-store.d.ts +18 -2
- package/dist/core/gist-state-store.js +73 -13
- package/dist/core/guidelines-store.js +2 -2
- package/dist/core/http-cache.js +6 -6
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/core/issue-conversation.js +3 -1
- package/dist/core/paths.js +4 -4
- package/dist/core/pr-monitor.js +1 -2
- package/dist/core/pr-template.js +1 -1
- package/dist/core/state-persistence.js +7 -7
- package/dist/core/state.d.ts +27 -0
- package/dist/core/state.js +66 -13
- package/dist/core/untrusted-content.d.ts +48 -0
- package/dist/core/untrusted-content.js +106 -0
- package/dist/core/urls.js +2 -2
- package/dist/formatters/json.d.ts +53 -3
- package/dist/formatters/json.js +49 -14
- package/package.json +1 -1
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Check integration command (#83)
|
|
3
3
|
* Detects new files in the current branch that aren't referenced elsewhere
|
|
4
4
|
*/
|
|
5
|
-
import * as path from 'path';
|
|
6
|
-
import { execFileSync } from 'child_process';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import { execFileSync } from 'node:child_process';
|
|
7
7
|
import { debug } from '../core/index.js';
|
|
8
8
|
import { errorMessage } from '../core/errors.js';
|
|
9
9
|
/** File extensions we consider "code" that should be imported/referenced */
|
|
@@ -71,8 +71,8 @@ export async function runCheckIntegration(options) {
|
|
|
71
71
|
let newFiles;
|
|
72
72
|
try {
|
|
73
73
|
const output = execFileSync('git', ['diff', '--name-only', '--diff-filter=A', `${base}...HEAD`], {
|
|
74
|
-
encoding: '
|
|
75
|
-
timeout:
|
|
74
|
+
encoding: 'utf8',
|
|
75
|
+
timeout: 10_000,
|
|
76
76
|
}).trim();
|
|
77
77
|
newFiles = output ? output.split('\n').filter(Boolean) : [];
|
|
78
78
|
}
|
|
@@ -94,8 +94,8 @@ export async function runCheckIntegration(options) {
|
|
|
94
94
|
let allFiles;
|
|
95
95
|
try {
|
|
96
96
|
allFiles = execFileSync('git', ['ls-files'], {
|
|
97
|
-
encoding: '
|
|
98
|
-
timeout:
|
|
97
|
+
encoding: 'utf8',
|
|
98
|
+
timeout: 10_000,
|
|
99
99
|
})
|
|
100
100
|
.trim()
|
|
101
101
|
.split('\n')
|
|
@@ -124,8 +124,8 @@ export async function runCheckIntegration(options) {
|
|
|
124
124
|
for (const pattern of patterns) {
|
|
125
125
|
try {
|
|
126
126
|
const grepOutput = execFileSync('git', ['grep', '-l', '--', pattern], {
|
|
127
|
-
encoding: '
|
|
128
|
-
timeout:
|
|
127
|
+
encoding: 'utf8',
|
|
128
|
+
timeout: 10_000,
|
|
129
129
|
}).trim();
|
|
130
130
|
if (grepOutput) {
|
|
131
131
|
const matches = grepOutput.split('\n').filter((f) => f !== newFile);
|
|
@@ -7,6 +7,7 @@ import { ValidationError } from '../core/errors.js';
|
|
|
7
7
|
import { warn } from '../core/logger.js';
|
|
8
8
|
const MODULE = 'comments';
|
|
9
9
|
import { paginateAll } from '../core/pagination.js';
|
|
10
|
+
import { buildStalenessWarning } from '../formatters/json.js';
|
|
10
11
|
import { validateUrl, validateMessage, validateGitHubUrl, PR_URL_PATTERN, ISSUE_OR_PR_URL_PATTERN, ISSUE_URL_PATTERN, } from './validation.js';
|
|
11
12
|
/**
|
|
12
13
|
* Fetch all comments, reviews, and inline review comments for a PR.
|
|
@@ -76,6 +77,7 @@ export async function runComments(options) {
|
|
|
76
77
|
const relevantReviews = reviews
|
|
77
78
|
.filter((r) => filterComment(r) && r.body && r.body.trim())
|
|
78
79
|
.sort((a, b) => new Date(b.submitted_at || 0).getTime() - new Date(a.submitted_at || 0).getTime());
|
|
80
|
+
const staleness = stateManager.getStateStaleness();
|
|
79
81
|
return {
|
|
80
82
|
pr: {
|
|
81
83
|
title: pr.title,
|
|
@@ -107,6 +109,7 @@ export async function runComments(options) {
|
|
|
107
109
|
inlineCommentCount: relevantReviewComments.length,
|
|
108
110
|
discussionCommentCount: relevantIssueComments.length,
|
|
109
111
|
},
|
|
112
|
+
...(staleness ? { warnings: [buildStalenessWarning(staleness)] } : {}),
|
|
110
113
|
};
|
|
111
114
|
}
|
|
112
115
|
/**
|
package/dist/commands/config.js
CHANGED
|
@@ -44,25 +44,29 @@ export async function runConfig(options) {
|
|
|
44
44
|
const value = options.value;
|
|
45
45
|
// Handle specific config keys
|
|
46
46
|
switch (options.key) {
|
|
47
|
-
case 'username':
|
|
47
|
+
case 'username': {
|
|
48
48
|
stateManager.updateConfig({ githubUsername: validateGitHubUsername(value) });
|
|
49
49
|
break;
|
|
50
|
-
|
|
50
|
+
}
|
|
51
|
+
case 'add-language': {
|
|
51
52
|
if (!currentConfig.languages.includes(value)) {
|
|
52
53
|
stateManager.updateConfig({ languages: [...currentConfig.languages, value] });
|
|
53
54
|
}
|
|
54
55
|
break;
|
|
55
|
-
|
|
56
|
+
}
|
|
57
|
+
case 'add-label': {
|
|
56
58
|
if (!currentConfig.labels.includes(value)) {
|
|
57
59
|
stateManager.updateConfig({ labels: [...currentConfig.labels, value] });
|
|
58
60
|
}
|
|
59
61
|
break;
|
|
60
|
-
|
|
62
|
+
}
|
|
63
|
+
case 'remove-label': {
|
|
61
64
|
if (!currentConfig.labels.includes(value)) {
|
|
62
65
|
throw new Error(`Label "${value}" is not currently configured. Current labels: ${currentConfig.labels.join(', ')}`);
|
|
63
66
|
}
|
|
64
67
|
stateManager.updateConfig({ labels: currentConfig.labels.filter((l) => l !== value) });
|
|
65
68
|
break;
|
|
69
|
+
}
|
|
66
70
|
case 'add-scope': {
|
|
67
71
|
const scope = validateScope(value);
|
|
68
72
|
const currentScopes = currentConfig.scope ?? [];
|
|
@@ -111,9 +115,10 @@ export async function runConfig(options) {
|
|
|
111
115
|
}
|
|
112
116
|
break;
|
|
113
117
|
}
|
|
114
|
-
case 'issueListPath':
|
|
118
|
+
case 'issueListPath': {
|
|
115
119
|
stateManager.updateConfig({ issueListPath: value || undefined });
|
|
116
120
|
break;
|
|
121
|
+
}
|
|
117
122
|
case 'diffTool': {
|
|
118
123
|
if (!DIFF_TOOLS.includes(value)) {
|
|
119
124
|
throw new Error(`Invalid diffTool "${value}". Valid options: ${DIFF_TOOLS.join(', ')}`);
|
|
@@ -121,13 +126,15 @@ export async function runConfig(options) {
|
|
|
121
126
|
stateManager.updateConfig({ diffTool: value });
|
|
122
127
|
break;
|
|
123
128
|
}
|
|
124
|
-
case 'diffToolCustomCommand':
|
|
129
|
+
case 'diffToolCustomCommand': {
|
|
125
130
|
stateManager.updateConfig({
|
|
126
131
|
diffToolCustomCommand: value || undefined,
|
|
127
132
|
});
|
|
128
133
|
break;
|
|
129
|
-
|
|
134
|
+
}
|
|
135
|
+
default: {
|
|
130
136
|
throw new ValidationError(formatUnknownKeyError(options.key, 'config'));
|
|
137
|
+
}
|
|
131
138
|
}
|
|
132
139
|
return { success: true, key: options.key, value };
|
|
133
140
|
}
|
|
@@ -20,16 +20,21 @@ import { formatRelativeTime } from '../core/dates.js';
|
|
|
20
20
|
*/
|
|
21
21
|
export function formatActionHint(hint) {
|
|
22
22
|
switch (hint) {
|
|
23
|
-
case 'demo_requested':
|
|
23
|
+
case 'demo_requested': {
|
|
24
24
|
return 'demo/screenshot requested';
|
|
25
|
-
|
|
25
|
+
}
|
|
26
|
+
case 'tests_requested': {
|
|
26
27
|
return 'tests requested';
|
|
27
|
-
|
|
28
|
+
}
|
|
29
|
+
case 'changes_requested': {
|
|
28
30
|
return 'code changes requested';
|
|
29
|
-
|
|
31
|
+
}
|
|
32
|
+
case 'docs_requested': {
|
|
30
33
|
return 'documentation requested';
|
|
31
|
-
|
|
34
|
+
}
|
|
35
|
+
case 'rebase_requested': {
|
|
32
36
|
return 'rebase requested';
|
|
37
|
+
}
|
|
33
38
|
}
|
|
34
39
|
}
|
|
35
40
|
/**
|
package/dist/commands/daily.js
CHANGED
|
@@ -12,7 +12,7 @@ import { warn } from '../core/logger.js';
|
|
|
12
12
|
import { emptyPRCountsResult } from '../core/github-stats.js';
|
|
13
13
|
import { createAutopilotScout } from './scout-bridge.js';
|
|
14
14
|
import { updateMonthlyAnalytics } from './dashboard-data.js';
|
|
15
|
-
import { deduplicateDigest, compactActionableIssues, compactRepoGroups, } from '../formatters/json.js';
|
|
15
|
+
import { deduplicateDigest, compactActionableIssues, compactRepoGroups, buildStalenessWarning, } from '../formatters/json.js';
|
|
16
16
|
const MODULE = 'daily';
|
|
17
17
|
/**
|
|
18
18
|
* Record a non-fatal failure: push a structured entry into the run's warnings
|
|
@@ -504,6 +504,11 @@ async function executeDailyCheckInternal(token) {
|
|
|
504
504
|
// One collector shared by every phase — threaded through explicitly so the
|
|
505
505
|
// callgraph documents which phases can produce non-fatal warnings. See #1042.
|
|
506
506
|
const warnings = [];
|
|
507
|
+
// Surface Gist staleness up-front so consumers see it even if Phase 1 fails (#1193).
|
|
508
|
+
const staleness = getStateManager().getStateStaleness();
|
|
509
|
+
if (staleness) {
|
|
510
|
+
warnings.push(buildStalenessWarning(staleness));
|
|
511
|
+
}
|
|
507
512
|
// Phase 1: Fetch all PR data from GitHub
|
|
508
513
|
const { prs, failures, mergedCounts, closedCounts, monthlyCounts, monthlyClosedCounts, openedFromMerged, openedFromClosed, recentlyClosedPRs, recentlyMergedPRs, commentedIssues, } = await fetchPRData(prMonitor, token, warnings);
|
|
509
514
|
// Phase 2: Update repo scores (signals, star counts, trust sync)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Handles launching the interactive SPA dashboard as a background process
|
|
4
4
|
* and detecting whether a server is already running.
|
|
5
5
|
*/
|
|
6
|
-
import { spawn } from 'child_process';
|
|
6
|
+
import { spawn } from 'node:child_process';
|
|
7
7
|
import { findRunningDashboardServer, isDashboardServerRunning, readDashboardServerInfo, removeDashboardServerInfo, } from './dashboard-process.js';
|
|
8
8
|
import { resolveAssetsDir } from './dashboard.js';
|
|
9
9
|
import { getCLIVersion } from '../core/index.js';
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* Dashboard server process management.
|
|
3
3
|
* PID file operations, health probes, and running server detection.
|
|
4
4
|
*/
|
|
5
|
-
import * as http from 'http';
|
|
6
|
-
import * as fs from 'fs';
|
|
7
|
-
import * as path from 'path';
|
|
5
|
+
import * as http from 'node:http';
|
|
6
|
+
import * as fs from 'node:fs';
|
|
7
|
+
import * as path from 'node:path';
|
|
8
8
|
import { getDataDir } from '../core/index.js';
|
|
9
9
|
import { warn } from '../core/logger.js';
|
|
10
10
|
const MODULE = 'dashboard-server';
|
|
@@ -17,7 +17,7 @@ export function writeDashboardServerInfo(info) {
|
|
|
17
17
|
}
|
|
18
18
|
export function readDashboardServerInfo() {
|
|
19
19
|
try {
|
|
20
|
-
const content = fs.readFileSync(getDashboardPidPath(), '
|
|
20
|
+
const content = fs.readFileSync(getDashboardPidPath(), 'utf8');
|
|
21
21
|
const parsed = JSON.parse(content);
|
|
22
22
|
if (typeof parsed !== 'object' ||
|
|
23
23
|
parsed === null ||
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Uses Node's built-in http module — no Express/Fastify.
|
|
7
7
|
*/
|
|
8
|
-
import * as http from 'http';
|
|
9
|
-
import * as fs from 'fs';
|
|
10
|
-
import * as path from 'path';
|
|
11
|
-
import * as crypto from 'crypto';
|
|
8
|
+
import * as http from 'node:http';
|
|
9
|
+
import * as fs from 'node:fs';
|
|
10
|
+
import * as path from 'node:path';
|
|
11
|
+
import * as crypto from 'node:crypto';
|
|
12
12
|
import { getStateManager, getGitHubToken, getCLIVersion, applyStatusOverrides } from '../core/index.js';
|
|
13
13
|
import { errorMessage, ValidationError } from '../core/errors.js';
|
|
14
14
|
import { warn } from '../core/logger.js';
|
|
@@ -44,7 +44,7 @@ function readVettedIssues() {
|
|
|
44
44
|
const info = detectIssueList();
|
|
45
45
|
if (!info)
|
|
46
46
|
return null;
|
|
47
|
-
const content = fs.readFileSync(info.path, '
|
|
47
|
+
const content = fs.readFileSync(info.path, 'utf8');
|
|
48
48
|
return parseIssueList(content);
|
|
49
49
|
}
|
|
50
50
|
catch (error) {
|
|
@@ -148,7 +148,7 @@ function readBody(req, maxBytes = MAX_BODY_BYTES) {
|
|
|
148
148
|
});
|
|
149
149
|
req.on('end', () => {
|
|
150
150
|
if (!aborted)
|
|
151
|
-
resolve(Buffer.concat(chunks).toString('
|
|
151
|
+
resolve(Buffer.concat(chunks).toString('utf8'));
|
|
152
152
|
});
|
|
153
153
|
req.on('error', (err) => {
|
|
154
154
|
if (!aborted)
|
|
@@ -599,6 +599,7 @@ export async function startDashboardServer(options) {
|
|
|
599
599
|
cachedJsonData = buildDashboardJson(cachedDigest, stateManager.getState(), cachedCommentedIssues, result.allMergedPRs, result.allClosedPRs, cachedPartialFailures);
|
|
600
600
|
cachedIssueListMtimeMs = getIssueListMtimeMs();
|
|
601
601
|
warn(MODULE, 'Background data refresh complete');
|
|
602
|
+
return;
|
|
602
603
|
})
|
|
603
604
|
.catch((error) => {
|
|
604
605
|
warn(MODULE, `Background data refresh failed (serving cached data): ${errorMessage(error)}`);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Dashboard command — serves the interactive Preact SPA dashboard.
|
|
3
3
|
*/
|
|
4
|
-
import * as fs from 'fs';
|
|
5
|
-
import * as path from 'path';
|
|
4
|
+
import * as fs from 'node:fs';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
6
|
import { getGitHubToken } from '../core/index.js';
|
|
7
7
|
/**
|
|
8
8
|
* Resolve the SPA assets directory from packages/dashboard/dist/.
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Scans a local repository for configured formatters/linters.
|
|
4
4
|
* Optionally diagnoses CI log output for formatting failures.
|
|
5
5
|
*/
|
|
6
|
-
import * as fs from 'fs';
|
|
7
|
-
import * as path from 'path';
|
|
6
|
+
import * as fs from 'node:fs';
|
|
7
|
+
import * as path from 'node:path';
|
|
8
8
|
import { detectFormatters, diagnoseCIFormatterFailure } from '../core/formatter-detection.js';
|
|
9
9
|
import { errorMessage } from '../core/errors.js';
|
|
10
10
|
export async function runDetectFormatters(options) {
|
|
@@ -13,7 +13,7 @@ export async function runDetectFormatters(options) {
|
|
|
13
13
|
if (options.ciLog) {
|
|
14
14
|
let logContent;
|
|
15
15
|
try {
|
|
16
|
-
logContent = fs.readFileSync(path.resolve(options.ciLog), '
|
|
16
|
+
logContent = fs.readFileSync(path.resolve(options.ciLog), 'utf8');
|
|
17
17
|
}
|
|
18
18
|
catch (err) {
|
|
19
19
|
throw new Error(`Failed to read CI log file: ${errorMessage(err)}`, { cause: err });
|
package/dist/commands/doctor.js
CHANGED
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
* Each `check*` function is exported individually so tests can mock its
|
|
10
10
|
* external dependencies in isolation.
|
|
11
11
|
*/
|
|
12
|
-
import * as fs from 'fs';
|
|
13
|
-
import * as path from 'path';
|
|
14
|
-
import { execFile } from 'child_process';
|
|
15
|
-
import { promisify } from 'util';
|
|
12
|
+
import * as fs from 'node:fs';
|
|
13
|
+
import * as path from 'node:path';
|
|
14
|
+
import { execFile } from 'node:child_process';
|
|
15
|
+
import { promisify } from 'node:util';
|
|
16
16
|
import { getGitHubTokenAsync, getOctokit, getStatePath } from '../core/index.js';
|
|
17
17
|
import { AgentStateSchema } from '../core/state-schema.js';
|
|
18
18
|
import { errorMessage } from '../core/errors.js';
|
|
@@ -195,7 +195,7 @@ export function checkStateFile(options) {
|
|
|
195
195
|
};
|
|
196
196
|
}
|
|
197
197
|
try {
|
|
198
|
-
const raw = fs.readFileSync(statePath, '
|
|
198
|
+
const raw = fs.readFileSync(statePath, 'utf8');
|
|
199
199
|
const parsed = JSON.parse(raw);
|
|
200
200
|
const result = AgentStateSchema.safeParse(parsed);
|
|
201
201
|
if (!result.success) {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* Standalone-mode users see "not available" on store/reset/fetch-corpus
|
|
12
12
|
* because per-repo guidelines require Gist persistence to be useful.
|
|
13
13
|
*/
|
|
14
|
-
import { getStateManager, requireGitHubToken, getOctokit, GuidelinesNotAvailableError } from '../core/index.js';
|
|
14
|
+
import { getStateManager, requireGitHubToken, getOctokit, GuidelinesNotAvailableError, maybeCheckpoint, } from '../core/index.js';
|
|
15
15
|
import { fetchPRCommentBundlesBatch } from '../core/pr-comments-fetcher.js';
|
|
16
16
|
import { warn } from '../core/logger.js';
|
|
17
17
|
const MODULE = 'guidelines';
|
|
@@ -36,7 +36,7 @@ export async function runGuidelinesView(options) {
|
|
|
36
36
|
return {
|
|
37
37
|
repo: options.repo,
|
|
38
38
|
content,
|
|
39
|
-
byteSize: content === null ? 0 : Buffer.byteLength(content, '
|
|
39
|
+
byteSize: content === null ? 0 : Buffer.byteLength(content, 'utf8'),
|
|
40
40
|
exists: content !== null,
|
|
41
41
|
storageMode: sm.isGuidelinesAvailable() ? 'gist' : 'local-unavailable',
|
|
42
42
|
};
|
|
@@ -54,9 +54,13 @@ export async function runGuidelinesStore(options) {
|
|
|
54
54
|
const sm = getStateManager();
|
|
55
55
|
// Throws GuidelinesNotAvailableError or GuidelinesTooLargeError on failure.
|
|
56
56
|
sm.setGuidelines(options.repo, options.content);
|
|
57
|
+
// Push to Gist — autoSave only writes the local state-cache mirror in Gist
|
|
58
|
+
// mode, so without this checkpoint the change never propagates across
|
|
59
|
+
// machines (#1200).
|
|
60
|
+
await maybeCheckpoint(sm, MODULE);
|
|
57
61
|
return {
|
|
58
62
|
repo: options.repo,
|
|
59
|
-
byteSize: Buffer.byteLength(options.content, '
|
|
63
|
+
byteSize: Buffer.byteLength(options.content, 'utf8'),
|
|
60
64
|
stored: true,
|
|
61
65
|
};
|
|
62
66
|
}
|
|
@@ -70,6 +74,8 @@ export async function runGuidelinesReset(options) {
|
|
|
70
74
|
const existed = sm.getGuidelines(options.repo) !== null;
|
|
71
75
|
if (existed) {
|
|
72
76
|
sm.deleteGuidelines(options.repo);
|
|
77
|
+
// Push to Gist — see runGuidelinesStore note (#1200).
|
|
78
|
+
await maybeCheckpoint(sm, MODULE);
|
|
73
79
|
}
|
|
74
80
|
return { repo: options.repo, deleted: existed };
|
|
75
81
|
}
|
|
@@ -131,6 +137,12 @@ export async function runFetchCorpus(options) {
|
|
|
131
137
|
for (const bundle of bundles) {
|
|
132
138
|
sm.markPRCommentsFetched(bundle.prUrl, now);
|
|
133
139
|
}
|
|
140
|
+
// Push commentsFetchedAt stamps to Gist so other machines don't re-fetch
|
|
141
|
+
// the same PRs forever. autoSave only writes the local mirror in Gist
|
|
142
|
+
// mode (#1200).
|
|
143
|
+
if (bundles.length > 0) {
|
|
144
|
+
await maybeCheckpoint(sm, MODULE);
|
|
145
|
+
}
|
|
134
146
|
return {
|
|
135
147
|
repo: options.repo,
|
|
136
148
|
bundles,
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
*
|
|
11
11
|
* No GitHub calls — pure read/transform/write of a local file.
|
|
12
12
|
*/
|
|
13
|
-
import * as fs from 'fs';
|
|
14
|
-
import * as path from 'path';
|
|
13
|
+
import * as fs from 'node:fs';
|
|
14
|
+
import * as path from 'node:path';
|
|
15
15
|
import { errorMessage } from '../core/errors.js';
|
|
16
16
|
const TIER_HEADERS = {
|
|
17
17
|
pursue: '## Pursue',
|
|
@@ -47,7 +47,7 @@ function findIssueBlocks(lines, issueUrl) {
|
|
|
47
47
|
for (let i = 0; i < lines.length; i++) {
|
|
48
48
|
const line = lines[i];
|
|
49
49
|
// Top-level list item — `- `, `* `, `+ `, or `1.` at the start (with no leading whitespace).
|
|
50
|
-
const isTopLevelListItem = /^[
|
|
50
|
+
const isTopLevelListItem = /^[*+-]\s|^\d+\.\s/.test(line);
|
|
51
51
|
if (!isTopLevelListItem || !line.includes(issueUrl))
|
|
52
52
|
continue;
|
|
53
53
|
// Capture indented sub-bullets that follow this line.
|
|
@@ -166,7 +166,7 @@ export async function runListMoveTier(options) {
|
|
|
166
166
|
}
|
|
167
167
|
let content;
|
|
168
168
|
try {
|
|
169
|
-
content = fs.readFileSync(filePath, '
|
|
169
|
+
content = fs.readFileSync(filePath, 'utf8');
|
|
170
170
|
}
|
|
171
171
|
catch (error) {
|
|
172
172
|
throw new Error(`Failed to read file: ${errorMessage(error)}`, { cause: error });
|
|
@@ -174,7 +174,7 @@ export async function runListMoveTier(options) {
|
|
|
174
174
|
const result = moveIssueToTier(content, options.issueUrl, options.tier);
|
|
175
175
|
if (result.moved) {
|
|
176
176
|
try {
|
|
177
|
-
fs.writeFileSync(filePath, result.content, '
|
|
177
|
+
fs.writeFileSync(filePath, result.content, 'utf8');
|
|
178
178
|
}
|
|
179
179
|
catch (error) {
|
|
180
180
|
throw new Error(`Failed to write file: ${errorMessage(error)}`, { cause: error });
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* Local repos command (#84)
|
|
3
3
|
* Scans configurable directories for local git clones and caches results
|
|
4
4
|
*/
|
|
5
|
-
import * as fs from 'fs';
|
|
6
|
-
import * as path from 'path';
|
|
7
|
-
import * as os from 'os';
|
|
8
|
-
import { execFileSync } from 'child_process';
|
|
5
|
+
import * as fs from 'node:fs';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
import * as os from 'node:os';
|
|
8
|
+
import { execFileSync } from 'node:child_process';
|
|
9
9
|
import { getStateManager, debug } from '../core/index.js';
|
|
10
10
|
import { errorMessage } from '../core/errors.js';
|
|
11
11
|
/** Default directories to scan for local clones */
|
|
@@ -21,7 +21,7 @@ const DEFAULT_SCAN_PATHS = [
|
|
|
21
21
|
function getGitHubRemote(repoPath) {
|
|
22
22
|
try {
|
|
23
23
|
const remoteUrl = execFileSync('git', ['-C', repoPath, 'remote', 'get-url', 'origin'], {
|
|
24
|
-
encoding: '
|
|
24
|
+
encoding: 'utf8',
|
|
25
25
|
timeout: 5000,
|
|
26
26
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
27
27
|
}).trim();
|
|
@@ -30,7 +30,7 @@ function getGitHubRemote(repoPath) {
|
|
|
30
30
|
if (httpsMatch)
|
|
31
31
|
return httpsMatch[1];
|
|
32
32
|
// Match SSH: git@github.com:owner/repo.git
|
|
33
|
-
const sshMatch = remoteUrl.match(/github\.com[
|
|
33
|
+
const sshMatch = remoteUrl.match(/github\.com[/:]([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
34
34
|
if (sshMatch)
|
|
35
35
|
return sshMatch[1];
|
|
36
36
|
return null;
|
|
@@ -45,7 +45,7 @@ function getGitHubRemote(repoPath) {
|
|
|
45
45
|
function getCurrentBranch(repoPath) {
|
|
46
46
|
try {
|
|
47
47
|
return (execFileSync('git', ['-C', repoPath, 'branch', '--show-current'], {
|
|
48
|
-
encoding: '
|
|
48
|
+
encoding: 'utf8',
|
|
49
49
|
timeout: 5000,
|
|
50
50
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
51
51
|
}).trim() || null);
|
|
@@ -66,8 +66,8 @@ export function scanForRepos(scanPaths) {
|
|
|
66
66
|
let gitDirs;
|
|
67
67
|
try {
|
|
68
68
|
const output = execFileSync('find', [scanPath, '-maxdepth', '4', '-name', '.git', '-type', 'd'], {
|
|
69
|
-
encoding: '
|
|
70
|
-
timeout:
|
|
69
|
+
encoding: 'utf8',
|
|
70
|
+
timeout: 30_000,
|
|
71
71
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
72
72
|
}).trim();
|
|
73
73
|
gitDirs = output ? output.split('\n').filter(Boolean) : [];
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Parse issue list command (#82)
|
|
3
3
|
* Parses markdown issue lists into structured JSON with tier classification
|
|
4
4
|
*/
|
|
5
|
-
import * as fs from 'fs';
|
|
6
|
-
import * as path from 'path';
|
|
5
|
+
import * as fs from 'node:fs';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
7
|
import { errorMessage } from '../core/errors.js';
|
|
8
8
|
/** Extract GitHub issue/PR URLs from a markdown line */
|
|
9
9
|
function extractGitHubUrl(line) {
|
|
@@ -26,11 +26,11 @@ function extractTitle(line) {
|
|
|
26
26
|
// Remove list markers (-, *, +, numbered)
|
|
27
27
|
cleaned = cleaned.replace(/^\s*[-*+]\s*/, '').replace(/^\s*\d+\.\s*/, '');
|
|
28
28
|
// Remove checkboxes
|
|
29
|
-
cleaned = cleaned.replace(/\[[
|
|
29
|
+
cleaned = cleaned.replace(/\[[ x]\]\s*/i, '');
|
|
30
30
|
// Remove strikethrough markers
|
|
31
31
|
cleaned = cleaned.replace(/~~/g, '');
|
|
32
32
|
// Remove "Done" markers
|
|
33
|
-
cleaned = cleaned.replace(/\
|
|
33
|
+
cleaned = cleaned.replace(/\bdone\b/gi, '');
|
|
34
34
|
// Remove leading/trailing punctuation and whitespace
|
|
35
35
|
cleaned = cleaned.replace(/^[\s\-\u2013\u2014:]+/, '').replace(/[\s\-\u2013\u2014:]+$/, '');
|
|
36
36
|
return cleaned.trim();
|
|
@@ -41,7 +41,7 @@ function isCompleted(line) {
|
|
|
41
41
|
if (/~~.+~~/.test(line))
|
|
42
42
|
return true;
|
|
43
43
|
// Checked checkbox: [x] or [X]
|
|
44
|
-
if (/\[
|
|
44
|
+
if (/\[x\]/i.test(line))
|
|
45
45
|
return true;
|
|
46
46
|
// "Done" marker (standalone word, case insensitive)
|
|
47
47
|
if (/\bdone\b/i.test(line))
|
|
@@ -55,11 +55,11 @@ function extractScore(line) {
|
|
|
55
55
|
}
|
|
56
56
|
/** Check if a sub-bullet indicates the item is terminal (completed/abandoned — safe to prune) */
|
|
57
57
|
function isSubBulletTerminal(line) {
|
|
58
|
-
return /\*\*(Skip|Done|Dropped|Merged|Closed)\*\*/i.test(line);
|
|
58
|
+
return /\*\*(?:Skip|Done|Dropped|Merged|Closed)\*\*/i.test(line);
|
|
59
59
|
}
|
|
60
60
|
/** Check if a sub-bullet indicates the item is in-progress (not available, but NOT safe to prune) */
|
|
61
61
|
function isSubBulletInProgress(line) {
|
|
62
|
-
return /\*\*(In Progress|Wait|Waiting)\*\*/i.test(line);
|
|
62
|
+
return /\*\*(?:In Progress|Wait|Waiting)\*\*/i.test(line);
|
|
63
63
|
}
|
|
64
64
|
/** Parse a markdown string into structured issue items */
|
|
65
65
|
export function parseIssueList(content) {
|
|
@@ -79,7 +79,7 @@ export function parseIssueList(content) {
|
|
|
79
79
|
continue;
|
|
80
80
|
}
|
|
81
81
|
// Skip empty lines and non-list items
|
|
82
|
-
if (!line.trim() || !/^\s*[-*+]|\s*\d+\.|\s*\[[
|
|
82
|
+
if (!line.trim() || !/^\s*[-*+]|\s*\d+\.|\s*\[[ x]\]/i.test(line)) {
|
|
83
83
|
continue;
|
|
84
84
|
}
|
|
85
85
|
// Extract GitHub URL -- skip lines without one
|
|
@@ -193,7 +193,7 @@ export function pruneIssueList(content, minScore = 6) {
|
|
|
193
193
|
if (/^---\s*$/.test(line)) {
|
|
194
194
|
continue;
|
|
195
195
|
}
|
|
196
|
-
if (/^\s*(
|
|
196
|
+
if (/^\s*(?:###?\s*)?(?:Removed|Previously dropped)/i.test(line)) {
|
|
197
197
|
continue;
|
|
198
198
|
}
|
|
199
199
|
// Skip blockquote metadata lines ("> Sources: ...", "> Prioritized ...")
|
|
@@ -242,7 +242,7 @@ export async function runParseList(options) {
|
|
|
242
242
|
}
|
|
243
243
|
let content;
|
|
244
244
|
try {
|
|
245
|
-
content = fs.readFileSync(filePath, '
|
|
245
|
+
content = fs.readFileSync(filePath, 'utf8');
|
|
246
246
|
}
|
|
247
247
|
catch (error) {
|
|
248
248
|
const msg = errorMessage(error);
|
|
@@ -44,8 +44,8 @@ export function buildScoutState() {
|
|
|
44
44
|
maxIssueAgeDays: config.maxIssueAgeDays,
|
|
45
45
|
includeDocIssues: config.includeDocIssues,
|
|
46
46
|
minRepoScoreThreshold: config.minRepoScoreThreshold,
|
|
47
|
-
interPhaseDelayMs:
|
|
48
|
-
broadPhaseDelayMs:
|
|
47
|
+
interPhaseDelayMs: 30_000,
|
|
48
|
+
broadPhaseDelayMs: 90_000,
|
|
49
49
|
skipBroadWhenSufficientResults: 15,
|
|
50
50
|
persistence: config.persistence,
|
|
51
51
|
slmTriageModel: config.slmTriageModel,
|