@oss-autopilot/core 0.42.4 → 0.42.5
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.bundle.cjs +124 -43
- package/dist/cli.js +28 -10
- package/dist/commands/comments.js +24 -16
- package/dist/commands/dashboard-data.js +6 -1
- package/dist/commands/dashboard-server.js +41 -2
- package/dist/commands/dashboard.js +3 -3
- package/dist/commands/local-repos.js +1 -1
- package/dist/commands/setup.js +30 -12
- package/dist/core/daily-logic.js +2 -1
- package/package.json +1 -1
package/dist/cli.bundle.cjs
CHANGED
|
@@ -12943,7 +12943,7 @@ function buildRepoMap(prs, label) {
|
|
|
12943
12943
|
const repoMap = /* @__PURE__ */ new Map();
|
|
12944
12944
|
for (const pr of prs) {
|
|
12945
12945
|
if (!pr.repo) {
|
|
12946
|
-
|
|
12946
|
+
warn(label, `Skipping PR #${pr.number} (${pr.url}) with empty repo field`);
|
|
12947
12947
|
continue;
|
|
12948
12948
|
}
|
|
12949
12949
|
const existing = repoMap.get(pr.repo) || [];
|
|
@@ -13348,6 +13348,7 @@ var init_daily_logic = __esm({
|
|
|
13348
13348
|
"src/core/daily-logic.ts"() {
|
|
13349
13349
|
"use strict";
|
|
13350
13350
|
init_utils();
|
|
13351
|
+
init_logger();
|
|
13351
13352
|
CRITICAL_STATUSES = /* @__PURE__ */ new Set([
|
|
13352
13353
|
"needs_response",
|
|
13353
13354
|
"needs_changes",
|
|
@@ -13947,12 +13948,13 @@ function validateGitHubUsername(username) {
|
|
|
13947
13948
|
}
|
|
13948
13949
|
return trimmed;
|
|
13949
13950
|
}
|
|
13950
|
-
var PR_URL_PATTERN, ISSUE_OR_PR_URL_PATTERN, MAX_URL_LENGTH, MAX_MESSAGE_LENGTH, MAX_USERNAME_LENGTH, USERNAME_CHARS_PATTERN, CONSECUTIVE_HYPHENS_PATTERN;
|
|
13951
|
+
var PR_URL_PATTERN, ISSUE_URL_PATTERN, ISSUE_OR_PR_URL_PATTERN, MAX_URL_LENGTH, MAX_MESSAGE_LENGTH, MAX_USERNAME_LENGTH, USERNAME_CHARS_PATTERN, CONSECUTIVE_HYPHENS_PATTERN;
|
|
13951
13952
|
var init_validation = __esm({
|
|
13952
13953
|
"src/commands/validation.ts"() {
|
|
13953
13954
|
"use strict";
|
|
13954
13955
|
init_errors();
|
|
13955
13956
|
PR_URL_PATTERN = /^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+$/;
|
|
13957
|
+
ISSUE_URL_PATTERN = /^https:\/\/github\.com\/[^/]+\/[^/]+\/issues\/\d+$/;
|
|
13956
13958
|
ISSUE_OR_PR_URL_PATTERN = /^https:\/\/github\.com\/[^/]+\/[^/]+\/(issues|pull)\/\d+$/;
|
|
13957
13959
|
MAX_URL_LENGTH = 2048;
|
|
13958
13960
|
MAX_MESSAGE_LENGTH = 1e3;
|
|
@@ -14072,6 +14074,7 @@ __export(comments_exports, {
|
|
|
14072
14074
|
});
|
|
14073
14075
|
async function runComments(options) {
|
|
14074
14076
|
validateUrl(options.prUrl);
|
|
14077
|
+
validateGitHubUrl(options.prUrl, PR_URL_PATTERN, "PR");
|
|
14075
14078
|
const token = requireGitHubToken();
|
|
14076
14079
|
const stateManager2 = getStateManager();
|
|
14077
14080
|
const octokit = getOctokit(token);
|
|
@@ -14155,6 +14158,7 @@ async function runComments(options) {
|
|
|
14155
14158
|
}
|
|
14156
14159
|
async function runPost(options) {
|
|
14157
14160
|
validateUrl(options.url);
|
|
14161
|
+
validateGitHubUrl(options.url, ISSUE_OR_PR_URL_PATTERN, "issue or PR");
|
|
14158
14162
|
if (!options.message.trim()) {
|
|
14159
14163
|
throw new Error("No message provided");
|
|
14160
14164
|
}
|
|
@@ -14179,6 +14183,7 @@ async function runPost(options) {
|
|
|
14179
14183
|
}
|
|
14180
14184
|
async function runClaim(options) {
|
|
14181
14185
|
validateUrl(options.issueUrl);
|
|
14186
|
+
validateGitHubUrl(options.issueUrl, ISSUE_URL_PATTERN, "issue");
|
|
14182
14187
|
const token = requireGitHubToken();
|
|
14183
14188
|
const message = options.message || "Hi! I'd like to work on this issue. Could you assign it to me?";
|
|
14184
14189
|
validateMessage(message);
|
|
@@ -14194,20 +14199,26 @@ async function runClaim(options) {
|
|
|
14194
14199
|
issue_number: number,
|
|
14195
14200
|
body: message
|
|
14196
14201
|
});
|
|
14197
|
-
|
|
14198
|
-
|
|
14199
|
-
|
|
14200
|
-
|
|
14201
|
-
|
|
14202
|
-
|
|
14203
|
-
|
|
14204
|
-
|
|
14205
|
-
|
|
14206
|
-
|
|
14207
|
-
|
|
14208
|
-
|
|
14209
|
-
|
|
14210
|
-
|
|
14202
|
+
try {
|
|
14203
|
+
const stateManager2 = getStateManager();
|
|
14204
|
+
stateManager2.addIssue({
|
|
14205
|
+
id: number,
|
|
14206
|
+
url: options.issueUrl,
|
|
14207
|
+
repo: `${owner}/${repo}`,
|
|
14208
|
+
number,
|
|
14209
|
+
title: "(claimed)",
|
|
14210
|
+
status: "claimed",
|
|
14211
|
+
labels: [],
|
|
14212
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14213
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14214
|
+
vetted: false
|
|
14215
|
+
});
|
|
14216
|
+
stateManager2.save();
|
|
14217
|
+
} catch (error) {
|
|
14218
|
+
console.error(
|
|
14219
|
+
`Warning: Comment posted on ${options.issueUrl} but failed to save to local state: ${error instanceof Error ? error.message : error}`
|
|
14220
|
+
);
|
|
14221
|
+
}
|
|
14211
14222
|
return {
|
|
14212
14223
|
commentUrl: comment.html_url,
|
|
14213
14224
|
issueUrl: options.issueUrl
|
|
@@ -14320,6 +14331,13 @@ __export(setup_exports, {
|
|
|
14320
14331
|
runCheckSetup: () => runCheckSetup,
|
|
14321
14332
|
runSetup: () => runSetup
|
|
14322
14333
|
});
|
|
14334
|
+
function parsePositiveInt(value, settingName) {
|
|
14335
|
+
const parsed = Number(value);
|
|
14336
|
+
if (!Number.isFinite(parsed) || parsed < 1 || !Number.isInteger(parsed)) {
|
|
14337
|
+
throw new ValidationError(`Invalid value for ${settingName}: "${value}". Must be a positive integer.`);
|
|
14338
|
+
}
|
|
14339
|
+
return parsed;
|
|
14340
|
+
}
|
|
14323
14341
|
async function runSetup(options) {
|
|
14324
14342
|
const stateManager2 = getStateManager();
|
|
14325
14343
|
const config = stateManager2.getState().config;
|
|
@@ -14335,18 +14353,24 @@ async function runSetup(options) {
|
|
|
14335
14353
|
stateManager2.updateConfig({ githubUsername: value });
|
|
14336
14354
|
results[key] = value;
|
|
14337
14355
|
break;
|
|
14338
|
-
case "maxActivePRs":
|
|
14339
|
-
|
|
14340
|
-
|
|
14356
|
+
case "maxActivePRs": {
|
|
14357
|
+
const maxPRs = parsePositiveInt(value, "maxActivePRs");
|
|
14358
|
+
stateManager2.updateConfig({ maxActivePRs: maxPRs });
|
|
14359
|
+
results[key] = String(maxPRs);
|
|
14341
14360
|
break;
|
|
14342
|
-
|
|
14343
|
-
|
|
14344
|
-
|
|
14361
|
+
}
|
|
14362
|
+
case "dormantDays": {
|
|
14363
|
+
const dormant = parsePositiveInt(value, "dormantDays");
|
|
14364
|
+
stateManager2.updateConfig({ dormantThresholdDays: dormant });
|
|
14365
|
+
results[key] = String(dormant);
|
|
14345
14366
|
break;
|
|
14346
|
-
|
|
14347
|
-
|
|
14348
|
-
|
|
14367
|
+
}
|
|
14368
|
+
case "approachingDays": {
|
|
14369
|
+
const approaching = parsePositiveInt(value, "approachingDays");
|
|
14370
|
+
stateManager2.updateConfig({ approachingDormantDays: approaching });
|
|
14371
|
+
results[key] = String(approaching);
|
|
14349
14372
|
break;
|
|
14373
|
+
}
|
|
14350
14374
|
case "languages":
|
|
14351
14375
|
stateManager2.updateConfig({ languages: value.split(",").map((l) => l.trim()) });
|
|
14352
14376
|
results[key] = value;
|
|
@@ -14369,9 +14393,12 @@ async function runSetup(options) {
|
|
|
14369
14393
|
}
|
|
14370
14394
|
break;
|
|
14371
14395
|
case "minStars": {
|
|
14372
|
-
const
|
|
14373
|
-
|
|
14374
|
-
|
|
14396
|
+
const stars = Number(value);
|
|
14397
|
+
if (!Number.isFinite(stars) || !Number.isInteger(stars) || stars < 0) {
|
|
14398
|
+
throw new ValidationError(`Invalid value for minStars: "${value}". Must be a non-negative integer.`);
|
|
14399
|
+
}
|
|
14400
|
+
stateManager2.updateConfig({ minStars: stars });
|
|
14401
|
+
results[key] = String(stars);
|
|
14375
14402
|
break;
|
|
14376
14403
|
}
|
|
14377
14404
|
case "includeDocIssues":
|
|
@@ -14495,6 +14522,7 @@ var init_setup = __esm({
|
|
|
14495
14522
|
"src/commands/setup.ts"() {
|
|
14496
14523
|
"use strict";
|
|
14497
14524
|
init_core();
|
|
14525
|
+
init_errors();
|
|
14498
14526
|
init_validation();
|
|
14499
14527
|
}
|
|
14500
14528
|
});
|
|
@@ -14570,7 +14598,11 @@ async function fetchDashboardData(token) {
|
|
|
14570
14598
|
digest.autoUnshelvedPRs = [];
|
|
14571
14599
|
digest.summary.totalActivePRs = prs.length - freshShelved.length;
|
|
14572
14600
|
stateManager2.setLastDigest(digest);
|
|
14573
|
-
|
|
14601
|
+
try {
|
|
14602
|
+
stateManager2.save();
|
|
14603
|
+
} catch (error) {
|
|
14604
|
+
console.error("Warning: Failed to save dashboard digest to state:", errorMessage(error));
|
|
14605
|
+
}
|
|
14574
14606
|
console.error(`Refreshed: ${prs.length} PRs fetched`);
|
|
14575
14607
|
return { digest, commentedIssues };
|
|
14576
14608
|
}
|
|
@@ -16364,6 +16396,38 @@ async function startDashboardServer(options) {
|
|
|
16364
16396
|
sendError(res, 400, 'Missing or invalid "url" field');
|
|
16365
16397
|
return;
|
|
16366
16398
|
}
|
|
16399
|
+
try {
|
|
16400
|
+
validateUrl(body.url);
|
|
16401
|
+
validateGitHubUrl(body.url, PR_URL_PATTERN, "PR");
|
|
16402
|
+
} catch (err) {
|
|
16403
|
+
if (err instanceof ValidationError) {
|
|
16404
|
+
sendError(res, 400, err.message);
|
|
16405
|
+
} else {
|
|
16406
|
+
console.error("Unexpected error during URL validation:", err);
|
|
16407
|
+
sendError(res, 400, "Invalid URL");
|
|
16408
|
+
}
|
|
16409
|
+
return;
|
|
16410
|
+
}
|
|
16411
|
+
if (body.action === "snooze") {
|
|
16412
|
+
const days = body.days ?? 7;
|
|
16413
|
+
if (typeof days !== "number" || !Number.isFinite(days) || days <= 0) {
|
|
16414
|
+
sendError(res, 400, "Snooze days must be a positive finite number");
|
|
16415
|
+
return;
|
|
16416
|
+
}
|
|
16417
|
+
if (body.reason !== void 0) {
|
|
16418
|
+
try {
|
|
16419
|
+
validateMessage(String(body.reason));
|
|
16420
|
+
} catch (err) {
|
|
16421
|
+
if (err instanceof ValidationError) {
|
|
16422
|
+
sendError(res, 400, err.message);
|
|
16423
|
+
} else {
|
|
16424
|
+
console.error("Unexpected error during message validation:", err);
|
|
16425
|
+
sendError(res, 400, "Invalid reason");
|
|
16426
|
+
}
|
|
16427
|
+
return;
|
|
16428
|
+
}
|
|
16429
|
+
}
|
|
16430
|
+
}
|
|
16367
16431
|
try {
|
|
16368
16432
|
switch (body.action) {
|
|
16369
16433
|
case "shelve":
|
|
@@ -16373,7 +16437,7 @@ async function startDashboardServer(options) {
|
|
|
16373
16437
|
stateManager2.unshelvePR(body.url);
|
|
16374
16438
|
break;
|
|
16375
16439
|
case "snooze":
|
|
16376
|
-
stateManager2.snoozePR(body.url, body.reason || "Snoozed via dashboard", body.days
|
|
16440
|
+
stateManager2.snoozePR(body.url, body.reason || "Snoozed via dashboard", body.days ?? 7);
|
|
16377
16441
|
break;
|
|
16378
16442
|
case "unsnooze":
|
|
16379
16443
|
stateManager2.unsnoozePR(body.url);
|
|
@@ -16527,6 +16591,7 @@ var init_dashboard_server = __esm({
|
|
|
16527
16591
|
path5 = __toESM(require("path"), 1);
|
|
16528
16592
|
init_core();
|
|
16529
16593
|
init_errors();
|
|
16594
|
+
init_validation();
|
|
16530
16595
|
init_dashboard_data();
|
|
16531
16596
|
init_dashboard_templates();
|
|
16532
16597
|
VALID_ACTIONS = /* @__PURE__ */ new Set(["shelve", "unshelve", "snooze", "unsnooze"]);
|
|
@@ -16580,6 +16645,8 @@ async function runDashboard(options) {
|
|
|
16580
16645
|
digest = stateManager2.getState().lastDigest;
|
|
16581
16646
|
}
|
|
16582
16647
|
} else {
|
|
16648
|
+
console.error("Warning: No GitHub token found. Using cached data (may be stale).");
|
|
16649
|
+
console.error("Set GITHUB_TOKEN or run `gh auth login` for fresh data.");
|
|
16583
16650
|
digest = stateManager2.getState().lastDigest;
|
|
16584
16651
|
}
|
|
16585
16652
|
if (!digest) {
|
|
@@ -16618,7 +16685,6 @@ async function runDashboard(options) {
|
|
|
16618
16685
|
const html = generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpened, digest, state, issueResponses);
|
|
16619
16686
|
const dashboardPath = getDashboardPath();
|
|
16620
16687
|
fs6.writeFileSync(dashboardPath, html, { mode: 420 });
|
|
16621
|
-
fs6.chmodSync(dashboardPath, 420);
|
|
16622
16688
|
if (options.offline) {
|
|
16623
16689
|
const lastUpdated = digest.generatedAt || state.lastDigestAt || state.lastRunAt;
|
|
16624
16690
|
console.log(`
|
|
@@ -16654,7 +16720,6 @@ function writeDashboardFromState() {
|
|
|
16654
16720
|
const html = generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpened, digest, state);
|
|
16655
16721
|
const dashboardPath = getDashboardPath();
|
|
16656
16722
|
fs6.writeFileSync(dashboardPath, html, { mode: 420 });
|
|
16657
|
-
fs6.chmodSync(dashboardPath, 420);
|
|
16658
16723
|
return dashboardPath;
|
|
16659
16724
|
}
|
|
16660
16725
|
function resolveAssetsDir() {
|
|
@@ -17043,7 +17108,7 @@ async function runLocalRepos(options) {
|
|
|
17043
17108
|
stateManager2.save();
|
|
17044
17109
|
} catch (error) {
|
|
17045
17110
|
const msg = errorMessage(error);
|
|
17046
|
-
|
|
17111
|
+
console.error(`Warning: Failed to cache scan results: ${msg}`);
|
|
17047
17112
|
}
|
|
17048
17113
|
return {
|
|
17049
17114
|
repos,
|
|
@@ -17424,12 +17489,20 @@ Last Run: ${data.lastRunAt || "Never"}`);
|
|
|
17424
17489
|
program2.command("search [count]").description("Search for new issues to work on").option("--json", "Output as JSON").action(async (count, options) => {
|
|
17425
17490
|
try {
|
|
17426
17491
|
const { runSearch: runSearch2 } = await Promise.resolve().then(() => (init_search(), search_exports));
|
|
17492
|
+
let maxResults = 5;
|
|
17493
|
+
if (count !== void 0) {
|
|
17494
|
+
const parsed = Number(count);
|
|
17495
|
+
if (!Number.isFinite(parsed) || parsed < 1 || !Number.isInteger(parsed)) {
|
|
17496
|
+
throw new Error(`Invalid count "${count}". Must be a positive integer.`);
|
|
17497
|
+
}
|
|
17498
|
+
maxResults = parsed;
|
|
17499
|
+
}
|
|
17427
17500
|
if (!options.json) {
|
|
17428
17501
|
console.log(`
|
|
17429
|
-
Searching for issues (max ${
|
|
17502
|
+
Searching for issues (max ${maxResults})...
|
|
17430
17503
|
`);
|
|
17431
17504
|
}
|
|
17432
|
-
const data = await runSearch2({ maxResults
|
|
17505
|
+
const data = await runSearch2({ maxResults });
|
|
17433
17506
|
if (options.json) {
|
|
17434
17507
|
outputJson(data);
|
|
17435
17508
|
} else {
|
|
@@ -17728,17 +17801,25 @@ program2.command("checkSetup").description("Check if setup is complete").option(
|
|
|
17728
17801
|
});
|
|
17729
17802
|
var dashboardCmd = program2.command("dashboard").description("Dashboard commands");
|
|
17730
17803
|
dashboardCmd.command("serve").description("Start interactive dashboard server").option("--port <port>", "Port to listen on", "3000").option("--no-open", "Do not open browser automatically").action(async (options) => {
|
|
17731
|
-
|
|
17732
|
-
|
|
17733
|
-
|
|
17734
|
-
|
|
17804
|
+
try {
|
|
17805
|
+
const port = parseInt(options.port, 10);
|
|
17806
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
17807
|
+
console.error(`Invalid port number: "${options.port}". Must be an integer between 1 and 65535.`);
|
|
17808
|
+
process.exit(1);
|
|
17809
|
+
}
|
|
17810
|
+
const { serveDashboard: serveDashboard2 } = await Promise.resolve().then(() => (init_dashboard(), dashboard_exports));
|
|
17811
|
+
await serveDashboard2({ port, open: options.open });
|
|
17812
|
+
} catch (err) {
|
|
17813
|
+
handleCommandError(err);
|
|
17735
17814
|
}
|
|
17736
|
-
const { serveDashboard: serveDashboard2 } = await Promise.resolve().then(() => (init_dashboard(), dashboard_exports));
|
|
17737
|
-
await serveDashboard2({ port, open: options.open });
|
|
17738
17815
|
});
|
|
17739
17816
|
dashboardCmd.option("--open", "Open in browser").option("--json", "Output as JSON").option("--offline", "Use cached data only (no GitHub API calls)").action(async (options) => {
|
|
17740
|
-
|
|
17741
|
-
|
|
17817
|
+
try {
|
|
17818
|
+
const { runDashboard: runDashboard2 } = await Promise.resolve().then(() => (init_dashboard(), dashboard_exports));
|
|
17819
|
+
await runDashboard2({ open: options.open, json: options.json, offline: options.offline });
|
|
17820
|
+
} catch (err) {
|
|
17821
|
+
handleCommandError(err, options.json);
|
|
17822
|
+
}
|
|
17742
17823
|
});
|
|
17743
17824
|
program2.command("parse-issue-list <path>").description("Parse a markdown issue list into structured JSON").option("--json", "Output as JSON").action(async (filePath, options) => {
|
|
17744
17825
|
try {
|
package/dist/cli.js
CHANGED
|
@@ -127,10 +127,18 @@ program
|
|
|
127
127
|
.action(async (count, options) => {
|
|
128
128
|
try {
|
|
129
129
|
const { runSearch } = await import('./commands/search.js');
|
|
130
|
+
let maxResults = 5;
|
|
131
|
+
if (count !== undefined) {
|
|
132
|
+
const parsed = Number(count);
|
|
133
|
+
if (!Number.isFinite(parsed) || parsed < 1 || !Number.isInteger(parsed)) {
|
|
134
|
+
throw new Error(`Invalid count "${count}". Must be a positive integer.`);
|
|
135
|
+
}
|
|
136
|
+
maxResults = parsed;
|
|
137
|
+
}
|
|
130
138
|
if (!options.json) {
|
|
131
|
-
console.log(`\nSearching for issues (max ${
|
|
139
|
+
console.log(`\nSearching for issues (max ${maxResults})...\n`);
|
|
132
140
|
}
|
|
133
|
-
const data = await runSearch({ maxResults
|
|
141
|
+
const data = await runSearch({ maxResults });
|
|
134
142
|
if (options.json) {
|
|
135
143
|
outputJson(data);
|
|
136
144
|
}
|
|
@@ -516,13 +524,18 @@ dashboardCmd
|
|
|
516
524
|
.option('--port <port>', 'Port to listen on', '3000')
|
|
517
525
|
.option('--no-open', 'Do not open browser automatically')
|
|
518
526
|
.action(async (options) => {
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
527
|
+
try {
|
|
528
|
+
const port = parseInt(options.port, 10);
|
|
529
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
530
|
+
console.error(`Invalid port number: "${options.port}". Must be an integer between 1 and 65535.`);
|
|
531
|
+
process.exit(1);
|
|
532
|
+
}
|
|
533
|
+
const { serveDashboard } = await import('./commands/dashboard.js');
|
|
534
|
+
await serveDashboard({ port, open: options.open });
|
|
535
|
+
}
|
|
536
|
+
catch (err) {
|
|
537
|
+
handleCommandError(err);
|
|
523
538
|
}
|
|
524
|
-
const { serveDashboard } = await import('./commands/dashboard.js');
|
|
525
|
-
await serveDashboard({ port, open: options.open });
|
|
526
539
|
});
|
|
527
540
|
// Keep bare `dashboard` (no subcommand) for backward compat — generates static HTML
|
|
528
541
|
dashboardCmd
|
|
@@ -530,8 +543,13 @@ dashboardCmd
|
|
|
530
543
|
.option('--json', 'Output as JSON')
|
|
531
544
|
.option('--offline', 'Use cached data only (no GitHub API calls)')
|
|
532
545
|
.action(async (options) => {
|
|
533
|
-
|
|
534
|
-
|
|
546
|
+
try {
|
|
547
|
+
const { runDashboard } = await import('./commands/dashboard.js');
|
|
548
|
+
await runDashboard({ open: options.open, json: options.json, offline: options.offline });
|
|
549
|
+
}
|
|
550
|
+
catch (err) {
|
|
551
|
+
handleCommandError(err, options.json);
|
|
552
|
+
}
|
|
535
553
|
});
|
|
536
554
|
// Parse issue list command (#82)
|
|
537
555
|
program
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { getStateManager, getOctokit, parseGitHubUrl, requireGitHubToken } from '../core/index.js';
|
|
6
6
|
import { paginateAll } from '../core/pagination.js';
|
|
7
|
-
import { validateUrl, validateMessage } from './validation.js';
|
|
7
|
+
import { validateUrl, validateMessage, validateGitHubUrl, PR_URL_PATTERN, ISSUE_OR_PR_URL_PATTERN, ISSUE_URL_PATTERN, } from './validation.js';
|
|
8
8
|
export async function runComments(options) {
|
|
9
9
|
validateUrl(options.prUrl);
|
|
10
|
+
validateGitHubUrl(options.prUrl, PR_URL_PATTERN, 'PR');
|
|
10
11
|
const token = requireGitHubToken();
|
|
11
12
|
const stateManager = getStateManager();
|
|
12
13
|
const octokit = getOctokit(token);
|
|
@@ -97,6 +98,7 @@ export async function runComments(options) {
|
|
|
97
98
|
}
|
|
98
99
|
export async function runPost(options) {
|
|
99
100
|
validateUrl(options.url);
|
|
101
|
+
validateGitHubUrl(options.url, ISSUE_OR_PR_URL_PATTERN, 'issue or PR');
|
|
100
102
|
if (!options.message.trim()) {
|
|
101
103
|
throw new Error('No message provided');
|
|
102
104
|
}
|
|
@@ -122,6 +124,7 @@ export async function runPost(options) {
|
|
|
122
124
|
}
|
|
123
125
|
export async function runClaim(options) {
|
|
124
126
|
validateUrl(options.issueUrl);
|
|
127
|
+
validateGitHubUrl(options.issueUrl, ISSUE_URL_PATTERN, 'issue');
|
|
125
128
|
const token = requireGitHubToken();
|
|
126
129
|
// Default claim message or custom
|
|
127
130
|
const message = options.message || "Hi! I'd like to work on this issue. Could you assign it to me?";
|
|
@@ -139,21 +142,26 @@ export async function runClaim(options) {
|
|
|
139
142
|
issue_number: number,
|
|
140
143
|
body: message,
|
|
141
144
|
});
|
|
142
|
-
// Add to tracked issues
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
145
|
+
// Add to tracked issues — non-fatal if state save fails (comment already posted)
|
|
146
|
+
try {
|
|
147
|
+
const stateManager = getStateManager();
|
|
148
|
+
stateManager.addIssue({
|
|
149
|
+
id: number,
|
|
150
|
+
url: options.issueUrl,
|
|
151
|
+
repo: `${owner}/${repo}`,
|
|
152
|
+
number,
|
|
153
|
+
title: '(claimed)',
|
|
154
|
+
status: 'claimed',
|
|
155
|
+
labels: [],
|
|
156
|
+
createdAt: new Date().toISOString(),
|
|
157
|
+
updatedAt: new Date().toISOString(),
|
|
158
|
+
vetted: false,
|
|
159
|
+
});
|
|
160
|
+
stateManager.save();
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
console.error(`Warning: Comment posted on ${options.issueUrl} but failed to save to local state: ${error instanceof Error ? error.message : error}`);
|
|
164
|
+
}
|
|
157
165
|
return {
|
|
158
166
|
commentUrl: comment.html_url,
|
|
159
167
|
issueUrl: options.issueUrl,
|
|
@@ -88,7 +88,12 @@ export async function fetchDashboardData(token) {
|
|
|
88
88
|
digest.autoUnshelvedPRs = [];
|
|
89
89
|
digest.summary.totalActivePRs = prs.length - freshShelved.length;
|
|
90
90
|
stateManager.setLastDigest(digest);
|
|
91
|
-
|
|
91
|
+
try {
|
|
92
|
+
stateManager.save();
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
console.error('Warning: Failed to save dashboard digest to state:', errorMessage(error));
|
|
96
|
+
}
|
|
92
97
|
console.error(`Refreshed: ${prs.length} PRs fetched`);
|
|
93
98
|
return { digest, commentedIssues };
|
|
94
99
|
}
|
|
@@ -9,7 +9,8 @@ import * as http from 'http';
|
|
|
9
9
|
import * as fs from 'fs';
|
|
10
10
|
import * as path from 'path';
|
|
11
11
|
import { getStateManager, getGitHubToken } from '../core/index.js';
|
|
12
|
-
import { errorMessage } from '../core/errors.js';
|
|
12
|
+
import { errorMessage, ValidationError } from '../core/errors.js';
|
|
13
|
+
import { validateUrl, validateGitHubUrl, validateMessage, PR_URL_PATTERN } from './validation.js';
|
|
13
14
|
import { fetchDashboardData, computePRsByRepo, computeTopRepos, getMonthlyData } from './dashboard-data.js';
|
|
14
15
|
import { buildDashboardStats } from './dashboard-templates.js';
|
|
15
16
|
// ── Constants ────────────────────────────────────────────────────────────────
|
|
@@ -185,6 +186,44 @@ export async function startDashboardServer(options) {
|
|
|
185
186
|
sendError(res, 400, 'Missing or invalid "url" field');
|
|
186
187
|
return;
|
|
187
188
|
}
|
|
189
|
+
// Validate URL format — same checks as CLI commands
|
|
190
|
+
try {
|
|
191
|
+
validateUrl(body.url);
|
|
192
|
+
validateGitHubUrl(body.url, PR_URL_PATTERN, 'PR');
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
if (err instanceof ValidationError) {
|
|
196
|
+
sendError(res, 400, err.message);
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
console.error('Unexpected error during URL validation:', err);
|
|
200
|
+
sendError(res, 400, 'Invalid URL');
|
|
201
|
+
}
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
// Validate snooze-specific fields
|
|
205
|
+
if (body.action === 'snooze') {
|
|
206
|
+
const days = body.days ?? 7;
|
|
207
|
+
if (typeof days !== 'number' || !Number.isFinite(days) || days <= 0) {
|
|
208
|
+
sendError(res, 400, 'Snooze days must be a positive finite number');
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (body.reason !== undefined) {
|
|
212
|
+
try {
|
|
213
|
+
validateMessage(String(body.reason));
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
if (err instanceof ValidationError) {
|
|
217
|
+
sendError(res, 400, err.message);
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
console.error('Unexpected error during message validation:', err);
|
|
221
|
+
sendError(res, 400, 'Invalid reason');
|
|
222
|
+
}
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
188
227
|
try {
|
|
189
228
|
switch (body.action) {
|
|
190
229
|
case 'shelve':
|
|
@@ -194,7 +233,7 @@ export async function startDashboardServer(options) {
|
|
|
194
233
|
stateManager.unshelvePR(body.url);
|
|
195
234
|
break;
|
|
196
235
|
case 'snooze':
|
|
197
|
-
stateManager.snoozePR(body.url, body.reason || 'Snoozed via dashboard', body.days
|
|
236
|
+
stateManager.snoozePR(body.url, body.reason || 'Snoozed via dashboard', body.days ?? 7);
|
|
198
237
|
break;
|
|
199
238
|
case 'unsnooze':
|
|
200
239
|
stateManager.unsnoozePR(body.url);
|
|
@@ -46,7 +46,9 @@ export async function runDashboard(options) {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
else {
|
|
49
|
-
// No token and not offline — fall back to cached digest
|
|
49
|
+
// No token and not offline — fall back to cached digest with warning
|
|
50
|
+
console.error('Warning: No GitHub token found. Using cached data (may be stale).');
|
|
51
|
+
console.error('Set GITHUB_TOKEN or run `gh auth login` for fresh data.');
|
|
50
52
|
digest = stateManager.getState().lastDigest;
|
|
51
53
|
}
|
|
52
54
|
// Check if we have a digest to display
|
|
@@ -89,7 +91,6 @@ export async function runDashboard(options) {
|
|
|
89
91
|
// Write to file in ~/.oss-autopilot/
|
|
90
92
|
const dashboardPath = getDashboardPath();
|
|
91
93
|
fs.writeFileSync(dashboardPath, html, { mode: 0o644 });
|
|
92
|
-
fs.chmodSync(dashboardPath, 0o644);
|
|
93
94
|
if (options.offline) {
|
|
94
95
|
const lastUpdated = digest.generatedAt || state.lastDigestAt || state.lastRunAt;
|
|
95
96
|
console.log(`\n📊 Dashboard generated (offline, cached data from ${lastUpdated}): ${dashboardPath}`);
|
|
@@ -131,7 +132,6 @@ export function writeDashboardFromState() {
|
|
|
131
132
|
const html = generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpened, digest, state);
|
|
132
133
|
const dashboardPath = getDashboardPath();
|
|
133
134
|
fs.writeFileSync(dashboardPath, html, { mode: 0o644 });
|
|
134
|
-
fs.chmodSync(dashboardPath, 0o644);
|
|
135
135
|
return dashboardPath;
|
|
136
136
|
}
|
|
137
137
|
/**
|
|
@@ -117,7 +117,7 @@ export async function runLocalRepos(options) {
|
|
|
117
117
|
}
|
|
118
118
|
catch (error) {
|
|
119
119
|
const msg = errorMessage(error);
|
|
120
|
-
|
|
120
|
+
console.error(`Warning: Failed to cache scan results: ${msg}`);
|
|
121
121
|
}
|
|
122
122
|
return {
|
|
123
123
|
repos,
|
package/dist/commands/setup.js
CHANGED
|
@@ -3,7 +3,16 @@
|
|
|
3
3
|
* Interactive setup / configuration
|
|
4
4
|
*/
|
|
5
5
|
import { getStateManager, DEFAULT_CONFIG } from '../core/index.js';
|
|
6
|
+
import { ValidationError } from '../core/errors.js';
|
|
6
7
|
import { validateGitHubUsername } from './validation.js';
|
|
8
|
+
/** Parse and validate a positive integer setting value. */
|
|
9
|
+
function parsePositiveInt(value, settingName) {
|
|
10
|
+
const parsed = Number(value);
|
|
11
|
+
if (!Number.isFinite(parsed) || parsed < 1 || !Number.isInteger(parsed)) {
|
|
12
|
+
throw new ValidationError(`Invalid value for ${settingName}: "${value}". Must be a positive integer.`);
|
|
13
|
+
}
|
|
14
|
+
return parsed;
|
|
15
|
+
}
|
|
7
16
|
export async function runSetup(options) {
|
|
8
17
|
const stateManager = getStateManager();
|
|
9
18
|
const config = stateManager.getState().config;
|
|
@@ -20,18 +29,24 @@ export async function runSetup(options) {
|
|
|
20
29
|
stateManager.updateConfig({ githubUsername: value });
|
|
21
30
|
results[key] = value;
|
|
22
31
|
break;
|
|
23
|
-
case 'maxActivePRs':
|
|
24
|
-
|
|
25
|
-
|
|
32
|
+
case 'maxActivePRs': {
|
|
33
|
+
const maxPRs = parsePositiveInt(value, 'maxActivePRs');
|
|
34
|
+
stateManager.updateConfig({ maxActivePRs: maxPRs });
|
|
35
|
+
results[key] = String(maxPRs);
|
|
26
36
|
break;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
37
|
+
}
|
|
38
|
+
case 'dormantDays': {
|
|
39
|
+
const dormant = parsePositiveInt(value, 'dormantDays');
|
|
40
|
+
stateManager.updateConfig({ dormantThresholdDays: dormant });
|
|
41
|
+
results[key] = String(dormant);
|
|
30
42
|
break;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
43
|
+
}
|
|
44
|
+
case 'approachingDays': {
|
|
45
|
+
const approaching = parsePositiveInt(value, 'approachingDays');
|
|
46
|
+
stateManager.updateConfig({ approachingDormantDays: approaching });
|
|
47
|
+
results[key] = String(approaching);
|
|
34
48
|
break;
|
|
49
|
+
}
|
|
35
50
|
case 'languages':
|
|
36
51
|
stateManager.updateConfig({ languages: value.split(',').map((l) => l.trim()) });
|
|
37
52
|
results[key] = value;
|
|
@@ -55,9 +70,12 @@ export async function runSetup(options) {
|
|
|
55
70
|
}
|
|
56
71
|
break;
|
|
57
72
|
case 'minStars': {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
73
|
+
const stars = Number(value);
|
|
74
|
+
if (!Number.isFinite(stars) || !Number.isInteger(stars) || stars < 0) {
|
|
75
|
+
throw new ValidationError(`Invalid value for minStars: "${value}". Must be a non-negative integer.`);
|
|
76
|
+
}
|
|
77
|
+
stateManager.updateConfig({ minStars: stars });
|
|
78
|
+
results[key] = String(stars);
|
|
61
79
|
break;
|
|
62
80
|
}
|
|
63
81
|
case 'includeDocIssues':
|
package/dist/core/daily-logic.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* - formatBriefSummary / formatSummary / printDigest — rendering
|
|
12
12
|
*/
|
|
13
13
|
import { formatRelativeTime } from './utils.js';
|
|
14
|
+
import { warn } from './logger.js';
|
|
14
15
|
// ---------------------------------------------------------------------------
|
|
15
16
|
// Constants
|
|
16
17
|
// ---------------------------------------------------------------------------
|
|
@@ -45,7 +46,7 @@ function buildRepoMap(prs, label) {
|
|
|
45
46
|
const repoMap = new Map();
|
|
46
47
|
for (const pr of prs) {
|
|
47
48
|
if (!pr.repo) {
|
|
48
|
-
|
|
49
|
+
warn(label, `Skipping PR #${pr.number} (${pr.url}) with empty repo field`);
|
|
49
50
|
continue;
|
|
50
51
|
}
|
|
51
52
|
const existing = repoMap.get(pr.repo) || [];
|