@oss-autopilot/core 0.42.1 → 0.42.3
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 +804 -751
- package/dist/cli.js +14 -14
- package/dist/commands/daily.d.ts +0 -1
- package/dist/commands/daily.js +4 -3
- package/dist/commands/dashboard-components.d.ts +33 -0
- package/dist/commands/dashboard-components.js +57 -0
- package/dist/commands/dashboard-formatters.d.ts +20 -0
- package/dist/commands/dashboard-formatters.js +33 -0
- package/dist/commands/dashboard-scripts.d.ts +7 -0
- package/dist/commands/dashboard-scripts.js +281 -0
- package/dist/commands/dashboard-styles.d.ts +5 -0
- package/dist/commands/dashboard-styles.js +765 -0
- package/dist/commands/dashboard-templates.d.ts +6 -18
- package/dist/commands/dashboard-templates.js +30 -1134
- package/dist/commands/dismiss.d.ts +4 -6
- package/dist/commands/dismiss.js +11 -13
- package/dist/commands/validation.d.ts +3 -1
- package/dist/commands/validation.js +8 -2
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/issue-discovery.d.ts +5 -0
- package/dist/core/issue-discovery.js +56 -79
- package/dist/core/logger.d.ts +5 -0
- package/dist/core/logger.js +8 -0
- package/dist/core/pr-monitor.js +5 -0
- package/dist/core/review-analysis.js +10 -7
- package/dist/core/state.d.ts +7 -7
- package/dist/core/state.js +7 -7
- package/dist/core/test-utils.d.ts +14 -0
- package/dist/core/test-utils.js +125 -0
- package/dist/core/types.d.ts +1 -1
- package/dist/formatters/json.d.ts +0 -1
- package/package.json +1 -1
package/dist/cli.bundle.cjs
CHANGED
|
@@ -3543,6 +3543,10 @@ function debug(module2, message, ...args) {
|
|
|
3543
3543
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
3544
3544
|
console.error(`[${timestamp}] [DEBUG] [${module2}] ${message}`, ...args);
|
|
3545
3545
|
}
|
|
3546
|
+
function info(module2, message, ...args) {
|
|
3547
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
3548
|
+
console.error(`[${timestamp}] [INFO] [${module2}] ${message}`, ...args);
|
|
3549
|
+
}
|
|
3546
3550
|
function warn(module2, message, ...args) {
|
|
3547
3551
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
3548
3552
|
console.error(`[${timestamp}] [WARN] [${module2}] ${message}`, ...args);
|
|
@@ -4383,10 +4387,10 @@ var init_state = __esm({
|
|
|
4383
4387
|
}
|
|
4384
4388
|
// === Dismiss / Undismiss Issues ===
|
|
4385
4389
|
/**
|
|
4386
|
-
* Dismiss an issue by URL. Dismissed
|
|
4390
|
+
* Dismiss an issue or PR by URL. Dismissed URLs are excluded from `new_response` notifications
|
|
4387
4391
|
* until new activity occurs after the dismiss timestamp.
|
|
4388
|
-
* @param url - The full GitHub issue URL.
|
|
4389
|
-
* @param timestamp - ISO timestamp of when the issue was dismissed.
|
|
4392
|
+
* @param url - The full GitHub issue or PR URL.
|
|
4393
|
+
* @param timestamp - ISO timestamp of when the issue/PR was dismissed.
|
|
4390
4394
|
* @returns true if newly dismissed, false if already dismissed.
|
|
4391
4395
|
*/
|
|
4392
4396
|
dismissIssue(url, timestamp) {
|
|
@@ -4400,8 +4404,8 @@ var init_state = __esm({
|
|
|
4400
4404
|
return true;
|
|
4401
4405
|
}
|
|
4402
4406
|
/**
|
|
4403
|
-
* Undismiss an issue by URL.
|
|
4404
|
-
* @param url - The full GitHub issue URL.
|
|
4407
|
+
* Undismiss an issue or PR by URL.
|
|
4408
|
+
* @param url - The full GitHub issue or PR URL.
|
|
4405
4409
|
* @returns true if found and removed, false if not dismissed.
|
|
4406
4410
|
*/
|
|
4407
4411
|
undismissIssue(url) {
|
|
@@ -4412,8 +4416,8 @@ var init_state = __esm({
|
|
|
4412
4416
|
return true;
|
|
4413
4417
|
}
|
|
4414
4418
|
/**
|
|
4415
|
-
* Get the timestamp when an issue was dismissed.
|
|
4416
|
-
* @param url - The full GitHub issue URL.
|
|
4419
|
+
* Get the timestamp when an issue or PR was dismissed.
|
|
4420
|
+
* @param url - The full GitHub issue or PR URL.
|
|
4417
4421
|
* @returns The ISO dismiss timestamp, or undefined if not dismissed.
|
|
4418
4422
|
*/
|
|
4419
4423
|
getIssueDismissedAt(url) {
|
|
@@ -4465,11 +4469,11 @@ var init_state = __esm({
|
|
|
4465
4469
|
* @returns true if the PR is snoozed and the snooze has not expired.
|
|
4466
4470
|
*/
|
|
4467
4471
|
isSnoozed(url) {
|
|
4468
|
-
const
|
|
4469
|
-
if (!
|
|
4470
|
-
const expiresAtMs = new Date(
|
|
4472
|
+
const info2 = this.getSnoozeInfo(url);
|
|
4473
|
+
if (!info2) return false;
|
|
4474
|
+
const expiresAtMs = new Date(info2.expiresAt).getTime();
|
|
4471
4475
|
if (isNaN(expiresAtMs)) {
|
|
4472
|
-
warn(MODULE2, `Invalid expiresAt for snoozed PR ${url}: "${
|
|
4476
|
+
warn(MODULE2, `Invalid expiresAt for snoozed PR ${url}: "${info2.expiresAt}". Treating as not snoozed.`);
|
|
4473
4477
|
return false;
|
|
4474
4478
|
}
|
|
4475
4479
|
return expiresAtMs > Date.now();
|
|
@@ -4490,8 +4494,8 @@ var init_state = __esm({
|
|
|
4490
4494
|
if (!this.state.config.snoozedPRs) return [];
|
|
4491
4495
|
const expired = [];
|
|
4492
4496
|
const now = Date.now();
|
|
4493
|
-
for (const [url,
|
|
4494
|
-
const expiresAtMs = new Date(
|
|
4497
|
+
for (const [url, info2] of Object.entries(this.state.config.snoozedPRs)) {
|
|
4498
|
+
const expiresAtMs = new Date(info2.expiresAt).getTime();
|
|
4495
4499
|
if (isNaN(expiresAtMs) || expiresAtMs <= now) {
|
|
4496
4500
|
expired.push(url);
|
|
4497
4501
|
}
|
|
@@ -9968,8 +9972,8 @@ function throttling(octokit, octokitOptions) {
|
|
|
9968
9972
|
"error",
|
|
9969
9973
|
(e) => octokit.log.warn("Error in throttling-plugin limit handler", e)
|
|
9970
9974
|
);
|
|
9971
|
-
state.retryLimiter.on("failed", async function(error,
|
|
9972
|
-
const [state2, request2, options] =
|
|
9975
|
+
state.retryLimiter.on("failed", async function(error, info2) {
|
|
9976
|
+
const [state2, request2, options] = info2.args;
|
|
9973
9977
|
const { pathname } = new URL(options.url, "http://github.test");
|
|
9974
9978
|
const shouldRetryGraphQL = pathname.startsWith("/graphql") && error.status !== 401;
|
|
9975
9979
|
if (!(shouldRetryGraphQL || error.status === 403 || error.status === 429)) {
|
|
@@ -10617,14 +10621,14 @@ function checkUnrespondedComments(comments, reviews, reviewComments, username) {
|
|
|
10617
10621
|
for (const review of reviews) {
|
|
10618
10622
|
if (!review.submitted_at) continue;
|
|
10619
10623
|
const body = (review.body || "").trim();
|
|
10620
|
-
if (!body && review.state !== "COMMENTED") continue;
|
|
10624
|
+
if (!body && review.state !== "COMMENTED" && review.state !== "CHANGES_REQUESTED") continue;
|
|
10621
10625
|
const author = review.user?.login || "unknown";
|
|
10622
10626
|
if (!body && review.state === "COMMENTED" && review.id != null) {
|
|
10623
10627
|
if (isAllSelfReplies(review.id, reviewComments)) {
|
|
10624
10628
|
continue;
|
|
10625
10629
|
}
|
|
10626
10630
|
}
|
|
10627
|
-
const resolvedBody = body || (review.id != null ? getInlineCommentBody(review.id, reviewComments) : void 0) || "(posted inline review comments)";
|
|
10631
|
+
const resolvedBody = body || (review.id != null ? getInlineCommentBody(review.id, reviewComments) : void 0) || (review.state === "CHANGES_REQUESTED" ? "(requested changes via inline review comments)" : "(posted inline review comments)");
|
|
10628
10632
|
timeline.push({
|
|
10629
10633
|
author,
|
|
10630
10634
|
body: resolvedBody,
|
|
@@ -11291,6 +11295,9 @@ var init_pr_monitor = __esm({
|
|
|
11291
11295
|
} = input;
|
|
11292
11296
|
if (hasUnrespondedComment) {
|
|
11293
11297
|
if (latestCommitDate && lastMaintainerCommentDate && latestCommitDate > lastMaintainerCommentDate) {
|
|
11298
|
+
if (latestChangesRequestedDate && latestCommitDate < latestChangesRequestedDate) {
|
|
11299
|
+
return "needs_response";
|
|
11300
|
+
}
|
|
11294
11301
|
if (ciStatus === "failing") return "failing_ci";
|
|
11295
11302
|
return "changes_addressed";
|
|
11296
11303
|
}
|
|
@@ -12178,7 +12185,7 @@ var init_issue_discovery = __esm({
|
|
|
12178
12185
|
* Updates the state manager with the list and timestamp.
|
|
12179
12186
|
*/
|
|
12180
12187
|
async fetchStarredRepos() {
|
|
12181
|
-
|
|
12188
|
+
info(MODULE9, "Fetching starred repositories...");
|
|
12182
12189
|
const starredRepos = [];
|
|
12183
12190
|
try {
|
|
12184
12191
|
const iterator2 = this.octokit.paginate.iterator(this.octokit.activity.listReposStarredByAuthenticatedUser, {
|
|
@@ -12199,11 +12206,11 @@ var init_issue_discovery = __esm({
|
|
|
12199
12206
|
}
|
|
12200
12207
|
pageCount++;
|
|
12201
12208
|
if (pageCount >= 5) {
|
|
12202
|
-
|
|
12209
|
+
info(MODULE9, "Reached pagination limit for starred repos (500)");
|
|
12203
12210
|
break;
|
|
12204
12211
|
}
|
|
12205
12212
|
}
|
|
12206
|
-
|
|
12213
|
+
info(MODULE9, `Fetched ${starredRepos.length} starred repositories`);
|
|
12207
12214
|
this.stateManager.setStarredRepos(starredRepos);
|
|
12208
12215
|
return starredRepos;
|
|
12209
12216
|
} catch (error) {
|
|
@@ -12234,6 +12241,48 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
|
|
|
12234
12241
|
}
|
|
12235
12242
|
return this.stateManager.getStarredRepos();
|
|
12236
12243
|
}
|
|
12244
|
+
/**
|
|
12245
|
+
* Shared pipeline for Phases 2 and 3: spam-filter, repo-exclusion, vetting, and star-count filter.
|
|
12246
|
+
* Extracts the common logic so each phase only needs to supply search results and context.
|
|
12247
|
+
*/
|
|
12248
|
+
async filterVetAndScore(items, filterIssues, excludedRepoSets, remainingNeeded, minStars, phaseLabel) {
|
|
12249
|
+
const spamRepos = detectLabelFarmingRepos(items);
|
|
12250
|
+
if (spamRepos.size > 0) {
|
|
12251
|
+
const spamCount = items.filter((i) => spamRepos.has(i.repository_url.split("/").slice(-2).join("/"))).length;
|
|
12252
|
+
debug(
|
|
12253
|
+
MODULE9,
|
|
12254
|
+
`[SPAM_FILTER] Filtered ${spamCount} issues from ${spamRepos.size} label-farming repos: ${[...spamRepos].join(", ")}`
|
|
12255
|
+
);
|
|
12256
|
+
}
|
|
12257
|
+
const itemsToVet = filterIssues(items).filter((item) => {
|
|
12258
|
+
const repoFullName = item.repository_url.split("/").slice(-2).join("/");
|
|
12259
|
+
if (spamRepos.has(repoFullName)) return false;
|
|
12260
|
+
return excludedRepoSets.every((s) => !s.has(repoFullName));
|
|
12261
|
+
}).slice(0, remainingNeeded * 2);
|
|
12262
|
+
if (itemsToVet.length === 0) {
|
|
12263
|
+
debug(MODULE9, `[${phaseLabel}] All ${items.length} items filtered before vetting`);
|
|
12264
|
+
return { candidates: [], allVetFailed: false, rateLimitHit: false };
|
|
12265
|
+
}
|
|
12266
|
+
const {
|
|
12267
|
+
candidates: results,
|
|
12268
|
+
allFailed: allVetFailed,
|
|
12269
|
+
rateLimitHit
|
|
12270
|
+
} = await this.vetter.vetIssuesParallel(
|
|
12271
|
+
itemsToVet.map((i) => i.html_url),
|
|
12272
|
+
remainingNeeded,
|
|
12273
|
+
"normal"
|
|
12274
|
+
);
|
|
12275
|
+
const starFiltered = results.filter((c) => {
|
|
12276
|
+
if (c.projectHealth.checkFailed) return true;
|
|
12277
|
+
const stars = c.projectHealth.stargazersCount ?? 0;
|
|
12278
|
+
return stars >= minStars;
|
|
12279
|
+
});
|
|
12280
|
+
const starFilteredCount = results.length - starFiltered.length;
|
|
12281
|
+
if (starFilteredCount > 0) {
|
|
12282
|
+
debug(MODULE9, `[STAR_FILTER] Filtered ${starFilteredCount} ${phaseLabel} candidates below ${minStars} stars`);
|
|
12283
|
+
}
|
|
12284
|
+
return { candidates: starFiltered, allVetFailed, rateLimitHit };
|
|
12285
|
+
}
|
|
12237
12286
|
/**
|
|
12238
12287
|
* Search for issues matching our criteria.
|
|
12239
12288
|
* Searches in priority order: merged-PR repos first (no label filter), then starred repos,
|
|
@@ -12245,6 +12294,7 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
|
|
|
12245
12294
|
const languages = options.languages || config.languages;
|
|
12246
12295
|
const labels = options.labels || config.labels;
|
|
12247
12296
|
const maxResults = options.maxResults || 10;
|
|
12297
|
+
const minStars = config.minStars ?? 50;
|
|
12248
12298
|
const allCandidates = [];
|
|
12249
12299
|
let phase0Error = null;
|
|
12250
12300
|
let phase1Error = null;
|
|
@@ -12281,7 +12331,8 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
|
|
|
12281
12331
|
const includeDocIssues = config.includeDocIssues ?? true;
|
|
12282
12332
|
const aiBlocklisted = new Set(config.aiPolicyBlocklist ?? DEFAULT_CONFIG.aiPolicyBlocklist ?? []);
|
|
12283
12333
|
if (aiBlocklisted.size > 0) {
|
|
12284
|
-
|
|
12334
|
+
debug(
|
|
12335
|
+
MODULE9,
|
|
12285
12336
|
`[AI_POLICY_FILTER] Filtering issues from ${aiBlocklisted.size} blocklisted repo(s): ${[...aiBlocklisted].join(", ")}`
|
|
12286
12337
|
);
|
|
12287
12338
|
}
|
|
@@ -12304,7 +12355,8 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
|
|
|
12304
12355
|
if (phase0Repos.length > 0) {
|
|
12305
12356
|
const mergedInPhase0 = Math.min(mergedPRRepos.length, phase0Repos.length);
|
|
12306
12357
|
const openInPhase0 = phase0Repos.length - mergedInPhase0;
|
|
12307
|
-
|
|
12358
|
+
info(
|
|
12359
|
+
MODULE9,
|
|
12308
12360
|
`Phase 0: Searching issues in ${phase0Repos.length} repos (${mergedInPhase0} merged-PR, ${openInPhase0} open-PR, no label filter)...`
|
|
12309
12361
|
);
|
|
12310
12362
|
const mergedPhase0Repos = phase0Repos.slice(0, mergedInPhase0);
|
|
@@ -12323,7 +12375,7 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
|
|
|
12323
12375
|
if (rateLimitHit) {
|
|
12324
12376
|
rateLimitHitDuringSearch = true;
|
|
12325
12377
|
}
|
|
12326
|
-
|
|
12378
|
+
info(MODULE9, `Found ${mergedCandidates.length} candidates from merged-PR repos`);
|
|
12327
12379
|
}
|
|
12328
12380
|
}
|
|
12329
12381
|
const openPhase0Repos = phase0Repos.slice(mergedInPhase0);
|
|
@@ -12343,14 +12395,14 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
|
|
|
12343
12395
|
if (rateLimitHit) {
|
|
12344
12396
|
rateLimitHitDuringSearch = true;
|
|
12345
12397
|
}
|
|
12346
|
-
|
|
12398
|
+
info(MODULE9, `Found ${openCandidates.length} candidates from open-PR repos`);
|
|
12347
12399
|
}
|
|
12348
12400
|
}
|
|
12349
12401
|
}
|
|
12350
12402
|
if (allCandidates.length < maxResults && starredRepos.length > 0) {
|
|
12351
12403
|
const reposToSearch = starredRepos.filter((r) => !phase0RepoSet.has(r));
|
|
12352
12404
|
if (reposToSearch.length > 0) {
|
|
12353
|
-
|
|
12405
|
+
info(MODULE9, `Phase 1: Searching issues in ${reposToSearch.length} starred repos...`);
|
|
12354
12406
|
const remainingNeeded = maxResults - allCandidates.length;
|
|
12355
12407
|
if (remainingNeeded > 0) {
|
|
12356
12408
|
const {
|
|
@@ -12365,13 +12417,13 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
|
|
|
12365
12417
|
if (rateLimitHit) {
|
|
12366
12418
|
rateLimitHitDuringSearch = true;
|
|
12367
12419
|
}
|
|
12368
|
-
|
|
12420
|
+
info(MODULE9, `Found ${starredCandidates.length} candidates from starred repos`);
|
|
12369
12421
|
}
|
|
12370
12422
|
}
|
|
12371
12423
|
}
|
|
12372
12424
|
let phase2Error = null;
|
|
12373
12425
|
if (allCandidates.length < maxResults) {
|
|
12374
|
-
|
|
12426
|
+
info(MODULE9, "Phase 2: General issue search...");
|
|
12375
12427
|
const remainingNeeded = maxResults - allCandidates.length;
|
|
12376
12428
|
try {
|
|
12377
12429
|
const { data } = await this.octokit.search.issuesAndPullRequests({
|
|
@@ -12381,43 +12433,20 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
|
|
|
12381
12433
|
per_page: remainingNeeded * 3
|
|
12382
12434
|
// Fetch extra since some will be filtered
|
|
12383
12435
|
});
|
|
12384
|
-
|
|
12385
|
-
const spamRepos = detectLabelFarmingRepos(data.items);
|
|
12386
|
-
if (spamRepos.size > 0) {
|
|
12387
|
-
const spamCount = data.items.filter(
|
|
12388
|
-
(i) => spamRepos.has(i.repository_url.split("/").slice(-2).join("/"))
|
|
12389
|
-
).length;
|
|
12390
|
-
console.log(
|
|
12391
|
-
`[SPAM_FILTER] Filtered ${spamCount} issues from ${spamRepos.size} label-farming repos: ${[...spamRepos].join(", ")}`
|
|
12392
|
-
);
|
|
12393
|
-
}
|
|
12436
|
+
info(MODULE9, `Found ${data.total_count} issues in general search, processing top ${data.items.length}...`);
|
|
12394
12437
|
const seenRepos = new Set(allCandidates.map((c) => c.issue.repo));
|
|
12395
|
-
const itemsToVet = filterIssues(data.items).filter((item) => {
|
|
12396
|
-
const repoFullName = item.repository_url.split("/").slice(-2).join("/");
|
|
12397
|
-
return !spamRepos.has(repoFullName);
|
|
12398
|
-
}).filter((item) => {
|
|
12399
|
-
const repoFullName = item.repository_url.split("/").slice(-2).join("/");
|
|
12400
|
-
return !phase0RepoSet.has(repoFullName) && !starredRepoSet.has(repoFullName) && !seenRepos.has(repoFullName);
|
|
12401
|
-
}).slice(0, remainingNeeded * 2);
|
|
12402
12438
|
const {
|
|
12403
|
-
candidates:
|
|
12404
|
-
|
|
12439
|
+
candidates: starFiltered,
|
|
12440
|
+
allVetFailed,
|
|
12405
12441
|
rateLimitHit: vetRateLimitHit
|
|
12406
|
-
} = await this.
|
|
12407
|
-
|
|
12442
|
+
} = await this.filterVetAndScore(
|
|
12443
|
+
data.items,
|
|
12444
|
+
filterIssues,
|
|
12445
|
+
[phase0RepoSet, starredRepoSet, seenRepos],
|
|
12408
12446
|
remainingNeeded,
|
|
12409
|
-
|
|
12447
|
+
minStars,
|
|
12448
|
+
"Phase 2"
|
|
12410
12449
|
);
|
|
12411
|
-
const minStars = config.minStars ?? 50;
|
|
12412
|
-
const starFiltered = results.filter((c) => {
|
|
12413
|
-
if (c.projectHealth.checkFailed) return true;
|
|
12414
|
-
const stars = c.projectHealth.stargazersCount ?? 0;
|
|
12415
|
-
return stars >= minStars;
|
|
12416
|
-
});
|
|
12417
|
-
const starFilteredCount = results.length - starFiltered.length;
|
|
12418
|
-
if (starFilteredCount > 0) {
|
|
12419
|
-
console.log(`[STAR_FILTER] Filtered ${starFilteredCount} candidates below ${minStars} stars`);
|
|
12420
|
-
}
|
|
12421
12450
|
allCandidates.push(...starFiltered);
|
|
12422
12451
|
if (allVetFailed) {
|
|
12423
12452
|
phase2Error = (phase2Error ? phase2Error + "; " : "") + "all vetting failed";
|
|
@@ -12425,7 +12454,7 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
|
|
|
12425
12454
|
if (vetRateLimitHit) {
|
|
12426
12455
|
rateLimitHitDuringSearch = true;
|
|
12427
12456
|
}
|
|
12428
|
-
|
|
12457
|
+
info(MODULE9, `Found ${starFiltered.length} candidates from general search`);
|
|
12429
12458
|
} catch (error) {
|
|
12430
12459
|
const errMsg = errorMessage(error);
|
|
12431
12460
|
phase2Error = errMsg;
|
|
@@ -12437,13 +12466,12 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
|
|
|
12437
12466
|
}
|
|
12438
12467
|
let phase3Error = null;
|
|
12439
12468
|
if (allCandidates.length < maxResults) {
|
|
12440
|
-
|
|
12469
|
+
info(MODULE9, "Phase 3: Searching actively maintained repos...");
|
|
12441
12470
|
const remainingNeeded = maxResults - allCandidates.length;
|
|
12442
12471
|
const thirtyDaysAgo = /* @__PURE__ */ new Date();
|
|
12443
12472
|
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
|
12444
12473
|
const pushedSince = thirtyDaysAgo.toISOString().split("T")[0];
|
|
12445
|
-
const
|
|
12446
|
-
const phase3Query = `is:issue is:open no:assignee ${langQuery} stars:>=${phase3MinStars} pushed:>=${pushedSince} archived:false`.replace(/ +/g, " ").trim();
|
|
12474
|
+
const phase3Query = `is:issue is:open no:assignee ${langQuery} stars:>=${minStars} pushed:>=${pushedSince} archived:false`.replace(/ +/g, " ").trim();
|
|
12447
12475
|
try {
|
|
12448
12476
|
const { data } = await this.octokit.search.issuesAndPullRequests({
|
|
12449
12477
|
q: phase3Query,
|
|
@@ -12451,42 +12479,23 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
|
|
|
12451
12479
|
order: "desc",
|
|
12452
12480
|
per_page: remainingNeeded * 3
|
|
12453
12481
|
});
|
|
12454
|
-
|
|
12482
|
+
info(
|
|
12483
|
+
MODULE9,
|
|
12455
12484
|
`Found ${data.total_count} issues in maintained-repo search, processing top ${data.items.length}...`
|
|
12456
12485
|
);
|
|
12457
|
-
const spamRepos = detectLabelFarmingRepos(data.items);
|
|
12458
|
-
if (spamRepos.size > 0) {
|
|
12459
|
-
const spamCount = data.items.filter(
|
|
12460
|
-
(i) => spamRepos.has(i.repository_url.split("/").slice(-2).join("/"))
|
|
12461
|
-
).length;
|
|
12462
|
-
console.log(
|
|
12463
|
-
`[SPAM_FILTER] Filtered ${spamCount} issues from ${spamRepos.size} label-farming repos: ${[...spamRepos].join(", ")}`
|
|
12464
|
-
);
|
|
12465
|
-
}
|
|
12466
12486
|
const seenRepos = new Set(allCandidates.map((c) => c.issue.repo));
|
|
12467
|
-
const itemsToVet = filterIssues(data.items).filter((item) => {
|
|
12468
|
-
const repoFullName = item.repository_url.split("/").slice(-2).join("/");
|
|
12469
|
-
return !spamRepos.has(repoFullName) && !phase0RepoSet.has(repoFullName) && !starredRepoSet.has(repoFullName) && !seenRepos.has(repoFullName);
|
|
12470
|
-
}).slice(0, remainingNeeded * 2);
|
|
12471
12487
|
const {
|
|
12472
|
-
candidates:
|
|
12473
|
-
|
|
12488
|
+
candidates: starFiltered,
|
|
12489
|
+
allVetFailed,
|
|
12474
12490
|
rateLimitHit: vetRateLimitHit
|
|
12475
|
-
} = await this.
|
|
12476
|
-
|
|
12491
|
+
} = await this.filterVetAndScore(
|
|
12492
|
+
data.items,
|
|
12493
|
+
filterIssues,
|
|
12494
|
+
[phase0RepoSet, starredRepoSet, seenRepos],
|
|
12477
12495
|
remainingNeeded,
|
|
12478
|
-
|
|
12496
|
+
minStars,
|
|
12497
|
+
"Phase 3"
|
|
12479
12498
|
);
|
|
12480
|
-
const minStars = config.minStars ?? 50;
|
|
12481
|
-
const starFiltered = results.filter((c) => {
|
|
12482
|
-
if (c.projectHealth.checkFailed) return true;
|
|
12483
|
-
const stars = c.projectHealth.stargazersCount ?? 0;
|
|
12484
|
-
return stars >= minStars;
|
|
12485
|
-
});
|
|
12486
|
-
const starFilteredCount = results.length - starFiltered.length;
|
|
12487
|
-
if (starFilteredCount > 0) {
|
|
12488
|
-
console.log(`[STAR_FILTER] Filtered ${starFilteredCount} Phase 3 candidates below ${minStars} stars`);
|
|
12489
|
-
}
|
|
12490
12499
|
allCandidates.push(...starFiltered);
|
|
12491
12500
|
if (allVetFailed) {
|
|
12492
12501
|
phase3Error = "all vetting failed";
|
|
@@ -12494,7 +12503,7 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
|
|
|
12494
12503
|
if (vetRateLimitHit) {
|
|
12495
12504
|
rateLimitHitDuringSearch = true;
|
|
12496
12505
|
}
|
|
12497
|
-
|
|
12506
|
+
info(MODULE9, `Found ${starFiltered.length} candidates from maintained-repo search`);
|
|
12498
12507
|
} catch (error) {
|
|
12499
12508
|
const errMsg = errorMessage(error);
|
|
12500
12509
|
phase3Error = errMsg;
|
|
@@ -12665,7 +12674,7 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
|
|
|
12665
12674
|
content += `- **Recommendation**: Y = approve, N = skip, ? = needs_review
|
|
12666
12675
|
`;
|
|
12667
12676
|
fs4.writeFileSync(outputFile, content, "utf-8");
|
|
12668
|
-
|
|
12677
|
+
info(MODULE9, `Saved ${sorted.length} issues to ${outputFile}`);
|
|
12669
12678
|
return outputFile;
|
|
12670
12679
|
}
|
|
12671
12680
|
/**
|
|
@@ -13698,14 +13707,15 @@ function generateDigestOutput(digest, activePRs, shelvedPRs, commentedIssues, fa
|
|
|
13698
13707
|
const snoozedUrls = new Set(
|
|
13699
13708
|
Object.keys(stateManager2.getState().config.snoozedPRs ?? {}).filter((url) => stateManager2.isSnoozed(url))
|
|
13700
13709
|
);
|
|
13701
|
-
const
|
|
13710
|
+
const dismissedUrls = new Set(Object.keys(stateManager2.getState().config.dismissedIssues ?? {}));
|
|
13711
|
+
const nonDismissedPRs = activePRs.filter((pr) => !dismissedUrls.has(pr.url));
|
|
13712
|
+
const actionableIssues = collectActionableIssues(nonDismissedPRs, snoozedUrls);
|
|
13702
13713
|
digest.summary.totalNeedingAttention = actionableIssues.length;
|
|
13703
13714
|
const briefSummary = formatBriefSummary(digest, actionableIssues.length, issueResponses.length);
|
|
13704
13715
|
const actionMenu = computeActionMenu(actionableIssues, capacity, filteredCommentedIssues);
|
|
13705
13716
|
const repoGroups = groupPRsByRepo(activePRs);
|
|
13706
13717
|
return {
|
|
13707
13718
|
digest,
|
|
13708
|
-
updates: [],
|
|
13709
13719
|
capacity,
|
|
13710
13720
|
summary,
|
|
13711
13721
|
briefSummary,
|
|
@@ -13719,7 +13729,6 @@ function generateDigestOutput(digest, activePRs, shelvedPRs, commentedIssues, fa
|
|
|
13719
13729
|
function toDailyOutput(result) {
|
|
13720
13730
|
return {
|
|
13721
13731
|
digest: deduplicateDigest(result.digest),
|
|
13722
|
-
updates: result.updates,
|
|
13723
13732
|
capacity: result.capacity,
|
|
13724
13733
|
summary: result.summary,
|
|
13725
13734
|
briefSummary: result.briefSummary,
|
|
@@ -13856,8 +13865,12 @@ var init_search = __esm({
|
|
|
13856
13865
|
// src/commands/validation.ts
|
|
13857
13866
|
function validateGitHubUrl(url, pattern, entityType) {
|
|
13858
13867
|
if (pattern.test(url)) return;
|
|
13859
|
-
const
|
|
13860
|
-
|
|
13868
|
+
const examples = {
|
|
13869
|
+
PR: "https://github.com/owner/repo/pull/123",
|
|
13870
|
+
issue: "https://github.com/owner/repo/issues/123",
|
|
13871
|
+
"issue or PR": "https://github.com/owner/repo/issues/123 or https://github.com/owner/repo/pull/123"
|
|
13872
|
+
};
|
|
13873
|
+
throw new ValidationError(`Invalid ${entityType} URL: ${url}. Expected format: ${examples[entityType]}`);
|
|
13861
13874
|
}
|
|
13862
13875
|
function validateUrl(url) {
|
|
13863
13876
|
if (url.length > MAX_URL_LENGTH) {
|
|
@@ -13895,13 +13908,13 @@ function validateGitHubUsername(username) {
|
|
|
13895
13908
|
}
|
|
13896
13909
|
return trimmed;
|
|
13897
13910
|
}
|
|
13898
|
-
var PR_URL_PATTERN,
|
|
13911
|
+
var PR_URL_PATTERN, ISSUE_OR_PR_URL_PATTERN, MAX_URL_LENGTH, MAX_MESSAGE_LENGTH, MAX_USERNAME_LENGTH, USERNAME_CHARS_PATTERN, CONSECUTIVE_HYPHENS_PATTERN;
|
|
13899
13912
|
var init_validation = __esm({
|
|
13900
13913
|
"src/commands/validation.ts"() {
|
|
13901
13914
|
"use strict";
|
|
13902
13915
|
init_errors();
|
|
13903
13916
|
PR_URL_PATTERN = /^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+$/;
|
|
13904
|
-
|
|
13917
|
+
ISSUE_OR_PR_URL_PATTERN = /^https:\/\/github\.com\/[^/]+\/[^/]+\/(issues|pull)\/\d+$/;
|
|
13905
13918
|
MAX_URL_LENGTH = 2048;
|
|
13906
13919
|
MAX_MESSAGE_LENGTH = 1e3;
|
|
13907
13920
|
MAX_USERNAME_LENGTH = 39;
|
|
@@ -14554,7 +14567,7 @@ var init_dashboard_data = __esm({
|
|
|
14554
14567
|
}
|
|
14555
14568
|
});
|
|
14556
14569
|
|
|
14557
|
-
// src/commands/dashboard-
|
|
14570
|
+
// src/commands/dashboard-formatters.ts
|
|
14558
14571
|
function escapeHtml(text) {
|
|
14559
14572
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
14560
14573
|
}
|
|
@@ -14573,78 +14586,18 @@ function buildDashboardStats(digest, state) {
|
|
|
14573
14586
|
mergeRate: `${(summary.mergeRate ?? 0).toFixed(1)}%`
|
|
14574
14587
|
};
|
|
14575
14588
|
}
|
|
14576
|
-
|
|
14577
|
-
|
|
14578
|
-
|
|
14579
|
-
|
|
14580
|
-
|
|
14581
|
-
|
|
14582
|
-
|
|
14583
|
-
|
|
14584
|
-
|
|
14585
|
-
|
|
14586
|
-
|
|
14587
|
-
|
|
14588
|
-
...digest.incompleteChecklistPRs || [],
|
|
14589
|
-
...digest.missingRequiredFilesPRs || [],
|
|
14590
|
-
...digest.needsRebasePRs || []
|
|
14591
|
-
];
|
|
14592
|
-
const waitingOnOthers = [
|
|
14593
|
-
...digest.changesAddressedPRs || [],
|
|
14594
|
-
...digest.waitingOnMaintainerPRs || [],
|
|
14595
|
-
...digest.ciBlockedPRs || [],
|
|
14596
|
-
...digest.ciNotRunningPRs || []
|
|
14597
|
-
];
|
|
14598
|
-
function truncateTitle(title, max = 50) {
|
|
14599
|
-
const truncated = title.length <= max ? title : title.slice(0, max) + "...";
|
|
14600
|
-
return escapeHtml(truncated);
|
|
14601
|
-
}
|
|
14602
|
-
function renderHealthItems(prs, cssClass, svgPaths, labelFn, metaFn) {
|
|
14603
|
-
return prs.map((pr) => {
|
|
14604
|
-
const rawLabel = typeof labelFn === "string" ? labelFn : labelFn(pr);
|
|
14605
|
-
const label = escapeHtml(rawLabel);
|
|
14606
|
-
return `
|
|
14607
|
-
<div class="health-item ${cssClass}" data-status="${cssClass}" data-repo="${escapeHtml(pr.repo)}" data-title="${escapeHtml(pr.title.toLowerCase())}">
|
|
14608
|
-
<div class="health-icon">
|
|
14609
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
14610
|
-
${svgPaths}
|
|
14611
|
-
</svg>
|
|
14612
|
-
</div>
|
|
14613
|
-
<div class="health-content">
|
|
14614
|
-
<div class="health-title"><a href="${escapeHtml(pr.url)}" target="_blank">${escapeHtml(pr.repo)}#${pr.number}</a> - ${label}</div>
|
|
14615
|
-
<div class="health-meta">${metaFn(pr)}</div>
|
|
14616
|
-
</div>
|
|
14617
|
-
</div>`;
|
|
14618
|
-
}).join("");
|
|
14619
|
-
}
|
|
14620
|
-
const SVG = {
|
|
14621
|
-
comment: '<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>',
|
|
14622
|
-
edit: '<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>',
|
|
14623
|
-
xCircle: '<circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/>',
|
|
14624
|
-
conflict: '<path d="M8 3v3a2 2 0 0 1-2 2H3"/><path d="M21 8h-3a2 2 0 0 1-2-2V3"/><path d="M3 16h3a2 2 0 0 1 2 2v3"/><path d="M16 21v-3a2 2 0 0 1 2-2h3"/>',
|
|
14625
|
-
checklist: '<path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>',
|
|
14626
|
-
file: '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="12" y1="18" x2="12" y2="12"/><line x1="9" y1="15" x2="15" y2="15"/>',
|
|
14627
|
-
checkCircle: '<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>',
|
|
14628
|
-
clock: '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>',
|
|
14629
|
-
lock: '<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>',
|
|
14630
|
-
infoCircle: '<circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/>',
|
|
14631
|
-
refresh: '<polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>',
|
|
14632
|
-
box: '<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/>',
|
|
14633
|
-
bell: '<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/>',
|
|
14634
|
-
gitMerge: '<circle cx="7" cy="18" r="3"/><circle cx="7" cy="6" r="3"/><circle cx="17" cy="12" r="3"/><line x1="7" y1="9" x2="7" y2="15"/><path d="M7 9c0 4 10 3 10 3"/>'
|
|
14635
|
-
};
|
|
14636
|
-
const titleMeta = (pr) => truncateTitle(pr.title);
|
|
14637
|
-
return `<!DOCTYPE html>
|
|
14638
|
-
<html lang="en">
|
|
14639
|
-
<head>
|
|
14640
|
-
<meta charset="UTF-8">
|
|
14641
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
14642
|
-
<title>OSS Autopilot - Mission Control</title>
|
|
14643
|
-
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
14644
|
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
14645
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
14646
|
-
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
14647
|
-
<style>
|
|
14589
|
+
var init_dashboard_formatters = __esm({
|
|
14590
|
+
"src/commands/dashboard-formatters.ts"() {
|
|
14591
|
+
"use strict";
|
|
14592
|
+
}
|
|
14593
|
+
});
|
|
14594
|
+
|
|
14595
|
+
// src/commands/dashboard-styles.ts
|
|
14596
|
+
var DASHBOARD_CSS;
|
|
14597
|
+
var init_dashboard_styles = __esm({
|
|
14598
|
+
"src/commands/dashboard-styles.ts"() {
|
|
14599
|
+
"use strict";
|
|
14600
|
+
DASHBOARD_CSS = `
|
|
14648
14601
|
:root, [data-theme="dark"] {
|
|
14649
14602
|
--bg-base: #080b10;
|
|
14650
14603
|
--bg-surface: rgba(22, 27, 34, 0.65);
|
|
@@ -15404,167 +15357,526 @@ function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpene
|
|
|
15404
15357
|
.health-item[data-hidden="true"] {
|
|
15405
15358
|
display: none;
|
|
15406
15359
|
}
|
|
15407
|
-
|
|
15408
|
-
|
|
15409
|
-
|
|
15410
|
-
<div class="container">
|
|
15411
|
-
<header class="header">
|
|
15412
|
-
<div class="header-left">
|
|
15413
|
-
<div class="logo">
|
|
15414
|
-
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
15415
|
-
<circle cx="12" cy="12" r="10"/>
|
|
15416
|
-
<path d="M12 6v6l4 2"/>
|
|
15417
|
-
</svg>
|
|
15418
|
-
</div>
|
|
15419
|
-
<div>
|
|
15420
|
-
<h1>OSS Autopilot</h1>
|
|
15421
|
-
<span class="header-subtitle">Mission Control</span>
|
|
15422
|
-
</div>
|
|
15423
|
-
</div>
|
|
15424
|
-
<div class="header-controls">
|
|
15425
|
-
<div class="timestamp">
|
|
15426
|
-
Last updated: ${digest.generatedAt ? new Date(digest.generatedAt).toLocaleString("en-US", {
|
|
15427
|
-
weekday: "short",
|
|
15428
|
-
month: "short",
|
|
15429
|
-
day: "numeric",
|
|
15430
|
-
year: "numeric",
|
|
15431
|
-
hour: "2-digit",
|
|
15432
|
-
minute: "2-digit",
|
|
15433
|
-
second: "2-digit",
|
|
15434
|
-
hour12: false
|
|
15435
|
-
}) : "Unknown"}
|
|
15436
|
-
</div>
|
|
15437
|
-
<button class="theme-toggle" id="themeToggle" title="Toggle light/dark mode">
|
|
15438
|
-
<svg id="themeIconSun" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
15439
|
-
<circle cx="12" cy="12" r="5"/>
|
|
15440
|
-
<line x1="12" y1="1" x2="12" y2="3"/>
|
|
15441
|
-
<line x1="12" y1="21" x2="12" y2="23"/>
|
|
15442
|
-
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
|
|
15443
|
-
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
|
|
15444
|
-
<line x1="1" y1="12" x2="3" y2="12"/>
|
|
15445
|
-
<line x1="21" y1="12" x2="23" y2="12"/>
|
|
15446
|
-
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
|
|
15447
|
-
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
|
|
15448
|
-
</svg>
|
|
15449
|
-
<svg id="themeIconMoon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none;">
|
|
15450
|
-
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
|
|
15451
|
-
</svg>
|
|
15452
|
-
<span id="themeLabel">Light</span>
|
|
15453
|
-
</button>
|
|
15454
|
-
</div>
|
|
15455
|
-
</header>
|
|
15456
|
-
|
|
15457
|
-
<div class="stats-grid">
|
|
15458
|
-
<div class="stat-card active">
|
|
15459
|
-
<div class="stat-value">${stats.activePRs}</div>
|
|
15460
|
-
<div class="stat-label">Active PRs</div>
|
|
15461
|
-
</div>
|
|
15462
|
-
<div class="stat-card shelved">
|
|
15463
|
-
<div class="stat-value">${stats.shelvedPRs}</div>
|
|
15464
|
-
<div class="stat-label">Shelved</div>
|
|
15465
|
-
</div>
|
|
15466
|
-
<div class="stat-card merged">
|
|
15467
|
-
<div class="stat-value">${stats.mergedPRs}</div>
|
|
15468
|
-
<div class="stat-label">Merged</div>
|
|
15469
|
-
</div>
|
|
15470
|
-
<div class="stat-card closed">
|
|
15471
|
-
<div class="stat-value">${stats.closedPRs}</div>
|
|
15472
|
-
<div class="stat-label">Closed</div>
|
|
15473
|
-
</div>
|
|
15474
|
-
<div class="stat-card rate">
|
|
15475
|
-
<div class="stat-value">${stats.mergeRate}</div>
|
|
15476
|
-
<div class="stat-label">Merge Rate</div>
|
|
15477
|
-
</div>
|
|
15478
|
-
</div>
|
|
15360
|
+
`;
|
|
15361
|
+
}
|
|
15362
|
+
});
|
|
15479
15363
|
|
|
15480
|
-
|
|
15481
|
-
|
|
15482
|
-
|
|
15483
|
-
|
|
15484
|
-
|
|
15485
|
-
|
|
15486
|
-
|
|
15487
|
-
|
|
15488
|
-
|
|
15489
|
-
|
|
15490
|
-
<
|
|
15491
|
-
|
|
15492
|
-
|
|
15493
|
-
|
|
15494
|
-
|
|
15495
|
-
|
|
15496
|
-
|
|
15497
|
-
|
|
15498
|
-
|
|
15499
|
-
|
|
15500
|
-
|
|
15501
|
-
|
|
15502
|
-
|
|
15503
|
-
|
|
15504
|
-
|
|
15505
|
-
|
|
15506
|
-
|
|
15507
|
-
|
|
15508
|
-
|
|
15509
|
-
|
|
15510
|
-
|
|
15511
|
-
|
|
15512
|
-
|
|
15513
|
-
|
|
15514
|
-
|
|
15515
|
-
|
|
15516
|
-
<
|
|
15517
|
-
|
|
15364
|
+
// src/commands/dashboard-components.ts
|
|
15365
|
+
function truncateTitle(title, max = 50) {
|
|
15366
|
+
const truncated = title.length <= max ? title : title.slice(0, max) + "...";
|
|
15367
|
+
return escapeHtml(truncated);
|
|
15368
|
+
}
|
|
15369
|
+
function renderHealthItems(prs, cssClass, svgPaths, labelFn, metaFn) {
|
|
15370
|
+
return prs.map((pr) => {
|
|
15371
|
+
const rawLabel = typeof labelFn === "string" ? labelFn : labelFn(pr);
|
|
15372
|
+
const label = escapeHtml(rawLabel);
|
|
15373
|
+
return `
|
|
15374
|
+
<div class="health-item ${cssClass}" data-status="${cssClass}" data-repo="${escapeHtml(pr.repo)}" data-title="${escapeHtml(pr.title.toLowerCase())}">
|
|
15375
|
+
<div class="health-icon">
|
|
15376
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
15377
|
+
${svgPaths}
|
|
15378
|
+
</svg>
|
|
15379
|
+
</div>
|
|
15380
|
+
<div class="health-content">
|
|
15381
|
+
<div class="health-title"><a href="${escapeHtml(pr.url)}" target="_blank">${escapeHtml(pr.repo)}#${pr.number}</a> - ${label}</div>
|
|
15382
|
+
<div class="health-meta">${metaFn(pr)}</div>
|
|
15383
|
+
</div>
|
|
15384
|
+
</div>`;
|
|
15385
|
+
}).join("");
|
|
15386
|
+
}
|
|
15387
|
+
function titleMeta(pr) {
|
|
15388
|
+
return truncateTitle(pr.title);
|
|
15389
|
+
}
|
|
15390
|
+
var SVG_ICONS;
|
|
15391
|
+
var init_dashboard_components = __esm({
|
|
15392
|
+
"src/commands/dashboard-components.ts"() {
|
|
15393
|
+
"use strict";
|
|
15394
|
+
init_dashboard_formatters();
|
|
15395
|
+
SVG_ICONS = {
|
|
15396
|
+
comment: '<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>',
|
|
15397
|
+
edit: '<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>',
|
|
15398
|
+
xCircle: '<circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/>',
|
|
15399
|
+
conflict: '<path d="M8 3v3a2 2 0 0 1-2 2H3"/><path d="M21 8h-3a2 2 0 0 1-2-2V3"/><path d="M3 16h3a2 2 0 0 1 2 2v3"/><path d="M16 21v-3a2 2 0 0 1 2-2h3"/>',
|
|
15400
|
+
checklist: '<path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>',
|
|
15401
|
+
file: '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="12" y1="18" x2="12" y2="12"/><line x1="9" y1="15" x2="15" y2="15"/>',
|
|
15402
|
+
checkCircle: '<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>',
|
|
15403
|
+
clock: '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>',
|
|
15404
|
+
lock: '<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>',
|
|
15405
|
+
infoCircle: '<circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/>',
|
|
15406
|
+
refresh: '<polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>',
|
|
15407
|
+
box: '<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/>',
|
|
15408
|
+
bell: '<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/>',
|
|
15409
|
+
gitMerge: '<circle cx="7" cy="18" r="3"/><circle cx="7" cy="6" r="3"/><circle cx="17" cy="12" r="3"/><line x1="7" y1="9" x2="7" y2="15"/><path d="M7 9c0 4 10 3 10 3"/>'
|
|
15410
|
+
};
|
|
15411
|
+
}
|
|
15412
|
+
});
|
|
15518
15413
|
|
|
15519
|
-
|
|
15520
|
-
|
|
15521
|
-
|
|
15522
|
-
|
|
15523
|
-
|
|
15524
|
-
|
|
15525
|
-
|
|
15526
|
-
</svg>
|
|
15527
|
-
<h2>Action Required</h2>
|
|
15528
|
-
<span class="health-badge">${actionRequired.length} issue${actionRequired.length !== 1 ? "s" : ""}</span>
|
|
15529
|
-
</div>
|
|
15530
|
-
<div class="health-items">
|
|
15531
|
-
${renderHealthItems(
|
|
15532
|
-
digest.prsNeedingResponse || [],
|
|
15533
|
-
"needs-response",
|
|
15534
|
-
SVG.comment,
|
|
15535
|
-
"Needs Response",
|
|
15536
|
-
(pr) => pr.lastMaintainerComment ? `@${escapeHtml(pr.lastMaintainerComment.author)}: ${truncateTitle(pr.lastMaintainerComment.body, 40)}` : truncateTitle(pr.title)
|
|
15537
|
-
)}
|
|
15538
|
-
${renderHealthItems(digest.needsChangesPRs || [], "needs-changes", SVG.edit, "Needs Changes", titleMeta)}
|
|
15539
|
-
${renderHealthItems(digest.ciFailingPRs || [], "ci-failing", SVG.xCircle, "CI Failing", titleMeta)}
|
|
15540
|
-
${renderHealthItems(digest.mergeConflictPRs || [], "conflict", SVG.conflict, "Merge Conflict", titleMeta)}
|
|
15541
|
-
${renderHealthItems(
|
|
15542
|
-
digest.incompleteChecklistPRs || [],
|
|
15543
|
-
"incomplete-checklist",
|
|
15544
|
-
SVG.checklist,
|
|
15545
|
-
(pr) => `Incomplete Checklist${pr.checklistStats ? ` (${pr.checklistStats.checked}/${pr.checklistStats.total})` : ""}`,
|
|
15546
|
-
titleMeta
|
|
15547
|
-
)}
|
|
15548
|
-
${renderHealthItems(
|
|
15549
|
-
digest.missingRequiredFilesPRs || [],
|
|
15550
|
-
"missing-files",
|
|
15551
|
-
SVG.file,
|
|
15552
|
-
"Missing Required Files",
|
|
15553
|
-
(pr) => pr.missingRequiredFiles ? escapeHtml(pr.missingRequiredFiles.join(", ")) : truncateTitle(pr.title)
|
|
15554
|
-
)}
|
|
15555
|
-
${renderHealthItems(
|
|
15556
|
-
digest.needsRebasePRs || [],
|
|
15557
|
-
"needs-rebase",
|
|
15558
|
-
SVG.refresh,
|
|
15559
|
-
(pr) => `Needs Rebase${pr.commitsBehindUpstream ? ` (${pr.commitsBehindUpstream} behind)` : ""}`,
|
|
15560
|
-
titleMeta
|
|
15561
|
-
)}
|
|
15562
|
-
</div>
|
|
15563
|
-
</section>
|
|
15564
|
-
` : ""}
|
|
15414
|
+
// src/commands/dashboard-scripts.ts
|
|
15415
|
+
function generateDashboardScripts(stats, monthlyMerged, monthlyClosed, monthlyOpened, digest, state) {
|
|
15416
|
+
const statusChart = `
|
|
15417
|
+
Chart.defaults.color = '#6e7681';
|
|
15418
|
+
Chart.defaults.borderColor = 'rgba(48, 54, 61, 0.4)';
|
|
15419
|
+
Chart.defaults.font.family = "'Geist', sans-serif";
|
|
15420
|
+
Chart.defaults.font.size = 11;
|
|
15565
15421
|
|
|
15566
|
-
|
|
15567
|
-
|
|
15422
|
+
// === Status Doughnut ===
|
|
15423
|
+
new Chart(document.getElementById('statusChart'), {
|
|
15424
|
+
type: 'doughnut',
|
|
15425
|
+
data: {
|
|
15426
|
+
labels: ['Active', 'Shelved', 'Merged', 'Closed'],
|
|
15427
|
+
datasets: [{
|
|
15428
|
+
data: [${stats.activePRs}, ${stats.shelvedPRs}, ${stats.mergedPRs}, ${stats.closedPRs}],
|
|
15429
|
+
backgroundColor: ['#3fb950', '#6e7681', '#a855f7', '#484f58'],
|
|
15430
|
+
borderColor: 'rgba(8, 11, 16, 0.8)',
|
|
15431
|
+
borderWidth: 2,
|
|
15432
|
+
hoverOffset: 8
|
|
15433
|
+
}]
|
|
15434
|
+
},
|
|
15435
|
+
options: {
|
|
15436
|
+
responsive: true,
|
|
15437
|
+
maintainAspectRatio: false,
|
|
15438
|
+
cutout: '65%',
|
|
15439
|
+
plugins: {
|
|
15440
|
+
legend: {
|
|
15441
|
+
position: 'bottom',
|
|
15442
|
+
labels: { padding: 16, usePointStyle: true, pointStyle: 'circle', font: { size: 11 } }
|
|
15443
|
+
}
|
|
15444
|
+
}
|
|
15445
|
+
}
|
|
15446
|
+
});`;
|
|
15447
|
+
const repoChart = (() => {
|
|
15448
|
+
const { excludeRepos: exRepos = [], excludeOrgs: exOrgs, minStars } = state.config;
|
|
15449
|
+
const starThreshold = minStars ?? 50;
|
|
15450
|
+
const shouldExcludeRepo = (repo) => {
|
|
15451
|
+
const repoLower = repo.toLowerCase();
|
|
15452
|
+
if (exRepos.some((r) => r.toLowerCase() === repoLower)) return true;
|
|
15453
|
+
if (exOrgs?.some((o) => o.toLowerCase() === repoLower.split("/")[0])) return true;
|
|
15454
|
+
const score = (state.repoScores || {})[repo];
|
|
15455
|
+
if (score?.stargazersCount !== void 0 && score.stargazersCount < starThreshold) return true;
|
|
15456
|
+
return false;
|
|
15457
|
+
};
|
|
15458
|
+
const allRepoEntries = Object.entries(
|
|
15459
|
+
// Rebuild from full prsByRepo to get all repos, not just top 10
|
|
15460
|
+
(() => {
|
|
15461
|
+
const all = {};
|
|
15462
|
+
for (const pr of digest.openPRs || []) {
|
|
15463
|
+
if (shouldExcludeRepo(pr.repo)) continue;
|
|
15464
|
+
if (!all[pr.repo]) all[pr.repo] = { active: 0, merged: 0, closed: 0 };
|
|
15465
|
+
all[pr.repo].active++;
|
|
15466
|
+
}
|
|
15467
|
+
for (const [repo, score] of Object.entries(state.repoScores || {})) {
|
|
15468
|
+
if (shouldExcludeRepo(repo)) continue;
|
|
15469
|
+
if (!all[repo]) all[repo] = { active: 0, merged: 0, closed: 0 };
|
|
15470
|
+
all[repo].merged = score.mergedPRCount;
|
|
15471
|
+
all[repo].closed = score.closedWithoutMergeCount;
|
|
15472
|
+
}
|
|
15473
|
+
return all;
|
|
15474
|
+
})()
|
|
15475
|
+
).sort((a, b) => {
|
|
15476
|
+
const totalA = a[1].merged + a[1].active + a[1].closed;
|
|
15477
|
+
const totalB = b[1].merged + b[1].active + b[1].closed;
|
|
15478
|
+
return totalB - totalA;
|
|
15479
|
+
});
|
|
15480
|
+
const displayRepos = allRepoEntries.slice(0, 10);
|
|
15481
|
+
const otherRepos = allRepoEntries.slice(10);
|
|
15482
|
+
const grandTotal = allRepoEntries.reduce((sum, [, d]) => sum + d.merged + d.active + d.closed, 0);
|
|
15483
|
+
if (otherRepos.length > 0) {
|
|
15484
|
+
const otherData = otherRepos.reduce(
|
|
15485
|
+
(acc, [, d]) => ({
|
|
15486
|
+
active: acc.active + d.active,
|
|
15487
|
+
merged: acc.merged + d.merged,
|
|
15488
|
+
closed: acc.closed + d.closed
|
|
15489
|
+
}),
|
|
15490
|
+
{ active: 0, merged: 0, closed: 0 }
|
|
15491
|
+
);
|
|
15492
|
+
displayRepos.push(["Other", otherData]);
|
|
15493
|
+
}
|
|
15494
|
+
const repoLabels = displayRepos.map(([repo]) => repo === "Other" ? "Other" : repo.split("/")[1] || repo);
|
|
15495
|
+
const mergedData = displayRepos.map(([, d]) => d.merged);
|
|
15496
|
+
const activeData = displayRepos.map(([, d]) => d.active);
|
|
15497
|
+
const closedData = displayRepos.map(([, d]) => d.closed);
|
|
15498
|
+
return `
|
|
15499
|
+
new Chart(document.getElementById('reposChart'), {
|
|
15500
|
+
type: 'bar',
|
|
15501
|
+
data: {
|
|
15502
|
+
labels: ${JSON.stringify(repoLabels)},
|
|
15503
|
+
datasets: [
|
|
15504
|
+
{ label: 'Merged', data: ${JSON.stringify(mergedData)}, backgroundColor: '#a855f7', borderRadius: 3 },
|
|
15505
|
+
{ label: 'Active', data: ${JSON.stringify(activeData)}, backgroundColor: '#3fb950', borderRadius: 3 },
|
|
15506
|
+
{ label: 'Closed', data: ${JSON.stringify(closedData)}, backgroundColor: '#484f58', borderRadius: 3 }
|
|
15507
|
+
]
|
|
15508
|
+
},
|
|
15509
|
+
options: {
|
|
15510
|
+
responsive: true,
|
|
15511
|
+
maintainAspectRatio: false,
|
|
15512
|
+
scales: {
|
|
15513
|
+
x: { stacked: true, grid: { display: false }, ticks: { font: { size: 10 } } },
|
|
15514
|
+
y: { stacked: true, grid: { color: 'rgba(48, 54, 61, 0.3)' }, ticks: { stepSize: 1 } }
|
|
15515
|
+
},
|
|
15516
|
+
plugins: {
|
|
15517
|
+
legend: { position: 'bottom', labels: { padding: 16, usePointStyle: true, pointStyle: 'circle', font: { size: 11 } } },
|
|
15518
|
+
tooltip: {
|
|
15519
|
+
callbacks: {
|
|
15520
|
+
afterBody: function(context) {
|
|
15521
|
+
const idx = context[0].dataIndex;
|
|
15522
|
+
const total = ${JSON.stringify(mergedData)}[idx] + ${JSON.stringify(activeData)}[idx] + ${JSON.stringify(closedData)}[idx];
|
|
15523
|
+
const pct = ${grandTotal} > 0 ? ((total / ${grandTotal}) * 100).toFixed(1) : '0.0';
|
|
15524
|
+
return pct + '% of all PRs';
|
|
15525
|
+
}
|
|
15526
|
+
}
|
|
15527
|
+
}
|
|
15528
|
+
}
|
|
15529
|
+
}
|
|
15530
|
+
});`;
|
|
15531
|
+
})();
|
|
15532
|
+
const timelineChart = (() => {
|
|
15533
|
+
const now = /* @__PURE__ */ new Date();
|
|
15534
|
+
const allMonths = [];
|
|
15535
|
+
for (let offset = 5; offset >= 0; offset--) {
|
|
15536
|
+
const d = new Date(now.getFullYear(), now.getMonth() - offset, 1);
|
|
15537
|
+
allMonths.push(`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`);
|
|
15538
|
+
}
|
|
15539
|
+
return `
|
|
15540
|
+
const timelineMonths = ${JSON.stringify(allMonths)};
|
|
15541
|
+
const openedData = ${JSON.stringify(monthlyOpened)};
|
|
15542
|
+
const mergedData = ${JSON.stringify(monthlyMerged)};
|
|
15543
|
+
const closedData = ${JSON.stringify(monthlyClosed)};
|
|
15544
|
+
new Chart(document.getElementById('monthlyChart'), {
|
|
15545
|
+
type: 'bar',
|
|
15546
|
+
data: {
|
|
15547
|
+
labels: timelineMonths,
|
|
15548
|
+
datasets: [
|
|
15549
|
+
{
|
|
15550
|
+
label: 'Opened',
|
|
15551
|
+
data: timelineMonths.map(m => openedData[m] || 0),
|
|
15552
|
+
backgroundColor: '#58a6ff',
|
|
15553
|
+
borderRadius: 3
|
|
15554
|
+
},
|
|
15555
|
+
{
|
|
15556
|
+
label: 'Merged',
|
|
15557
|
+
data: timelineMonths.map(m => mergedData[m] || 0),
|
|
15558
|
+
backgroundColor: '#a855f7',
|
|
15559
|
+
borderRadius: 3
|
|
15560
|
+
},
|
|
15561
|
+
{
|
|
15562
|
+
label: 'Closed',
|
|
15563
|
+
data: timelineMonths.map(m => closedData[m] || 0),
|
|
15564
|
+
backgroundColor: '#484f58',
|
|
15565
|
+
borderRadius: 3
|
|
15566
|
+
}
|
|
15567
|
+
]
|
|
15568
|
+
},
|
|
15569
|
+
options: {
|
|
15570
|
+
responsive: true,
|
|
15571
|
+
maintainAspectRatio: false,
|
|
15572
|
+
scales: {
|
|
15573
|
+
x: { grid: { display: false } },
|
|
15574
|
+
y: { grid: { color: 'rgba(48, 54, 61, 0.3)' }, beginAtZero: true, ticks: { stepSize: 1 } }
|
|
15575
|
+
},
|
|
15576
|
+
plugins: {
|
|
15577
|
+
legend: { position: 'bottom', labels: { padding: 16, usePointStyle: true, pointStyle: 'circle', font: { size: 11 } } }
|
|
15578
|
+
},
|
|
15579
|
+
interaction: { intersect: false, mode: 'index' }
|
|
15580
|
+
}
|
|
15581
|
+
});`;
|
|
15582
|
+
})();
|
|
15583
|
+
return THEME_AND_FILTER_SCRIPT + statusChart + "\n" + repoChart + "\n" + timelineChart;
|
|
15584
|
+
}
|
|
15585
|
+
var THEME_AND_FILTER_SCRIPT;
|
|
15586
|
+
var init_dashboard_scripts = __esm({
|
|
15587
|
+
"src/commands/dashboard-scripts.ts"() {
|
|
15588
|
+
"use strict";
|
|
15589
|
+
THEME_AND_FILTER_SCRIPT = `
|
|
15590
|
+
// === Theme Toggle ===
|
|
15591
|
+
(function() {
|
|
15592
|
+
var html = document.documentElement;
|
|
15593
|
+
var toggle = document.getElementById('themeToggle');
|
|
15594
|
+
var sunIcon = document.getElementById('themeIconSun');
|
|
15595
|
+
var moonIcon = document.getElementById('themeIconMoon');
|
|
15596
|
+
var label = document.getElementById('themeLabel');
|
|
15597
|
+
|
|
15598
|
+
function getEffectiveTheme() {
|
|
15599
|
+
try {
|
|
15600
|
+
var stored = localStorage.getItem('oss-dashboard-theme');
|
|
15601
|
+
if (stored === 'light' || stored === 'dark') return stored;
|
|
15602
|
+
} catch (e) { /* localStorage unavailable (private browsing) */ }
|
|
15603
|
+
return window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
|
|
15604
|
+
}
|
|
15605
|
+
|
|
15606
|
+
function applyTheme(theme) {
|
|
15607
|
+
html.setAttribute('data-theme', theme);
|
|
15608
|
+
if (theme === 'light') {
|
|
15609
|
+
sunIcon.style.display = 'none';
|
|
15610
|
+
moonIcon.style.display = 'block';
|
|
15611
|
+
label.textContent = 'Dark';
|
|
15612
|
+
} else {
|
|
15613
|
+
sunIcon.style.display = 'block';
|
|
15614
|
+
moonIcon.style.display = 'none';
|
|
15615
|
+
label.textContent = 'Light';
|
|
15616
|
+
}
|
|
15617
|
+
}
|
|
15618
|
+
|
|
15619
|
+
applyTheme(getEffectiveTheme());
|
|
15620
|
+
|
|
15621
|
+
toggle.addEventListener('click', function() {
|
|
15622
|
+
var current = html.getAttribute('data-theme');
|
|
15623
|
+
var next = current === 'dark' ? 'light' : 'dark';
|
|
15624
|
+
try { localStorage.setItem('oss-dashboard-theme', next); } catch (e) { /* private browsing */ }
|
|
15625
|
+
applyTheme(next);
|
|
15626
|
+
});
|
|
15627
|
+
})();
|
|
15628
|
+
|
|
15629
|
+
// === Filtering & Search ===
|
|
15630
|
+
(function() {
|
|
15631
|
+
var searchInput = document.getElementById('searchInput');
|
|
15632
|
+
var statusFilter = document.getElementById('statusFilter');
|
|
15633
|
+
var repoFilter = document.getElementById('repoFilter');
|
|
15634
|
+
var filterCount = document.getElementById('filterCount');
|
|
15635
|
+
|
|
15636
|
+
function applyFilters() {
|
|
15637
|
+
var query = searchInput.value.toLowerCase().trim();
|
|
15638
|
+
var status = statusFilter.value;
|
|
15639
|
+
var repo = repoFilter.value;
|
|
15640
|
+
var allItems = document.querySelectorAll('.health-item[data-status], .pr-item[data-status]');
|
|
15641
|
+
var visible = 0;
|
|
15642
|
+
var total = allItems.length;
|
|
15643
|
+
|
|
15644
|
+
allItems.forEach(function(item) {
|
|
15645
|
+
var itemStatus = item.getAttribute('data-status') || '';
|
|
15646
|
+
var itemRepo = item.getAttribute('data-repo') || '';
|
|
15647
|
+
var itemTitle = item.getAttribute('data-title') || '';
|
|
15648
|
+
|
|
15649
|
+
var matchesStatus = (status === 'all') || (itemStatus === status);
|
|
15650
|
+
var matchesRepo = (repo === 'all') || (itemRepo === repo);
|
|
15651
|
+
var matchesSearch = !query || itemTitle.indexOf(query) !== -1;
|
|
15652
|
+
|
|
15653
|
+
if (matchesStatus && matchesRepo && matchesSearch) {
|
|
15654
|
+
item.setAttribute('data-hidden', 'false');
|
|
15655
|
+
visible++;
|
|
15656
|
+
} else {
|
|
15657
|
+
item.setAttribute('data-hidden', 'true');
|
|
15658
|
+
}
|
|
15659
|
+
});
|
|
15660
|
+
|
|
15661
|
+
// Show/hide parent sections if all children are hidden
|
|
15662
|
+
var sections = document.querySelectorAll('.health-section, .pr-list-section');
|
|
15663
|
+
sections.forEach(function(section) {
|
|
15664
|
+
var items = section.querySelectorAll('.health-item[data-status], .pr-item[data-status]');
|
|
15665
|
+
if (items.length === 0) return; // sections without filterable items (e.g. empty state)
|
|
15666
|
+
var anyVisible = false;
|
|
15667
|
+
items.forEach(function(item) {
|
|
15668
|
+
if (item.getAttribute('data-hidden') !== 'true') anyVisible = true;
|
|
15669
|
+
});
|
|
15670
|
+
section.style.display = anyVisible ? '' : 'none';
|
|
15671
|
+
});
|
|
15672
|
+
|
|
15673
|
+
var isFiltering = (status !== 'all' || repo !== 'all' || query.length > 0);
|
|
15674
|
+
filterCount.textContent = isFiltering ? (visible + ' of ' + total + ' items') : '';
|
|
15675
|
+
}
|
|
15676
|
+
|
|
15677
|
+
searchInput.addEventListener('input', applyFilters);
|
|
15678
|
+
statusFilter.addEventListener('change', applyFilters);
|
|
15679
|
+
repoFilter.addEventListener('change', applyFilters);
|
|
15680
|
+
})();
|
|
15681
|
+
`;
|
|
15682
|
+
}
|
|
15683
|
+
});
|
|
15684
|
+
|
|
15685
|
+
// src/commands/dashboard-templates.ts
|
|
15686
|
+
function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpened, digest, state, issueResponses = []) {
|
|
15687
|
+
const approachingDormantDays = state.config?.approachingDormantDays ?? 25;
|
|
15688
|
+
const shelvedPRs = digest.shelvedPRs || [];
|
|
15689
|
+
const autoUnshelvedPRs = digest.autoUnshelvedPRs || [];
|
|
15690
|
+
const recentlyMerged = digest.recentlyMergedPRs || [];
|
|
15691
|
+
const shelvedUrls = new Set(shelvedPRs.map((pr) => pr.url));
|
|
15692
|
+
const activePRList = (digest.openPRs || []).filter((pr) => !shelvedUrls.has(pr.url));
|
|
15693
|
+
const actionRequired = [
|
|
15694
|
+
...digest.prsNeedingResponse || [],
|
|
15695
|
+
...digest.needsChangesPRs || [],
|
|
15696
|
+
...digest.ciFailingPRs || [],
|
|
15697
|
+
...digest.mergeConflictPRs || [],
|
|
15698
|
+
...digest.incompleteChecklistPRs || [],
|
|
15699
|
+
...digest.missingRequiredFilesPRs || [],
|
|
15700
|
+
...digest.needsRebasePRs || []
|
|
15701
|
+
];
|
|
15702
|
+
const waitingOnOthers = [
|
|
15703
|
+
...digest.changesAddressedPRs || [],
|
|
15704
|
+
...digest.waitingOnMaintainerPRs || [],
|
|
15705
|
+
...digest.ciBlockedPRs || [],
|
|
15706
|
+
...digest.ciNotRunningPRs || []
|
|
15707
|
+
];
|
|
15708
|
+
return `<!DOCTYPE html>
|
|
15709
|
+
<html lang="en">
|
|
15710
|
+
<head>
|
|
15711
|
+
<meta charset="UTF-8">
|
|
15712
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
15713
|
+
<title>OSS Autopilot - Mission Control</title>
|
|
15714
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
15715
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
15716
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
15717
|
+
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
15718
|
+
<style>${DASHBOARD_CSS}
|
|
15719
|
+
</style>
|
|
15720
|
+
</head>
|
|
15721
|
+
<body>
|
|
15722
|
+
<div class="container">
|
|
15723
|
+
<header class="header">
|
|
15724
|
+
<div class="header-left">
|
|
15725
|
+
<div class="logo">
|
|
15726
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
15727
|
+
<circle cx="12" cy="12" r="10"/>
|
|
15728
|
+
<path d="M12 6v6l4 2"/>
|
|
15729
|
+
</svg>
|
|
15730
|
+
</div>
|
|
15731
|
+
<div>
|
|
15732
|
+
<h1>OSS Autopilot</h1>
|
|
15733
|
+
<span class="header-subtitle">Mission Control</span>
|
|
15734
|
+
</div>
|
|
15735
|
+
</div>
|
|
15736
|
+
<div class="header-controls">
|
|
15737
|
+
<div class="timestamp">
|
|
15738
|
+
Last updated: ${digest.generatedAt ? new Date(digest.generatedAt).toLocaleString("en-US", {
|
|
15739
|
+
weekday: "short",
|
|
15740
|
+
month: "short",
|
|
15741
|
+
day: "numeric",
|
|
15742
|
+
year: "numeric",
|
|
15743
|
+
hour: "2-digit",
|
|
15744
|
+
minute: "2-digit",
|
|
15745
|
+
second: "2-digit",
|
|
15746
|
+
hour12: false
|
|
15747
|
+
}) : "Unknown"}
|
|
15748
|
+
</div>
|
|
15749
|
+
<button class="theme-toggle" id="themeToggle" title="Toggle light/dark mode">
|
|
15750
|
+
<svg id="themeIconSun" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
15751
|
+
<circle cx="12" cy="12" r="5"/>
|
|
15752
|
+
<line x1="12" y1="1" x2="12" y2="3"/>
|
|
15753
|
+
<line x1="12" y1="21" x2="12" y2="23"/>
|
|
15754
|
+
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
|
|
15755
|
+
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
|
|
15756
|
+
<line x1="1" y1="12" x2="3" y2="12"/>
|
|
15757
|
+
<line x1="21" y1="12" x2="23" y2="12"/>
|
|
15758
|
+
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
|
|
15759
|
+
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
|
|
15760
|
+
</svg>
|
|
15761
|
+
<svg id="themeIconMoon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none;">
|
|
15762
|
+
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
|
|
15763
|
+
</svg>
|
|
15764
|
+
<span id="themeLabel">Light</span>
|
|
15765
|
+
</button>
|
|
15766
|
+
</div>
|
|
15767
|
+
</header>
|
|
15768
|
+
|
|
15769
|
+
<div class="stats-grid">
|
|
15770
|
+
<div class="stat-card active">
|
|
15771
|
+
<div class="stat-value">${stats.activePRs}</div>
|
|
15772
|
+
<div class="stat-label">Active PRs</div>
|
|
15773
|
+
</div>
|
|
15774
|
+
<div class="stat-card shelved">
|
|
15775
|
+
<div class="stat-value">${stats.shelvedPRs}</div>
|
|
15776
|
+
<div class="stat-label">Shelved</div>
|
|
15777
|
+
</div>
|
|
15778
|
+
<div class="stat-card merged">
|
|
15779
|
+
<div class="stat-value">${stats.mergedPRs}</div>
|
|
15780
|
+
<div class="stat-label">Merged</div>
|
|
15781
|
+
</div>
|
|
15782
|
+
<div class="stat-card closed">
|
|
15783
|
+
<div class="stat-value">${stats.closedPRs}</div>
|
|
15784
|
+
<div class="stat-label">Closed</div>
|
|
15785
|
+
</div>
|
|
15786
|
+
<div class="stat-card rate">
|
|
15787
|
+
<div class="stat-value">${stats.mergeRate}</div>
|
|
15788
|
+
<div class="stat-label">Merge Rate</div>
|
|
15789
|
+
</div>
|
|
15790
|
+
</div>
|
|
15791
|
+
|
|
15792
|
+
<div class="filter-toolbar" id="filterToolbar">
|
|
15793
|
+
<label>Filters</label>
|
|
15794
|
+
<input type="text" class="filter-search" id="searchInput" placeholder="Search by PR title..." />
|
|
15795
|
+
<select class="filter-select" id="statusFilter">
|
|
15796
|
+
<option value="all">All Statuses</option>
|
|
15797
|
+
<option value="needs-response">Needs Response</option>
|
|
15798
|
+
<option value="needs-changes">Needs Changes</option>
|
|
15799
|
+
<option value="ci-failing">CI Failing</option>
|
|
15800
|
+
<option value="conflict">Merge Conflict</option>
|
|
15801
|
+
<option value="changes-addressed">Changes Addressed</option>
|
|
15802
|
+
<option value="waiting-maintainer">Waiting on Maintainer</option>
|
|
15803
|
+
<option value="ci-blocked">CI Blocked</option>
|
|
15804
|
+
<option value="ci-not-running">CI Not Running</option>
|
|
15805
|
+
<option value="incomplete-checklist">Incomplete Checklist</option>
|
|
15806
|
+
<option value="missing-files">Missing Files</option>
|
|
15807
|
+
<option value="needs-rebase">Needs Rebase</option>
|
|
15808
|
+
<option value="shelved">Shelved</option>
|
|
15809
|
+
<option value="merged">Recently Merged</option>
|
|
15810
|
+
<option value="closed">Recently Closed</option>
|
|
15811
|
+
<option value="auto-unshelved">Auto-Unshelved</option>
|
|
15812
|
+
<option value="active">Active (No Issues)</option>
|
|
15813
|
+
</select>
|
|
15814
|
+
<select class="filter-select" id="repoFilter">
|
|
15815
|
+
<option value="all">All Repositories</option>
|
|
15816
|
+
${(() => {
|
|
15817
|
+
const repos = /* @__PURE__ */ new Set();
|
|
15818
|
+
for (const pr of activePRList) repos.add(pr.repo);
|
|
15819
|
+
for (const pr of shelvedPRs) repos.add(pr.repo);
|
|
15820
|
+
for (const pr of actionRequired) repos.add(pr.repo);
|
|
15821
|
+
for (const pr of waitingOnOthers) repos.add(pr.repo);
|
|
15822
|
+
for (const pr of recentlyMerged) repos.add(pr.repo);
|
|
15823
|
+
for (const pr of digest.recentlyClosedPRs || []) repos.add(pr.repo);
|
|
15824
|
+
for (const pr of autoUnshelvedPRs) repos.add(pr.repo);
|
|
15825
|
+
return Array.from(repos).sort().map((repo) => `<option value="${escapeHtml(repo)}">${escapeHtml(repo)}</option>`).join("\n ");
|
|
15826
|
+
})()}
|
|
15827
|
+
</select>
|
|
15828
|
+
<span class="filter-count" id="filterCount"></span>
|
|
15829
|
+
</div>
|
|
15830
|
+
|
|
15831
|
+
${actionRequired.length > 0 ? `
|
|
15832
|
+
<section class="health-section">
|
|
15833
|
+
<div class="health-header">
|
|
15834
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--accent-warning)" stroke-width="2">
|
|
15835
|
+
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/>
|
|
15836
|
+
<line x1="12" y1="9" x2="12" y2="13"/>
|
|
15837
|
+
<line x1="12" y1="17" x2="12.01" y2="17"/>
|
|
15838
|
+
</svg>
|
|
15839
|
+
<h2>Action Required</h2>
|
|
15840
|
+
<span class="health-badge">${actionRequired.length} issue${actionRequired.length !== 1 ? "s" : ""}</span>
|
|
15841
|
+
</div>
|
|
15842
|
+
<div class="health-items">
|
|
15843
|
+
${renderHealthItems(
|
|
15844
|
+
digest.prsNeedingResponse || [],
|
|
15845
|
+
"needs-response",
|
|
15846
|
+
SVG_ICONS.comment,
|
|
15847
|
+
"Needs Response",
|
|
15848
|
+
(pr) => pr.lastMaintainerComment ? `@${escapeHtml(pr.lastMaintainerComment.author)}: ${truncateTitle(pr.lastMaintainerComment.body, 40)}` : truncateTitle(pr.title)
|
|
15849
|
+
)}
|
|
15850
|
+
${renderHealthItems(digest.needsChangesPRs || [], "needs-changes", SVG_ICONS.edit, "Needs Changes", titleMeta)}
|
|
15851
|
+
${renderHealthItems(digest.ciFailingPRs || [], "ci-failing", SVG_ICONS.xCircle, "CI Failing", titleMeta)}
|
|
15852
|
+
${renderHealthItems(digest.mergeConflictPRs || [], "conflict", SVG_ICONS.conflict, "Merge Conflict", titleMeta)}
|
|
15853
|
+
${renderHealthItems(
|
|
15854
|
+
digest.incompleteChecklistPRs || [],
|
|
15855
|
+
"incomplete-checklist",
|
|
15856
|
+
SVG_ICONS.checklist,
|
|
15857
|
+
(pr) => `Incomplete Checklist${pr.checklistStats ? ` (${pr.checklistStats.checked}/${pr.checklistStats.total})` : ""}`,
|
|
15858
|
+
titleMeta
|
|
15859
|
+
)}
|
|
15860
|
+
${renderHealthItems(
|
|
15861
|
+
digest.missingRequiredFilesPRs || [],
|
|
15862
|
+
"missing-files",
|
|
15863
|
+
SVG_ICONS.file,
|
|
15864
|
+
"Missing Required Files",
|
|
15865
|
+
(pr) => pr.missingRequiredFiles ? escapeHtml(pr.missingRequiredFiles.join(", ")) : truncateTitle(pr.title)
|
|
15866
|
+
)}
|
|
15867
|
+
${renderHealthItems(
|
|
15868
|
+
digest.needsRebasePRs || [],
|
|
15869
|
+
"needs-rebase",
|
|
15870
|
+
SVG_ICONS.refresh,
|
|
15871
|
+
(pr) => `Needs Rebase${pr.commitsBehindUpstream ? ` (${pr.commitsBehindUpstream} behind)` : ""}`,
|
|
15872
|
+
titleMeta
|
|
15873
|
+
)}
|
|
15874
|
+
</div>
|
|
15875
|
+
</section>
|
|
15876
|
+
` : ""}
|
|
15877
|
+
|
|
15878
|
+
${waitingOnOthers.length > 0 ? `
|
|
15879
|
+
<section class="health-section waiting-section">
|
|
15568
15880
|
<div class="health-header">
|
|
15569
15881
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--accent-info)" stroke-width="2">
|
|
15570
15882
|
<circle cx="12" cy="12" r="10"/>
|
|
@@ -15577,13 +15889,13 @@ function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpene
|
|
|
15577
15889
|
${renderHealthItems(
|
|
15578
15890
|
digest.changesAddressedPRs || [],
|
|
15579
15891
|
"changes-addressed",
|
|
15580
|
-
|
|
15892
|
+
SVG_ICONS.checkCircle,
|
|
15581
15893
|
"Changes Addressed",
|
|
15582
15894
|
(pr) => `Awaiting re-review${pr.lastMaintainerComment ? ` from @${escapeHtml(pr.lastMaintainerComment.author)}` : ""}`
|
|
15583
15895
|
)}
|
|
15584
|
-
${renderHealthItems(digest.waitingOnMaintainerPRs || [], "waiting-maintainer",
|
|
15585
|
-
${renderHealthItems(digest.ciBlockedPRs || [], "ci-blocked",
|
|
15586
|
-
${renderHealthItems(digest.ciNotRunningPRs || [], "ci-not-running",
|
|
15896
|
+
${renderHealthItems(digest.waitingOnMaintainerPRs || [], "waiting-maintainer", SVG_ICONS.clock, "Waiting on Maintainer", titleMeta)}
|
|
15897
|
+
${renderHealthItems(digest.ciBlockedPRs || [], "ci-blocked", SVG_ICONS.lock, "CI Blocked", titleMeta)}
|
|
15898
|
+
${renderHealthItems(digest.ciNotRunningPRs || [], "ci-not-running", SVG_ICONS.infoCircle, "CI Not Running", titleMeta)}
|
|
15587
15899
|
</div>
|
|
15588
15900
|
</section>
|
|
15589
15901
|
` : ""}
|
|
@@ -15607,7 +15919,7 @@ function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpene
|
|
|
15607
15919
|
<section class="health-section" style="animation-delay: 0.15s;">
|
|
15608
15920
|
<div class="health-header">
|
|
15609
15921
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--accent-merged)" stroke-width="2">
|
|
15610
|
-
${
|
|
15922
|
+
${SVG_ICONS.gitMerge}
|
|
15611
15923
|
</svg>
|
|
15612
15924
|
<h2>Recently Merged</h2>
|
|
15613
15925
|
<span class="health-badge" style="background: var(--accent-merged-dim); color: var(--accent-merged);">${recentlyMerged.length} merged</span>
|
|
@@ -15618,7 +15930,7 @@ function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpene
|
|
|
15618
15930
|
<div class="health-item" style="border-left-color: var(--accent-merged);" data-status="merged" data-repo="${escapeHtml(pr.repo)}" data-title="${escapeHtml(pr.title.toLowerCase())}">
|
|
15619
15931
|
<div class="health-icon" style="background: var(--accent-merged-dim); color: var(--accent-merged);">
|
|
15620
15932
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
15621
|
-
${
|
|
15933
|
+
${SVG_ICONS.gitMerge}
|
|
15622
15934
|
</svg>
|
|
15623
15935
|
</div>
|
|
15624
15936
|
<div class="health-content">
|
|
@@ -15669,7 +15981,7 @@ function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpene
|
|
|
15669
15981
|
<section class="health-section" style="animation-delay: 0.25s;">
|
|
15670
15982
|
<div class="health-header">
|
|
15671
15983
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--accent-info)" stroke-width="2">
|
|
15672
|
-
${
|
|
15984
|
+
${SVG_ICONS.bell}
|
|
15673
15985
|
</svg>
|
|
15674
15986
|
<h2>Auto-Unshelved</h2>
|
|
15675
15987
|
<span class="health-badge" style="background: var(--accent-info-dim); color: var(--accent-info);">${autoUnshelvedPRs.length} unshelved</span>
|
|
@@ -15678,7 +15990,7 @@ function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpene
|
|
|
15678
15990
|
${renderHealthItems(
|
|
15679
15991
|
autoUnshelvedPRs,
|
|
15680
15992
|
"auto-unshelved",
|
|
15681
|
-
|
|
15993
|
+
SVG_ICONS.bell,
|
|
15682
15994
|
(pr) => "Auto-Unshelved (" + pr.status.replace(/_/g, " ") + ")",
|
|
15683
15995
|
titleMeta
|
|
15684
15996
|
)}
|
|
@@ -15690,7 +16002,7 @@ function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpene
|
|
|
15690
16002
|
<section class="health-section" style="animation-delay: 0.3s;">
|
|
15691
16003
|
<div class="health-header">
|
|
15692
16004
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--accent-info)" stroke-width="2">
|
|
15693
|
-
${
|
|
16005
|
+
${SVG_ICONS.comment}
|
|
15694
16006
|
</svg>
|
|
15695
16007
|
<h2>Issue Conversations</h2>
|
|
15696
16008
|
<span class="health-badge" style="background: var(--accent-info-dim); color: var(--accent-info);">${issueResponses.length} repl${issueResponses.length !== 1 ? "ies" : "y"}</span>
|
|
@@ -15701,7 +16013,7 @@ function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpene
|
|
|
15701
16013
|
<div class="health-item changes-addressed">
|
|
15702
16014
|
<div class="health-icon" style="background: var(--accent-info-dim); color: var(--accent-info);">
|
|
15703
16015
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
15704
|
-
${
|
|
16016
|
+
${SVG_ICONS.comment}
|
|
15705
16017
|
</svg>
|
|
15706
16018
|
</div>
|
|
15707
16019
|
<div class="health-content">
|
|
@@ -15737,394 +16049,131 @@ function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpene
|
|
|
15737
16049
|
</div>
|
|
15738
16050
|
</div>
|
|
15739
16051
|
</div>
|
|
15740
|
-
</div>
|
|
15741
|
-
|
|
15742
|
-
<div class="card" style="margin-bottom: 1.25rem;">
|
|
15743
|
-
<div class="card-header">
|
|
15744
|
-
<span class="card-title">Contribution Timeline</span>
|
|
15745
|
-
</div>
|
|
15746
|
-
<div class="card-body">
|
|
15747
|
-
<div class="chart-container" style="height: 250px;">
|
|
15748
|
-
<canvas id="monthlyChart"></canvas>
|
|
15749
|
-
</div>
|
|
15750
|
-
</div>
|
|
15751
|
-
</div>
|
|
15752
|
-
|
|
15753
|
-
|
|
15754
|
-
${activePRList.length > 0 ? `
|
|
15755
|
-
<section class="pr-list-section">
|
|
15756
|
-
<div class="pr-list-header">
|
|
15757
|
-
<h2 class="pr-list-title">Active Pull Requests</h2>
|
|
15758
|
-
<span class="pr-count">${activePRList.length} open</span>
|
|
15759
|
-
</div>
|
|
15760
|
-
<div class="pr-list">
|
|
15761
|
-
${activePRList.map((pr) => {
|
|
15762
|
-
const hasIssues = pr.ciStatus === "failing" || pr.hasMergeConflict || pr.hasUnrespondedComment && pr.status !== "changes_addressed" || pr.status === "needs_changes";
|
|
15763
|
-
const isStale = pr.daysSinceActivity >= approachingDormantDays;
|
|
15764
|
-
const itemClass = hasIssues ? "has-issues" : isStale ? "stale" : "";
|
|
15765
|
-
const prStatus = pr.ciStatus === "failing" ? "ci-failing" : pr.hasMergeConflict ? "conflict" : pr.hasUnrespondedComment && pr.status !== "changes_addressed" && pr.status !== "failing_ci" ? "needs-response" : pr.status === "needs_changes" ? "needs-changes" : pr.status === "changes_addressed" ? "changes-addressed" : "active";
|
|
15766
|
-
return `
|
|
15767
|
-
<div class="pr-item ${itemClass}" data-status="${prStatus}" data-repo="${escapeHtml(pr.repo)}" data-title="${escapeHtml(pr.title.toLowerCase())}">
|
|
15768
|
-
<div class="pr-status-indicator">
|
|
15769
|
-
${hasIssues ? `
|
|
15770
|
-
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
15771
|
-
<circle cx="12" cy="12" r="10"/>
|
|
15772
|
-
<line x1="12" y1="8" x2="12" y2="12"/>
|
|
15773
|
-
<line x1="12" y1="16" x2="12.01" y2="16"/>
|
|
15774
|
-
</svg>
|
|
15775
|
-
` : `
|
|
15776
|
-
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
15777
|
-
<circle cx="12" cy="12" r="10"/>
|
|
15778
|
-
<line x1="12" y1="16" x2="12" y2="12"/>
|
|
15779
|
-
<line x1="12" y1="8" x2="12.01" y2="8"/>
|
|
15780
|
-
</svg>
|
|
15781
|
-
`}
|
|
15782
|
-
</div>
|
|
15783
|
-
<div class="pr-content">
|
|
15784
|
-
<div class="pr-title-row">
|
|
15785
|
-
<a href="${escapeHtml(pr.url)}" target="_blank" class="pr-title">${escapeHtml(pr.title)}</a>
|
|
15786
|
-
<span class="pr-repo">${escapeHtml(pr.repo)}#${pr.number}</span>
|
|
15787
|
-
</div>
|
|
15788
|
-
<div class="pr-badges">
|
|
15789
|
-
${pr.ciStatus === "failing" ? '<span class="badge badge-ci-failing">CI Failing</span>' : ""}
|
|
15790
|
-
${pr.ciStatus === "passing" ? '<span class="badge badge-passing">CI Passing</span>' : ""}
|
|
15791
|
-
${pr.ciStatus === "pending" ? '<span class="badge badge-pending">CI Pending</span>' : ""}
|
|
15792
|
-
${pr.hasMergeConflict ? '<span class="badge badge-conflict">Merge Conflict</span>' : ""}
|
|
15793
|
-
${pr.hasUnrespondedComment && pr.status === "changes_addressed" ? '<span class="badge badge-changes-addressed">Changes Addressed</span>' : ""}
|
|
15794
|
-
${pr.hasUnrespondedComment && pr.status !== "changes_addressed" && pr.status !== "failing_ci" ? '<span class="badge badge-needs-response">Needs Response</span>' : ""}
|
|
15795
|
-
${pr.reviewDecision === "changes_requested" ? '<span class="badge badge-changes-requested">Changes Requested</span>' : ""}
|
|
15796
|
-
${isStale ? `<span class="badge badge-stale">${pr.daysSinceActivity}d inactive</span>` : ""}
|
|
15797
|
-
</div>
|
|
15798
|
-
</div>
|
|
15799
|
-
<div class="pr-activity">
|
|
15800
|
-
${pr.daysSinceActivity === 0 ? "Today" : pr.daysSinceActivity === 1 ? "Yesterday" : pr.daysSinceActivity + "d ago"}
|
|
15801
|
-
</div>
|
|
15802
|
-
</div>`;
|
|
15803
|
-
}).join("")}
|
|
15804
|
-
</div>
|
|
15805
|
-
</section>
|
|
15806
|
-
` : `
|
|
15807
|
-
<section class="pr-list-section">
|
|
15808
|
-
<div class="pr-list-header">
|
|
15809
|
-
<h2 class="pr-list-title">Active Pull Requests</h2>
|
|
15810
|
-
<span class="pr-count">0 open</span>
|
|
15811
|
-
</div>
|
|
15812
|
-
<div class="empty-state">
|
|
15813
|
-
<div class="empty-state-icon">
|
|
15814
|
-
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
15815
|
-
<circle cx="12" cy="12" r="10"/>
|
|
15816
|
-
<path d="M8 12h8"/>
|
|
15817
|
-
</svg>
|
|
15818
|
-
</div>
|
|
15819
|
-
<p>No active pull requests</p>
|
|
15820
|
-
</div>
|
|
15821
|
-
</section>
|
|
15822
|
-
`}
|
|
15823
|
-
|
|
15824
|
-
${shelvedPRs.length > 0 ? `
|
|
15825
|
-
<section class="pr-list-section" style="margin-top: 1.25rem; opacity: 0.7;">
|
|
15826
|
-
<div class="pr-list-header">
|
|
15827
|
-
<h2 class="pr-list-title">Shelved Pull Requests</h2>
|
|
15828
|
-
<span class="pr-count" style="background: rgba(110, 118, 129, 0.15); color: var(--text-muted);">${shelvedPRs.length} shelved</span>
|
|
15829
|
-
</div>
|
|
15830
|
-
<div class="pr-list">
|
|
15831
|
-
${shelvedPRs.map(
|
|
15832
|
-
(pr) => `
|
|
15833
|
-
<div class="pr-item" data-status="shelved" data-repo="${escapeHtml(pr.repo)}" data-title="${escapeHtml(pr.title.toLowerCase())}">
|
|
15834
|
-
<div class="pr-status-indicator" style="background: rgba(110, 118, 129, 0.1); color: var(--text-muted);">
|
|
15835
|
-
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
15836
|
-
${SVG.box}
|
|
15837
|
-
</svg>
|
|
15838
|
-
</div>
|
|
15839
|
-
<div class="pr-content">
|
|
15840
|
-
<div class="pr-title-row">
|
|
15841
|
-
<a href="${escapeHtml(pr.url)}" target="_blank" class="pr-title">${escapeHtml(pr.title)}</a>
|
|
15842
|
-
<span class="pr-repo">${escapeHtml(pr.repo)}#${pr.number}</span>
|
|
15843
|
-
</div>
|
|
15844
|
-
<div class="pr-badges">
|
|
15845
|
-
<span class="badge badge-days">${pr.daysSinceActivity}d inactive</span>
|
|
15846
|
-
</div>
|
|
15847
|
-
</div>
|
|
15848
|
-
<div class="pr-activity">
|
|
15849
|
-
${pr.daysSinceActivity === 0 ? "Today" : pr.daysSinceActivity === 1 ? "Yesterday" : pr.daysSinceActivity + "d ago"}
|
|
15850
|
-
</div>
|
|
15851
|
-
</div>`
|
|
15852
|
-
).join("")}
|
|
15853
|
-
</div>
|
|
15854
|
-
</section>
|
|
15855
|
-
` : ""}
|
|
15856
|
-
|
|
15857
|
-
<footer class="footer">
|
|
15858
|
-
<p>OSS Autopilot // Mission Control</p>
|
|
15859
|
-
<p style="margin-top: 0.25rem;">Dashboard generated: ${digest.generatedAt ? new Date(digest.generatedAt).toISOString() : "Unknown"}</p>
|
|
15860
|
-
</footer>
|
|
15861
|
-
</div>
|
|
15862
|
-
|
|
15863
|
-
<script>
|
|
15864
|
-
// === Theme Toggle ===
|
|
15865
|
-
(function() {
|
|
15866
|
-
var html = document.documentElement;
|
|
15867
|
-
var toggle = document.getElementById('themeToggle');
|
|
15868
|
-
var sunIcon = document.getElementById('themeIconSun');
|
|
15869
|
-
var moonIcon = document.getElementById('themeIconMoon');
|
|
15870
|
-
var label = document.getElementById('themeLabel');
|
|
15871
|
-
|
|
15872
|
-
function getEffectiveTheme() {
|
|
15873
|
-
try {
|
|
15874
|
-
var stored = localStorage.getItem('oss-dashboard-theme');
|
|
15875
|
-
if (stored === 'light' || stored === 'dark') return stored;
|
|
15876
|
-
} catch (e) { /* localStorage unavailable (private browsing) */ }
|
|
15877
|
-
return window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
|
|
15878
|
-
}
|
|
15879
|
-
|
|
15880
|
-
function applyTheme(theme) {
|
|
15881
|
-
html.setAttribute('data-theme', theme);
|
|
15882
|
-
if (theme === 'light') {
|
|
15883
|
-
sunIcon.style.display = 'none';
|
|
15884
|
-
moonIcon.style.display = 'block';
|
|
15885
|
-
label.textContent = 'Dark';
|
|
15886
|
-
} else {
|
|
15887
|
-
sunIcon.style.display = 'block';
|
|
15888
|
-
moonIcon.style.display = 'none';
|
|
15889
|
-
label.textContent = 'Light';
|
|
15890
|
-
}
|
|
15891
|
-
}
|
|
15892
|
-
|
|
15893
|
-
applyTheme(getEffectiveTheme());
|
|
15894
|
-
|
|
15895
|
-
toggle.addEventListener('click', function() {
|
|
15896
|
-
var current = html.getAttribute('data-theme');
|
|
15897
|
-
var next = current === 'dark' ? 'light' : 'dark';
|
|
15898
|
-
try { localStorage.setItem('oss-dashboard-theme', next); } catch (e) { /* private browsing */ }
|
|
15899
|
-
applyTheme(next);
|
|
15900
|
-
});
|
|
15901
|
-
})();
|
|
15902
|
-
|
|
15903
|
-
// === Filtering & Search ===
|
|
15904
|
-
(function() {
|
|
15905
|
-
var searchInput = document.getElementById('searchInput');
|
|
15906
|
-
var statusFilter = document.getElementById('statusFilter');
|
|
15907
|
-
var repoFilter = document.getElementById('repoFilter');
|
|
15908
|
-
var filterCount = document.getElementById('filterCount');
|
|
15909
|
-
|
|
15910
|
-
function applyFilters() {
|
|
15911
|
-
var query = searchInput.value.toLowerCase().trim();
|
|
15912
|
-
var status = statusFilter.value;
|
|
15913
|
-
var repo = repoFilter.value;
|
|
15914
|
-
var allItems = document.querySelectorAll('.health-item[data-status], .pr-item[data-status]');
|
|
15915
|
-
var visible = 0;
|
|
15916
|
-
var total = allItems.length;
|
|
15917
|
-
|
|
15918
|
-
allItems.forEach(function(item) {
|
|
15919
|
-
var itemStatus = item.getAttribute('data-status') || '';
|
|
15920
|
-
var itemRepo = item.getAttribute('data-repo') || '';
|
|
15921
|
-
var itemTitle = item.getAttribute('data-title') || '';
|
|
15922
|
-
|
|
15923
|
-
var matchesStatus = (status === 'all') || (itemStatus === status);
|
|
15924
|
-
var matchesRepo = (repo === 'all') || (itemRepo === repo);
|
|
15925
|
-
var matchesSearch = !query || itemTitle.indexOf(query) !== -1;
|
|
15926
|
-
|
|
15927
|
-
if (matchesStatus && matchesRepo && matchesSearch) {
|
|
15928
|
-
item.setAttribute('data-hidden', 'false');
|
|
15929
|
-
visible++;
|
|
15930
|
-
} else {
|
|
15931
|
-
item.setAttribute('data-hidden', 'true');
|
|
15932
|
-
}
|
|
15933
|
-
});
|
|
15934
|
-
|
|
15935
|
-
// Show/hide parent sections if all children are hidden
|
|
15936
|
-
var sections = document.querySelectorAll('.health-section, .pr-list-section');
|
|
15937
|
-
sections.forEach(function(section) {
|
|
15938
|
-
var items = section.querySelectorAll('.health-item[data-status], .pr-item[data-status]');
|
|
15939
|
-
if (items.length === 0) return; // sections without filterable items (e.g. empty state)
|
|
15940
|
-
var anyVisible = false;
|
|
15941
|
-
items.forEach(function(item) {
|
|
15942
|
-
if (item.getAttribute('data-hidden') !== 'true') anyVisible = true;
|
|
15943
|
-
});
|
|
15944
|
-
section.style.display = anyVisible ? '' : 'none';
|
|
15945
|
-
});
|
|
15946
|
-
|
|
15947
|
-
var isFiltering = (status !== 'all' || repo !== 'all' || query.length > 0);
|
|
15948
|
-
filterCount.textContent = isFiltering ? (visible + ' of ' + total + ' items') : '';
|
|
15949
|
-
}
|
|
15950
|
-
|
|
15951
|
-
searchInput.addEventListener('input', applyFilters);
|
|
15952
|
-
statusFilter.addEventListener('change', applyFilters);
|
|
15953
|
-
repoFilter.addEventListener('change', applyFilters);
|
|
15954
|
-
})();
|
|
15955
|
-
|
|
15956
|
-
// === Chart.js Configuration ===
|
|
15957
|
-
Chart.defaults.color = '#6e7681';
|
|
15958
|
-
Chart.defaults.borderColor = 'rgba(48, 54, 61, 0.4)';
|
|
15959
|
-
Chart.defaults.font.family = "'Geist', sans-serif";
|
|
15960
|
-
Chart.defaults.font.size = 11;
|
|
15961
|
-
|
|
15962
|
-
// === Status Doughnut ===
|
|
15963
|
-
new Chart(document.getElementById('statusChart'), {
|
|
15964
|
-
type: 'doughnut',
|
|
15965
|
-
data: {
|
|
15966
|
-
labels: ['Active', 'Shelved', 'Merged', 'Closed'],
|
|
15967
|
-
datasets: [{
|
|
15968
|
-
data: [${stats.activePRs}, ${stats.shelvedPRs}, ${stats.mergedPRs}, ${stats.closedPRs}],
|
|
15969
|
-
backgroundColor: ['#3fb950', '#6e7681', '#a855f7', '#484f58'],
|
|
15970
|
-
borderColor: 'rgba(8, 11, 16, 0.8)',
|
|
15971
|
-
borderWidth: 2,
|
|
15972
|
-
hoverOffset: 8
|
|
15973
|
-
}]
|
|
15974
|
-
},
|
|
15975
|
-
options: {
|
|
15976
|
-
responsive: true,
|
|
15977
|
-
maintainAspectRatio: false,
|
|
15978
|
-
cutout: '65%',
|
|
15979
|
-
plugins: {
|
|
15980
|
-
legend: {
|
|
15981
|
-
position: 'bottom',
|
|
15982
|
-
labels: { padding: 16, usePointStyle: true, pointStyle: 'circle', font: { size: 11 } }
|
|
15983
|
-
}
|
|
15984
|
-
}
|
|
15985
|
-
}
|
|
15986
|
-
});
|
|
16052
|
+
</div>
|
|
15987
16053
|
|
|
15988
|
-
|
|
15989
|
-
|
|
15990
|
-
|
|
15991
|
-
|
|
15992
|
-
|
|
15993
|
-
|
|
15994
|
-
|
|
15995
|
-
|
|
15996
|
-
|
|
15997
|
-
|
|
15998
|
-
return false;
|
|
15999
|
-
};
|
|
16000
|
-
const allRepoEntries = Object.entries(
|
|
16001
|
-
// Rebuild from full prsByRepo to get all repos, not just top 10
|
|
16002
|
-
(() => {
|
|
16003
|
-
const all = {};
|
|
16004
|
-
for (const pr of digest.openPRs || []) {
|
|
16005
|
-
if (shouldExcludeRepo(pr.repo)) continue;
|
|
16006
|
-
if (!all[pr.repo]) all[pr.repo] = { active: 0, merged: 0, closed: 0 };
|
|
16007
|
-
all[pr.repo].active++;
|
|
16008
|
-
}
|
|
16009
|
-
for (const [repo, score] of Object.entries(state.repoScores || {})) {
|
|
16010
|
-
if (shouldExcludeRepo(repo)) continue;
|
|
16011
|
-
if (!all[repo]) all[repo] = { active: 0, merged: 0, closed: 0 };
|
|
16012
|
-
all[repo].merged = score.mergedPRCount;
|
|
16013
|
-
all[repo].closed = score.closedWithoutMergeCount;
|
|
16014
|
-
}
|
|
16015
|
-
return all;
|
|
16016
|
-
})()
|
|
16017
|
-
).sort((a, b) => {
|
|
16018
|
-
const totalA = a[1].merged + a[1].active + a[1].closed;
|
|
16019
|
-
const totalB = b[1].merged + b[1].active + b[1].closed;
|
|
16020
|
-
return totalB - totalA;
|
|
16021
|
-
});
|
|
16022
|
-
const displayRepos = allRepoEntries.slice(0, 10);
|
|
16023
|
-
const otherRepos = allRepoEntries.slice(10);
|
|
16024
|
-
const grandTotal = allRepoEntries.reduce((sum, [, d]) => sum + d.merged + d.active + d.closed, 0);
|
|
16025
|
-
if (otherRepos.length > 0) {
|
|
16026
|
-
const otherData = otherRepos.reduce(
|
|
16027
|
-
(acc, [, d]) => ({
|
|
16028
|
-
active: acc.active + d.active,
|
|
16029
|
-
merged: acc.merged + d.merged,
|
|
16030
|
-
closed: acc.closed + d.closed
|
|
16031
|
-
}),
|
|
16032
|
-
{ active: 0, merged: 0, closed: 0 }
|
|
16033
|
-
);
|
|
16034
|
-
displayRepos.push(["Other", otherData]);
|
|
16035
|
-
}
|
|
16036
|
-
const repoLabels = displayRepos.map(([repo]) => repo === "Other" ? "Other" : repo.split("/")[1] || repo);
|
|
16037
|
-
const mergedData = displayRepos.map(([, d]) => d.merged);
|
|
16038
|
-
const activeData = displayRepos.map(([, d]) => d.active);
|
|
16039
|
-
const closedData = displayRepos.map(([, d]) => d.closed);
|
|
16040
|
-
return `
|
|
16041
|
-
new Chart(document.getElementById('reposChart'), {
|
|
16042
|
-
type: 'bar',
|
|
16043
|
-
data: {
|
|
16044
|
-
labels: ${JSON.stringify(repoLabels)},
|
|
16045
|
-
datasets: [
|
|
16046
|
-
{ label: 'Merged', data: ${JSON.stringify(mergedData)}, backgroundColor: '#a855f7', borderRadius: 3 },
|
|
16047
|
-
{ label: 'Active', data: ${JSON.stringify(activeData)}, backgroundColor: '#3fb950', borderRadius: 3 },
|
|
16048
|
-
{ label: 'Closed', data: ${JSON.stringify(closedData)}, backgroundColor: '#484f58', borderRadius: 3 }
|
|
16049
|
-
]
|
|
16050
|
-
},
|
|
16051
|
-
options: {
|
|
16052
|
-
responsive: true,
|
|
16053
|
-
maintainAspectRatio: false,
|
|
16054
|
-
scales: {
|
|
16055
|
-
x: { stacked: true, grid: { display: false }, ticks: { font: { size: 10 } } },
|
|
16056
|
-
y: { stacked: true, grid: { color: 'rgba(48, 54, 61, 0.3)' }, ticks: { stepSize: 1 } }
|
|
16057
|
-
},
|
|
16058
|
-
plugins: {
|
|
16059
|
-
legend: { position: 'bottom', labels: { padding: 16, usePointStyle: true, pointStyle: 'circle', font: { size: 11 } } },
|
|
16060
|
-
tooltip: {
|
|
16061
|
-
callbacks: {
|
|
16062
|
-
afterBody: function(context) {
|
|
16063
|
-
const idx = context[0].dataIndex;
|
|
16064
|
-
const total = ${JSON.stringify(mergedData)}[idx] + ${JSON.stringify(activeData)}[idx] + ${JSON.stringify(closedData)}[idx];
|
|
16065
|
-
const pct = ${grandTotal} > 0 ? ((total / ${grandTotal}) * 100).toFixed(1) : '0.0';
|
|
16066
|
-
return pct + '% of all PRs';
|
|
16067
|
-
}
|
|
16068
|
-
}
|
|
16069
|
-
}
|
|
16070
|
-
}
|
|
16071
|
-
}
|
|
16072
|
-
});`;
|
|
16073
|
-
})()}
|
|
16054
|
+
<div class="card" style="margin-bottom: 1.25rem;">
|
|
16055
|
+
<div class="card-header">
|
|
16056
|
+
<span class="card-title">Contribution Timeline</span>
|
|
16057
|
+
</div>
|
|
16058
|
+
<div class="card-body">
|
|
16059
|
+
<div class="chart-container" style="height: 250px;">
|
|
16060
|
+
<canvas id="monthlyChart"></canvas>
|
|
16061
|
+
</div>
|
|
16062
|
+
</div>
|
|
16063
|
+
</div>
|
|
16074
16064
|
|
|
16075
|
-
|
|
16076
|
-
${
|
|
16077
|
-
|
|
16078
|
-
|
|
16079
|
-
|
|
16080
|
-
|
|
16081
|
-
|
|
16082
|
-
|
|
16065
|
+
|
|
16066
|
+
${activePRList.length > 0 ? `
|
|
16067
|
+
<section class="pr-list-section">
|
|
16068
|
+
<div class="pr-list-header">
|
|
16069
|
+
<h2 class="pr-list-title">Active Pull Requests</h2>
|
|
16070
|
+
<span class="pr-count">${activePRList.length} open</span>
|
|
16071
|
+
</div>
|
|
16072
|
+
<div class="pr-list">
|
|
16073
|
+
${activePRList.map((pr) => {
|
|
16074
|
+
const hasIssues = pr.ciStatus === "failing" || pr.hasMergeConflict || pr.hasUnrespondedComment && pr.status !== "changes_addressed" || pr.status === "needs_changes";
|
|
16075
|
+
const isStale = pr.daysSinceActivity >= approachingDormantDays;
|
|
16076
|
+
const itemClass = hasIssues ? "has-issues" : isStale ? "stale" : "";
|
|
16077
|
+
const prStatus = pr.ciStatus === "failing" ? "ci-failing" : pr.hasMergeConflict ? "conflict" : pr.hasUnrespondedComment && pr.status !== "changes_addressed" && pr.status !== "failing_ci" ? "needs-response" : pr.status === "needs_changes" ? "needs-changes" : pr.status === "changes_addressed" ? "changes-addressed" : "active";
|
|
16083
16078
|
return `
|
|
16084
|
-
|
|
16085
|
-
|
|
16086
|
-
|
|
16087
|
-
|
|
16088
|
-
|
|
16089
|
-
|
|
16090
|
-
|
|
16091
|
-
|
|
16092
|
-
|
|
16093
|
-
|
|
16094
|
-
|
|
16095
|
-
|
|
16096
|
-
|
|
16097
|
-
|
|
16098
|
-
|
|
16099
|
-
|
|
16100
|
-
|
|
16101
|
-
|
|
16102
|
-
|
|
16103
|
-
|
|
16104
|
-
|
|
16105
|
-
|
|
16106
|
-
|
|
16107
|
-
|
|
16108
|
-
|
|
16109
|
-
|
|
16110
|
-
|
|
16111
|
-
|
|
16112
|
-
|
|
16113
|
-
|
|
16114
|
-
|
|
16115
|
-
|
|
16116
|
-
|
|
16117
|
-
|
|
16118
|
-
|
|
16119
|
-
|
|
16120
|
-
|
|
16121
|
-
|
|
16122
|
-
|
|
16123
|
-
|
|
16124
|
-
|
|
16125
|
-
|
|
16126
|
-
|
|
16079
|
+
<div class="pr-item ${itemClass}" data-status="${prStatus}" data-repo="${escapeHtml(pr.repo)}" data-title="${escapeHtml(pr.title.toLowerCase())}">
|
|
16080
|
+
<div class="pr-status-indicator">
|
|
16081
|
+
${hasIssues ? `
|
|
16082
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
16083
|
+
<circle cx="12" cy="12" r="10"/>
|
|
16084
|
+
<line x1="12" y1="8" x2="12" y2="12"/>
|
|
16085
|
+
<line x1="12" y1="16" x2="12.01" y2="16"/>
|
|
16086
|
+
</svg>
|
|
16087
|
+
` : `
|
|
16088
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
16089
|
+
<circle cx="12" cy="12" r="10"/>
|
|
16090
|
+
<line x1="12" y1="16" x2="12" y2="12"/>
|
|
16091
|
+
<line x1="12" y1="8" x2="12.01" y2="8"/>
|
|
16092
|
+
</svg>
|
|
16093
|
+
`}
|
|
16094
|
+
</div>
|
|
16095
|
+
<div class="pr-content">
|
|
16096
|
+
<div class="pr-title-row">
|
|
16097
|
+
<a href="${escapeHtml(pr.url)}" target="_blank" class="pr-title">${escapeHtml(pr.title)}</a>
|
|
16098
|
+
<span class="pr-repo">${escapeHtml(pr.repo)}#${pr.number}</span>
|
|
16099
|
+
</div>
|
|
16100
|
+
<div class="pr-badges">
|
|
16101
|
+
${pr.ciStatus === "failing" ? '<span class="badge badge-ci-failing">CI Failing</span>' : ""}
|
|
16102
|
+
${pr.ciStatus === "passing" ? '<span class="badge badge-passing">CI Passing</span>' : ""}
|
|
16103
|
+
${pr.ciStatus === "pending" ? '<span class="badge badge-pending">CI Pending</span>' : ""}
|
|
16104
|
+
${pr.hasMergeConflict ? '<span class="badge badge-conflict">Merge Conflict</span>' : ""}
|
|
16105
|
+
${pr.hasUnrespondedComment && pr.status === "changes_addressed" ? '<span class="badge badge-changes-addressed">Changes Addressed</span>' : ""}
|
|
16106
|
+
${pr.hasUnrespondedComment && pr.status !== "changes_addressed" && pr.status !== "failing_ci" ? '<span class="badge badge-needs-response">Needs Response</span>' : ""}
|
|
16107
|
+
${pr.reviewDecision === "changes_requested" ? '<span class="badge badge-changes-requested">Changes Requested</span>' : ""}
|
|
16108
|
+
${isStale ? `<span class="badge badge-stale">${pr.daysSinceActivity}d inactive</span>` : ""}
|
|
16109
|
+
</div>
|
|
16110
|
+
</div>
|
|
16111
|
+
<div class="pr-activity">
|
|
16112
|
+
${pr.daysSinceActivity === 0 ? "Today" : pr.daysSinceActivity === 1 ? "Yesterday" : pr.daysSinceActivity + "d ago"}
|
|
16113
|
+
</div>
|
|
16114
|
+
</div>`;
|
|
16115
|
+
}).join("")}
|
|
16116
|
+
</div>
|
|
16117
|
+
</section>
|
|
16118
|
+
` : `
|
|
16119
|
+
<section class="pr-list-section">
|
|
16120
|
+
<div class="pr-list-header">
|
|
16121
|
+
<h2 class="pr-list-title">Active Pull Requests</h2>
|
|
16122
|
+
<span class="pr-count">0 open</span>
|
|
16123
|
+
</div>
|
|
16124
|
+
<div class="empty-state">
|
|
16125
|
+
<div class="empty-state-icon">
|
|
16126
|
+
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
16127
|
+
<circle cx="12" cy="12" r="10"/>
|
|
16128
|
+
<path d="M8 12h8"/>
|
|
16129
|
+
</svg>
|
|
16130
|
+
</div>
|
|
16131
|
+
<p>No active pull requests</p>
|
|
16132
|
+
</div>
|
|
16133
|
+
</section>
|
|
16134
|
+
`}
|
|
16135
|
+
|
|
16136
|
+
${shelvedPRs.length > 0 ? `
|
|
16137
|
+
<section class="pr-list-section" style="margin-top: 1.25rem; opacity: 0.7;">
|
|
16138
|
+
<div class="pr-list-header">
|
|
16139
|
+
<h2 class="pr-list-title">Shelved Pull Requests</h2>
|
|
16140
|
+
<span class="pr-count" style="background: rgba(110, 118, 129, 0.15); color: var(--text-muted);">${shelvedPRs.length} shelved</span>
|
|
16141
|
+
</div>
|
|
16142
|
+
<div class="pr-list">
|
|
16143
|
+
${shelvedPRs.map(
|
|
16144
|
+
(pr) => `
|
|
16145
|
+
<div class="pr-item" data-status="shelved" data-repo="${escapeHtml(pr.repo)}" data-title="${escapeHtml(pr.title.toLowerCase())}">
|
|
16146
|
+
<div class="pr-status-indicator" style="background: rgba(110, 118, 129, 0.1); color: var(--text-muted);">
|
|
16147
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
16148
|
+
${SVG_ICONS.box}
|
|
16149
|
+
</svg>
|
|
16150
|
+
</div>
|
|
16151
|
+
<div class="pr-content">
|
|
16152
|
+
<div class="pr-title-row">
|
|
16153
|
+
<a href="${escapeHtml(pr.url)}" target="_blank" class="pr-title">${escapeHtml(pr.title)}</a>
|
|
16154
|
+
<span class="pr-repo">${escapeHtml(pr.repo)}#${pr.number}</span>
|
|
16155
|
+
</div>
|
|
16156
|
+
<div class="pr-badges">
|
|
16157
|
+
<span class="badge badge-days">${pr.daysSinceActivity}d inactive</span>
|
|
16158
|
+
</div>
|
|
16159
|
+
</div>
|
|
16160
|
+
<div class="pr-activity">
|
|
16161
|
+
${pr.daysSinceActivity === 0 ? "Today" : pr.daysSinceActivity === 1 ? "Yesterday" : pr.daysSinceActivity + "d ago"}
|
|
16162
|
+
</div>
|
|
16163
|
+
</div>`
|
|
16164
|
+
).join("")}
|
|
16165
|
+
</div>
|
|
16166
|
+
</section>
|
|
16167
|
+
` : ""}
|
|
16168
|
+
|
|
16169
|
+
<footer class="footer">
|
|
16170
|
+
<p>OSS Autopilot // Mission Control</p>
|
|
16171
|
+
<p style="margin-top: 0.25rem;">Dashboard generated: ${digest.generatedAt ? new Date(digest.generatedAt).toISOString() : "Unknown"}</p>
|
|
16172
|
+
</footer>
|
|
16173
|
+
</div>
|
|
16127
16174
|
|
|
16175
|
+
<script>
|
|
16176
|
+
${generateDashboardScripts(stats, monthlyMerged, monthlyClosed, monthlyOpened, digest, state)}
|
|
16128
16177
|
</script>
|
|
16129
16178
|
</body>
|
|
16130
16179
|
</html>`;
|
|
@@ -16132,6 +16181,11 @@ function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpene
|
|
|
16132
16181
|
var init_dashboard_templates = __esm({
|
|
16133
16182
|
"src/commands/dashboard-templates.ts"() {
|
|
16134
16183
|
"use strict";
|
|
16184
|
+
init_dashboard_formatters();
|
|
16185
|
+
init_dashboard_styles();
|
|
16186
|
+
init_dashboard_components();
|
|
16187
|
+
init_dashboard_scripts();
|
|
16188
|
+
init_dashboard_formatters();
|
|
16135
16189
|
}
|
|
16136
16190
|
});
|
|
16137
16191
|
|
|
@@ -16807,15 +16861,15 @@ async function runCheckIntegration(options) {
|
|
|
16807
16861
|
}
|
|
16808
16862
|
referencedBy = [...new Set(referencedBy)];
|
|
16809
16863
|
const isIntegrated = referencedBy.length > 0;
|
|
16810
|
-
const
|
|
16864
|
+
const info2 = {
|
|
16811
16865
|
path: newFile,
|
|
16812
16866
|
referencedBy,
|
|
16813
16867
|
isIntegrated
|
|
16814
16868
|
};
|
|
16815
16869
|
if (!isIntegrated) {
|
|
16816
|
-
|
|
16870
|
+
info2.suggestedEntryPoints = suggestEntryPoints(newFile, allFiles);
|
|
16817
16871
|
}
|
|
16818
|
-
results.push(
|
|
16872
|
+
results.push(info2);
|
|
16819
16873
|
}
|
|
16820
16874
|
const unreferencedCount = results.filter((r) => !r.isIntegrated).length;
|
|
16821
16875
|
return { newFiles: results, unreferencedCount };
|
|
@@ -17145,29 +17199,28 @@ var init_shelve = __esm({
|
|
|
17145
17199
|
// src/commands/dismiss.ts
|
|
17146
17200
|
var dismiss_exports = {};
|
|
17147
17201
|
__export(dismiss_exports, {
|
|
17148
|
-
ISSUE_URL_PATTERN: () => ISSUE_URL_PATTERN,
|
|
17149
17202
|
runDismiss: () => runDismiss,
|
|
17150
17203
|
runUndismiss: () => runUndismiss
|
|
17151
17204
|
});
|
|
17152
17205
|
async function runDismiss(options) {
|
|
17153
|
-
validateUrl(options.
|
|
17154
|
-
validateGitHubUrl(options.
|
|
17206
|
+
validateUrl(options.url);
|
|
17207
|
+
validateGitHubUrl(options.url, ISSUE_OR_PR_URL_PATTERN, "issue or PR");
|
|
17155
17208
|
const stateManager2 = getStateManager();
|
|
17156
|
-
const added = stateManager2.dismissIssue(options.
|
|
17209
|
+
const added = stateManager2.dismissIssue(options.url, (/* @__PURE__ */ new Date()).toISOString());
|
|
17157
17210
|
if (added) {
|
|
17158
17211
|
stateManager2.save();
|
|
17159
17212
|
}
|
|
17160
|
-
return { dismissed: added, url: options.
|
|
17213
|
+
return { dismissed: added, url: options.url };
|
|
17161
17214
|
}
|
|
17162
17215
|
async function runUndismiss(options) {
|
|
17163
|
-
validateUrl(options.
|
|
17164
|
-
validateGitHubUrl(options.
|
|
17216
|
+
validateUrl(options.url);
|
|
17217
|
+
validateGitHubUrl(options.url, ISSUE_OR_PR_URL_PATTERN, "issue or PR");
|
|
17165
17218
|
const stateManager2 = getStateManager();
|
|
17166
|
-
const removed = stateManager2.undismissIssue(options.
|
|
17219
|
+
const removed = stateManager2.undismissIssue(options.url);
|
|
17167
17220
|
if (removed) {
|
|
17168
17221
|
stateManager2.save();
|
|
17169
17222
|
}
|
|
17170
|
-
return { undismissed: removed, url: options.
|
|
17223
|
+
return { undismissed: removed, url: options.url };
|
|
17171
17224
|
}
|
|
17172
17225
|
var init_dismiss = __esm({
|
|
17173
17226
|
"src/commands/dismiss.ts"() {
|
|
@@ -17248,10 +17301,10 @@ init_errors();
|
|
|
17248
17301
|
init_json();
|
|
17249
17302
|
function printRepos(repos) {
|
|
17250
17303
|
const entries = Object.entries(repos).sort(([a], [b]) => a.localeCompare(b));
|
|
17251
|
-
for (const [remote,
|
|
17252
|
-
const branch =
|
|
17304
|
+
for (const [remote, info2] of entries) {
|
|
17305
|
+
const branch = info2.currentBranch ? ` (${info2.currentBranch})` : "";
|
|
17253
17306
|
console.log(` ${remote}${branch}`);
|
|
17254
|
-
console.log(` ${
|
|
17307
|
+
console.log(` ${info2.path}`);
|
|
17255
17308
|
}
|
|
17256
17309
|
}
|
|
17257
17310
|
function handleCommandError(err, json) {
|
|
@@ -17784,34 +17837,34 @@ program2.command("unshelve <pr-url>").description("Unshelve a PR (include in cap
|
|
|
17784
17837
|
handleCommandError(err, options.json);
|
|
17785
17838
|
}
|
|
17786
17839
|
});
|
|
17787
|
-
program2.command("dismiss <
|
|
17840
|
+
program2.command("dismiss <url>").description("Dismiss notifications for an issue or PR (resurfaces on new activity)").option("--json", "Output as JSON").action(async (url, options) => {
|
|
17788
17841
|
try {
|
|
17789
17842
|
const { runDismiss: runDismiss2 } = await Promise.resolve().then(() => (init_dismiss(), dismiss_exports));
|
|
17790
|
-
const data = await runDismiss2({
|
|
17843
|
+
const data = await runDismiss2({ url });
|
|
17791
17844
|
if (options.json) {
|
|
17792
17845
|
outputJson(data);
|
|
17793
17846
|
} else if (data.dismissed) {
|
|
17794
|
-
console.log(`Dismissed: ${
|
|
17795
|
-
console.log("
|
|
17847
|
+
console.log(`Dismissed: ${url}`);
|
|
17848
|
+
console.log("Notifications are now muted.");
|
|
17796
17849
|
console.log("New responses after this point will resurface automatically.");
|
|
17797
17850
|
} else {
|
|
17798
|
-
console.log("
|
|
17851
|
+
console.log("Already dismissed.");
|
|
17799
17852
|
}
|
|
17800
17853
|
} catch (err) {
|
|
17801
17854
|
handleCommandError(err, options.json);
|
|
17802
17855
|
}
|
|
17803
17856
|
});
|
|
17804
|
-
program2.command("undismiss <
|
|
17857
|
+
program2.command("undismiss <url>").description("Undismiss an issue or PR (re-enable notifications)").option("--json", "Output as JSON").action(async (url, options) => {
|
|
17805
17858
|
try {
|
|
17806
17859
|
const { runUndismiss: runUndismiss2 } = await Promise.resolve().then(() => (init_dismiss(), dismiss_exports));
|
|
17807
|
-
const data = await runUndismiss2({
|
|
17860
|
+
const data = await runUndismiss2({ url });
|
|
17808
17861
|
if (options.json) {
|
|
17809
17862
|
outputJson(data);
|
|
17810
17863
|
} else if (data.undismissed) {
|
|
17811
|
-
console.log(`Undismissed: ${
|
|
17812
|
-
console.log("
|
|
17864
|
+
console.log(`Undismissed: ${url}`);
|
|
17865
|
+
console.log("Notifications are active again.");
|
|
17813
17866
|
} else {
|
|
17814
|
-
console.log("
|
|
17867
|
+
console.log("Was not dismissed.");
|
|
17815
17868
|
}
|
|
17816
17869
|
} catch (err) {
|
|
17817
17870
|
handleCommandError(err, options.json);
|