@oss-autopilot/core 0.42.0 → 0.42.2
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 +1026 -1018
- package/dist/cli.js +18 -30
- package/dist/commands/check-integration.js +5 -4
- package/dist/commands/comments.js +24 -24
- package/dist/commands/daily.d.ts +0 -1
- package/dist/commands/daily.js +18 -16
- package/dist/commands/dashboard-components.d.ts +33 -0
- package/dist/commands/dashboard-components.js +57 -0
- package/dist/commands/dashboard-data.js +7 -6
- 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-server.js +3 -2
- 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/dashboard.js +2 -1
- package/dist/commands/dismiss.d.ts +6 -6
- package/dist/commands/dismiss.js +13 -13
- package/dist/commands/local-repos.js +2 -1
- package/dist/commands/parse-list.js +2 -1
- package/dist/commands/startup.js +6 -16
- package/dist/commands/validation.d.ts +3 -1
- package/dist/commands/validation.js +12 -6
- package/dist/core/errors.d.ts +9 -0
- package/dist/core/errors.js +17 -0
- package/dist/core/github-stats.d.ts +14 -21
- package/dist/core/github-stats.js +84 -138
- package/dist/core/http-cache.d.ts +6 -0
- package/dist/core/http-cache.js +16 -4
- package/dist/core/index.d.ts +3 -2
- package/dist/core/index.js +3 -2
- package/dist/core/issue-conversation.js +4 -4
- package/dist/core/issue-discovery.d.ts +5 -0
- package/dist/core/issue-discovery.js +70 -93
- package/dist/core/issue-vetting.js +17 -17
- package/dist/core/logger.d.ts +5 -0
- package/dist/core/logger.js +8 -0
- package/dist/core/pr-monitor.d.ts +6 -20
- package/dist/core/pr-monitor.js +16 -52
- package/dist/core/review-analysis.js +8 -6
- package/dist/core/state.js +4 -5
- package/dist/core/test-utils.d.ts +14 -0
- package/dist/core/test-utils.js +125 -0
- package/dist/core/utils.d.ts +11 -0
- package/dist/core/utils.js +21 -0
- package/dist/formatters/json.d.ts +0 -1
- package/package.json +1 -1
package/dist/cli.bundle.cjs
CHANGED
|
@@ -1196,7 +1196,7 @@ var require_command = __commonJS({
|
|
|
1196
1196
|
"../../node_modules/.pnpm/commander@14.0.3/node_modules/commander/lib/command.js"(exports2) {
|
|
1197
1197
|
var EventEmitter = require("node:events").EventEmitter;
|
|
1198
1198
|
var childProcess = require("node:child_process");
|
|
1199
|
-
var
|
|
1199
|
+
var path10 = require("node:path");
|
|
1200
1200
|
var fs10 = require("node:fs");
|
|
1201
1201
|
var process2 = require("node:process");
|
|
1202
1202
|
var { Argument: Argument2, humanReadableArgName } = require_argument();
|
|
@@ -2209,9 +2209,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2209
2209
|
let launchWithNode = false;
|
|
2210
2210
|
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
2211
2211
|
function findFile(baseDir, baseName) {
|
|
2212
|
-
const localBin =
|
|
2212
|
+
const localBin = path10.resolve(baseDir, baseName);
|
|
2213
2213
|
if (fs10.existsSync(localBin)) return localBin;
|
|
2214
|
-
if (sourceExt.includes(
|
|
2214
|
+
if (sourceExt.includes(path10.extname(baseName))) return void 0;
|
|
2215
2215
|
const foundExt = sourceExt.find(
|
|
2216
2216
|
(ext) => fs10.existsSync(`${localBin}${ext}`)
|
|
2217
2217
|
);
|
|
@@ -2229,17 +2229,17 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2229
2229
|
} catch {
|
|
2230
2230
|
resolvedScriptPath = this._scriptPath;
|
|
2231
2231
|
}
|
|
2232
|
-
executableDir =
|
|
2233
|
-
|
|
2232
|
+
executableDir = path10.resolve(
|
|
2233
|
+
path10.dirname(resolvedScriptPath),
|
|
2234
2234
|
executableDir
|
|
2235
2235
|
);
|
|
2236
2236
|
}
|
|
2237
2237
|
if (executableDir) {
|
|
2238
2238
|
let localFile = findFile(executableDir, executableFile);
|
|
2239
2239
|
if (!localFile && !subcommand._executableFile && this._scriptPath) {
|
|
2240
|
-
const legacyName =
|
|
2240
|
+
const legacyName = path10.basename(
|
|
2241
2241
|
this._scriptPath,
|
|
2242
|
-
|
|
2242
|
+
path10.extname(this._scriptPath)
|
|
2243
2243
|
);
|
|
2244
2244
|
if (legacyName !== this._name) {
|
|
2245
2245
|
localFile = findFile(
|
|
@@ -2250,7 +2250,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2250
2250
|
}
|
|
2251
2251
|
executableFile = localFile || executableFile;
|
|
2252
2252
|
}
|
|
2253
|
-
launchWithNode = sourceExt.includes(
|
|
2253
|
+
launchWithNode = sourceExt.includes(path10.extname(executableFile));
|
|
2254
2254
|
let proc;
|
|
2255
2255
|
if (process2.platform !== "win32") {
|
|
2256
2256
|
if (launchWithNode) {
|
|
@@ -3165,7 +3165,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3165
3165
|
* @return {Command}
|
|
3166
3166
|
*/
|
|
3167
3167
|
nameFromFilename(filename) {
|
|
3168
|
-
this._name =
|
|
3168
|
+
this._name = path10.basename(filename, path10.extname(filename));
|
|
3169
3169
|
return this;
|
|
3170
3170
|
}
|
|
3171
3171
|
/**
|
|
@@ -3179,9 +3179,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3179
3179
|
* @param {string} [path]
|
|
3180
3180
|
* @return {(string|null|Command)}
|
|
3181
3181
|
*/
|
|
3182
|
-
executableDir(
|
|
3183
|
-
if (
|
|
3184
|
-
this._executableDir =
|
|
3182
|
+
executableDir(path11) {
|
|
3183
|
+
if (path11 === void 0) return this._executableDir;
|
|
3184
|
+
this._executableDir = path11;
|
|
3185
3185
|
return this;
|
|
3186
3186
|
}
|
|
3187
3187
|
/**
|
|
@@ -3498,6 +3498,16 @@ var init_types = __esm({
|
|
|
3498
3498
|
});
|
|
3499
3499
|
|
|
3500
3500
|
// src/core/errors.ts
|
|
3501
|
+
function errorMessage(e) {
|
|
3502
|
+
return e instanceof Error ? e.message : String(e);
|
|
3503
|
+
}
|
|
3504
|
+
function getHttpStatusCode(error) {
|
|
3505
|
+
if (error && typeof error === "object" && "status" in error) {
|
|
3506
|
+
const status = error.status;
|
|
3507
|
+
return typeof status === "number" && Number.isFinite(status) ? status : void 0;
|
|
3508
|
+
}
|
|
3509
|
+
return void 0;
|
|
3510
|
+
}
|
|
3501
3511
|
var OssAutopilotError, ConfigurationError, ValidationError;
|
|
3502
3512
|
var init_errors = __esm({
|
|
3503
3513
|
"src/core/errors.ts"() {
|
|
@@ -3533,6 +3543,10 @@ function debug(module2, message, ...args) {
|
|
|
3533
3543
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
3534
3544
|
console.error(`[${timestamp}] [DEBUG] [${module2}] ${message}`, ...args);
|
|
3535
3545
|
}
|
|
3546
|
+
function info(module2, message, ...args) {
|
|
3547
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
3548
|
+
console.error(`[${timestamp}] [INFO] [${module2}] ${message}`, ...args);
|
|
3549
|
+
}
|
|
3536
3550
|
function warn(module2, message, ...args) {
|
|
3537
3551
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
3538
3552
|
console.error(`[${timestamp}] [WARN] [${module2}] ${message}`, ...args);
|
|
@@ -3640,6 +3654,17 @@ function splitRepo(repoFullName) {
|
|
|
3640
3654
|
const [owner, repo] = repoFullName.split("/");
|
|
3641
3655
|
return { owner, repo };
|
|
3642
3656
|
}
|
|
3657
|
+
function isOwnRepo(owner, username) {
|
|
3658
|
+
return owner.toLowerCase() === username.toLowerCase();
|
|
3659
|
+
}
|
|
3660
|
+
function getCLIVersion() {
|
|
3661
|
+
try {
|
|
3662
|
+
const pkgPath = path.join(path.dirname(process.argv[1]), "..", "package.json");
|
|
3663
|
+
return JSON.parse(fs.readFileSync(pkgPath, "utf-8")).version;
|
|
3664
|
+
} catch {
|
|
3665
|
+
return "0.0.0";
|
|
3666
|
+
}
|
|
3667
|
+
}
|
|
3643
3668
|
function formatRelativeTime(dateStr) {
|
|
3644
3669
|
const date = new Date(dateStr);
|
|
3645
3670
|
const diffMs = Date.now() - date.getTime();
|
|
@@ -3960,8 +3985,7 @@ var init_state = __esm({
|
|
|
3960
3985
|
debug(MODULE2, "Migration complete!");
|
|
3961
3986
|
return true;
|
|
3962
3987
|
} catch (error) {
|
|
3963
|
-
|
|
3964
|
-
warn(MODULE2, `Failed to migrate state: ${errorMessage}`);
|
|
3988
|
+
warn(MODULE2, `Failed to migrate state: ${errorMessage(error)}`);
|
|
3965
3989
|
const newStatePath2 = getStatePath();
|
|
3966
3990
|
if (fs2.existsSync(newStatePath2) && fs2.existsSync(LEGACY_STATE_FILE)) {
|
|
3967
3991
|
try {
|
|
@@ -4113,11 +4137,11 @@ var init_state = __esm({
|
|
|
4113
4137
|
try {
|
|
4114
4138
|
fs2.unlinkSync(path2.join(backupDir, file));
|
|
4115
4139
|
} catch (error) {
|
|
4116
|
-
warn(MODULE2, `Could not delete old backup ${file}:`, error
|
|
4140
|
+
warn(MODULE2, `Could not delete old backup ${file}:`, errorMessage(error));
|
|
4117
4141
|
}
|
|
4118
4142
|
}
|
|
4119
4143
|
} catch (error) {
|
|
4120
|
-
warn(MODULE2, "Could not clean up backups:", error
|
|
4144
|
+
warn(MODULE2, "Could not clean up backups:", errorMessage(error));
|
|
4121
4145
|
}
|
|
4122
4146
|
}
|
|
4123
4147
|
/**
|
|
@@ -4445,11 +4469,11 @@ var init_state = __esm({
|
|
|
4445
4469
|
* @returns true if the PR is snoozed and the snooze has not expired.
|
|
4446
4470
|
*/
|
|
4447
4471
|
isSnoozed(url) {
|
|
4448
|
-
const
|
|
4449
|
-
if (!
|
|
4450
|
-
const expiresAtMs = new Date(
|
|
4472
|
+
const info2 = this.getSnoozeInfo(url);
|
|
4473
|
+
if (!info2) return false;
|
|
4474
|
+
const expiresAtMs = new Date(info2.expiresAt).getTime();
|
|
4451
4475
|
if (isNaN(expiresAtMs)) {
|
|
4452
|
-
warn(MODULE2, `Invalid expiresAt for snoozed PR ${url}: "${
|
|
4476
|
+
warn(MODULE2, `Invalid expiresAt for snoozed PR ${url}: "${info2.expiresAt}". Treating as not snoozed.`);
|
|
4453
4477
|
return false;
|
|
4454
4478
|
}
|
|
4455
4479
|
return expiresAtMs > Date.now();
|
|
@@ -4470,8 +4494,8 @@ var init_state = __esm({
|
|
|
4470
4494
|
if (!this.state.config.snoozedPRs) return [];
|
|
4471
4495
|
const expired = [];
|
|
4472
4496
|
const now = Date.now();
|
|
4473
|
-
for (const [url,
|
|
4474
|
-
const expiresAtMs = new Date(
|
|
4497
|
+
for (const [url, info2] of Object.entries(this.state.config.snoozedPRs)) {
|
|
4498
|
+
const expiresAtMs = new Date(info2.expiresAt).getTime();
|
|
4475
4499
|
if (isNaN(expiresAtMs) || expiresAtMs <= now) {
|
|
4476
4500
|
expired.push(url);
|
|
4477
4501
|
}
|
|
@@ -5900,17 +5924,17 @@ function requestLog(octokit) {
|
|
|
5900
5924
|
octokit.log.debug("request", options);
|
|
5901
5925
|
const start = Date.now();
|
|
5902
5926
|
const requestOptions = octokit.request.endpoint.parse(options);
|
|
5903
|
-
const
|
|
5927
|
+
const path10 = requestOptions.url.replace(options.baseUrl, "");
|
|
5904
5928
|
return request2(options).then((response) => {
|
|
5905
5929
|
const requestId = response.headers["x-github-request-id"];
|
|
5906
5930
|
octokit.log.info(
|
|
5907
|
-
`${requestOptions.method} ${
|
|
5931
|
+
`${requestOptions.method} ${path10} - ${response.status} with id ${requestId} in ${Date.now() - start}ms`
|
|
5908
5932
|
);
|
|
5909
5933
|
return response;
|
|
5910
5934
|
}).catch((error) => {
|
|
5911
5935
|
const requestId = error.response?.headers["x-github-request-id"] || "UNKNOWN";
|
|
5912
5936
|
octokit.log.error(
|
|
5913
|
-
`${requestOptions.method} ${
|
|
5937
|
+
`${requestOptions.method} ${path10} - ${error.status} with id ${requestId} in ${Date.now() - start}ms`
|
|
5914
5938
|
);
|
|
5915
5939
|
throw error;
|
|
5916
5940
|
});
|
|
@@ -9891,7 +9915,7 @@ function isAuthRequest(method, pathname) {
|
|
|
9891
9915
|
}
|
|
9892
9916
|
function routeMatcher(paths) {
|
|
9893
9917
|
const regexes = paths.map(
|
|
9894
|
-
(
|
|
9918
|
+
(path10) => path10.split("/").map((c) => c.startsWith("{") ? "(?:.+?)" : c).join("/")
|
|
9895
9919
|
);
|
|
9896
9920
|
const regex2 = `^(?:${regexes.map((r) => `(?:${r})`).join("|")})[^/]*$`;
|
|
9897
9921
|
return new RegExp(regex2, "i");
|
|
@@ -9948,8 +9972,8 @@ function throttling(octokit, octokitOptions) {
|
|
|
9948
9972
|
"error",
|
|
9949
9973
|
(e) => octokit.log.warn("Error in throttling-plugin limit handler", e)
|
|
9950
9974
|
);
|
|
9951
|
-
state.retryLimiter.on("failed", async function(error,
|
|
9952
|
-
const [state2, request2, options] =
|
|
9975
|
+
state.retryLimiter.on("failed", async function(error, info2) {
|
|
9976
|
+
const [state2, request2, options] = info2.args;
|
|
9953
9977
|
const { pathname } = new URL(options.url, "http://github.test");
|
|
9954
9978
|
const shouldRetryGraphQL = pathname.startsWith("/graphql") && error.status !== 401;
|
|
9955
9979
|
if (!(shouldRetryGraphQL || error.status === 403 || error.status === 429)) {
|
|
@@ -10219,10 +10243,7 @@ async function cachedRequest(cache, url, fetcher) {
|
|
|
10219
10243
|
}
|
|
10220
10244
|
}
|
|
10221
10245
|
function isNotModifiedError(err) {
|
|
10222
|
-
|
|
10223
|
-
return err.status === 304;
|
|
10224
|
-
}
|
|
10225
|
-
return false;
|
|
10246
|
+
return getHttpStatusCode(err) === 304;
|
|
10226
10247
|
}
|
|
10227
10248
|
var fs3, path3, crypto, MODULE4, DEFAULT_MAX_AGE_MS, HttpCache, _httpCache;
|
|
10228
10249
|
var init_http_cache = __esm({
|
|
@@ -10233,6 +10254,7 @@ var init_http_cache = __esm({
|
|
|
10233
10254
|
crypto = __toESM(require("crypto"), 1);
|
|
10234
10255
|
init_utils();
|
|
10235
10256
|
init_logger();
|
|
10257
|
+
init_errors();
|
|
10236
10258
|
MODULE4 = "http-cache";
|
|
10237
10259
|
DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
10238
10260
|
HttpCache = class {
|
|
@@ -10250,6 +10272,18 @@ var init_http_cache = __esm({
|
|
|
10250
10272
|
pathFor(url) {
|
|
10251
10273
|
return path3.join(this.cacheDir, `${this.keyFor(url)}.json`);
|
|
10252
10274
|
}
|
|
10275
|
+
/**
|
|
10276
|
+
* Return the cached body if the entry exists and is younger than `maxAgeMs`.
|
|
10277
|
+
* Useful for time-based caching where ETag validation isn't applicable
|
|
10278
|
+
* (e.g., caching aggregated results from paginated API calls).
|
|
10279
|
+
*/
|
|
10280
|
+
getIfFresh(key, maxAgeMs) {
|
|
10281
|
+
const entry = this.get(key);
|
|
10282
|
+
if (!entry) return null;
|
|
10283
|
+
const age = Date.now() - new Date(entry.cachedAt).getTime();
|
|
10284
|
+
if (!Number.isFinite(age) || age < 0 || age > maxAgeMs) return null;
|
|
10285
|
+
return entry.body;
|
|
10286
|
+
}
|
|
10253
10287
|
/**
|
|
10254
10288
|
* Look up a cached response. Returns `null` if no cache entry exists.
|
|
10255
10289
|
*/
|
|
@@ -10587,14 +10621,14 @@ function checkUnrespondedComments(comments, reviews, reviewComments, username) {
|
|
|
10587
10621
|
for (const review of reviews) {
|
|
10588
10622
|
if (!review.submitted_at) continue;
|
|
10589
10623
|
const body = (review.body || "").trim();
|
|
10590
|
-
if (!body && review.state !== "COMMENTED") continue;
|
|
10624
|
+
if (!body && review.state !== "COMMENTED" && review.state !== "CHANGES_REQUESTED") continue;
|
|
10591
10625
|
const author = review.user?.login || "unknown";
|
|
10592
10626
|
if (!body && review.state === "COMMENTED" && review.id != null) {
|
|
10593
10627
|
if (isAllSelfReplies(review.id, reviewComments)) {
|
|
10594
10628
|
continue;
|
|
10595
10629
|
}
|
|
10596
10630
|
}
|
|
10597
|
-
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)");
|
|
10598
10632
|
timeline.push({
|
|
10599
10633
|
author,
|
|
10600
10634
|
body: resolvedBody,
|
|
@@ -10813,11 +10847,28 @@ var init_display_utils = __esm({
|
|
|
10813
10847
|
});
|
|
10814
10848
|
|
|
10815
10849
|
// src/core/github-stats.ts
|
|
10816
|
-
|
|
10850
|
+
function isCachedPRCounts(v) {
|
|
10851
|
+
if (typeof v !== "object" || v === null) return false;
|
|
10852
|
+
const obj = v;
|
|
10853
|
+
return Array.isArray(obj.reposEntries) && typeof obj.monthlyCounts === "object" && obj.monthlyCounts !== null && typeof obj.monthlyOpenedCounts === "object" && obj.monthlyOpenedCounts !== null && typeof obj.dailyActivityCounts === "object" && obj.dailyActivityCounts !== null;
|
|
10854
|
+
}
|
|
10855
|
+
async function fetchUserPRCounts(octokit, githubUsername, query, label, accumulateRepo) {
|
|
10817
10856
|
if (!githubUsername) {
|
|
10818
10857
|
return { repos: /* @__PURE__ */ new Map(), monthlyCounts: {}, monthlyOpenedCounts: {}, dailyActivityCounts: {} };
|
|
10819
10858
|
}
|
|
10820
|
-
|
|
10859
|
+
const cache = getHttpCache();
|
|
10860
|
+
const cacheKey = `pr-counts:${label}:${githubUsername}`;
|
|
10861
|
+
const cached = cache.getIfFresh(cacheKey, PR_COUNTS_CACHE_TTL_MS);
|
|
10862
|
+
if (cached && isCachedPRCounts(cached)) {
|
|
10863
|
+
debug(MODULE6, `Using cached ${label} PR counts for @${githubUsername}`);
|
|
10864
|
+
return {
|
|
10865
|
+
repos: new Map(cached.reposEntries),
|
|
10866
|
+
monthlyCounts: cached.monthlyCounts,
|
|
10867
|
+
monthlyOpenedCounts: cached.monthlyOpenedCounts,
|
|
10868
|
+
dailyActivityCounts: cached.dailyActivityCounts
|
|
10869
|
+
};
|
|
10870
|
+
}
|
|
10871
|
+
debug(MODULE6, `Fetching ${label} PR counts for @${githubUsername}...`);
|
|
10821
10872
|
const repos = /* @__PURE__ */ new Map();
|
|
10822
10873
|
const monthlyCounts = {};
|
|
10823
10874
|
const monthlyOpenedCounts = {};
|
|
@@ -10826,7 +10877,7 @@ async function fetchUserMergedPRCounts(octokit, githubUsername) {
|
|
|
10826
10877
|
let fetched = 0;
|
|
10827
10878
|
while (true) {
|
|
10828
10879
|
const { data } = await octokit.search.issuesAndPullRequests({
|
|
10829
|
-
q: `is:pr
|
|
10880
|
+
q: `is:pr ${query} author:${githubUsername}`,
|
|
10830
10881
|
sort: "updated",
|
|
10831
10882
|
order: "desc",
|
|
10832
10883
|
per_page: 100,
|
|
@@ -10835,25 +10886,18 @@ async function fetchUserMergedPRCounts(octokit, githubUsername) {
|
|
|
10835
10886
|
for (const item of data.items) {
|
|
10836
10887
|
const parsed = extractOwnerRepo(item.html_url);
|
|
10837
10888
|
if (!parsed) {
|
|
10838
|
-
warn(MODULE6, `Skipping
|
|
10889
|
+
warn(MODULE6, `Skipping ${label} PR with unparseable URL: ${item.html_url}`);
|
|
10839
10890
|
continue;
|
|
10840
10891
|
}
|
|
10841
10892
|
const { owner } = parsed;
|
|
10842
10893
|
const repo = `${owner}/${parsed.repo}`;
|
|
10843
|
-
if (owner
|
|
10844
|
-
const
|
|
10845
|
-
|
|
10846
|
-
|
|
10847
|
-
existing.count += 1;
|
|
10848
|
-
if (mergedAt && mergedAt > existing.lastMergedAt) {
|
|
10849
|
-
existing.lastMergedAt = mergedAt;
|
|
10850
|
-
}
|
|
10851
|
-
} else {
|
|
10852
|
-
repos.set(repo, { count: 1, lastMergedAt: mergedAt });
|
|
10853
|
-
}
|
|
10854
|
-
if (mergedAt) {
|
|
10855
|
-
const month = mergedAt.slice(0, 7);
|
|
10894
|
+
if (isOwnRepo(owner, githubUsername)) continue;
|
|
10895
|
+
const primaryDate = accumulateRepo(repos, repo, item);
|
|
10896
|
+
if (primaryDate) {
|
|
10897
|
+
const month = primaryDate.slice(0, 7);
|
|
10856
10898
|
monthlyCounts[month] = (monthlyCounts[month] || 0) + 1;
|
|
10899
|
+
const day = primaryDate.slice(0, 10);
|
|
10900
|
+
if (day.length === 10) dailyActivityCounts[day] = (dailyActivityCounts[day] || 0) + 1;
|
|
10857
10901
|
}
|
|
10858
10902
|
if (item.created_at) {
|
|
10859
10903
|
const openedMonth = item.created_at.slice(0, 7);
|
|
@@ -10861,10 +10905,6 @@ async function fetchUserMergedPRCounts(octokit, githubUsername) {
|
|
|
10861
10905
|
const openedDay = item.created_at.slice(0, 10);
|
|
10862
10906
|
if (openedDay.length === 10) dailyActivityCounts[openedDay] = (dailyActivityCounts[openedDay] || 0) + 1;
|
|
10863
10907
|
}
|
|
10864
|
-
if (mergedAt) {
|
|
10865
|
-
const mergedDay = mergedAt.slice(0, 10);
|
|
10866
|
-
if (mergedDay.length === 10) dailyActivityCounts[mergedDay] = (dailyActivityCounts[mergedDay] || 0) + 1;
|
|
10867
|
-
}
|
|
10868
10908
|
}
|
|
10869
10909
|
fetched += data.items.length;
|
|
10870
10910
|
if (fetched >= data.total_count || fetched >= 1e3 || data.items.length === 0) {
|
|
@@ -10872,59 +10912,41 @@ async function fetchUserMergedPRCounts(octokit, githubUsername) {
|
|
|
10872
10912
|
}
|
|
10873
10913
|
page++;
|
|
10874
10914
|
}
|
|
10875
|
-
debug(MODULE6, `Found ${fetched}
|
|
10915
|
+
debug(MODULE6, `Found ${fetched} ${label} PRs across ${repos.size} repos`);
|
|
10916
|
+
cache.set(cacheKey, "", {
|
|
10917
|
+
reposEntries: Array.from(repos.entries()),
|
|
10918
|
+
monthlyCounts,
|
|
10919
|
+
monthlyOpenedCounts,
|
|
10920
|
+
dailyActivityCounts
|
|
10921
|
+
});
|
|
10876
10922
|
return { repos, monthlyCounts, monthlyOpenedCounts, dailyActivityCounts };
|
|
10877
10923
|
}
|
|
10878
|
-
|
|
10879
|
-
|
|
10880
|
-
|
|
10881
|
-
|
|
10882
|
-
|
|
10883
|
-
|
|
10884
|
-
|
|
10885
|
-
const monthlyOpenedCounts = {};
|
|
10886
|
-
const dailyActivityCounts = {};
|
|
10887
|
-
let page = 1;
|
|
10888
|
-
let fetched = 0;
|
|
10889
|
-
while (true) {
|
|
10890
|
-
const { data } = await octokit.search.issuesAndPullRequests({
|
|
10891
|
-
q: `is:pr is:closed is:unmerged author:${githubUsername}`,
|
|
10892
|
-
sort: "updated",
|
|
10893
|
-
order: "desc",
|
|
10894
|
-
per_page: 100,
|
|
10895
|
-
page
|
|
10896
|
-
});
|
|
10897
|
-
for (const item of data.items) {
|
|
10898
|
-
const parsed = extractOwnerRepo(item.html_url);
|
|
10899
|
-
if (!parsed) {
|
|
10900
|
-
warn(MODULE6, `Skipping closed PR with unparseable URL: ${item.html_url}`);
|
|
10901
|
-
continue;
|
|
10902
|
-
}
|
|
10903
|
-
const { owner } = parsed;
|
|
10904
|
-
const repo = `${owner}/${parsed.repo}`;
|
|
10905
|
-
if (owner.toLowerCase() === githubUsername.toLowerCase()) continue;
|
|
10906
|
-
repos.set(repo, (repos.get(repo) || 0) + 1);
|
|
10907
|
-
if (item.closed_at) {
|
|
10908
|
-
const closedMonth = item.closed_at.slice(0, 7);
|
|
10909
|
-
monthlyCounts[closedMonth] = (monthlyCounts[closedMonth] || 0) + 1;
|
|
10910
|
-
const closedDay = item.closed_at.slice(0, 10);
|
|
10911
|
-
if (closedDay.length === 10) dailyActivityCounts[closedDay] = (dailyActivityCounts[closedDay] || 0) + 1;
|
|
10912
|
-
}
|
|
10913
|
-
if (item.created_at) {
|
|
10914
|
-
const openedMonth = item.created_at.slice(0, 7);
|
|
10915
|
-
monthlyOpenedCounts[openedMonth] = (monthlyOpenedCounts[openedMonth] || 0) + 1;
|
|
10916
|
-
const openedDay = item.created_at.slice(0, 10);
|
|
10917
|
-
if (openedDay.length === 10) dailyActivityCounts[openedDay] = (dailyActivityCounts[openedDay] || 0) + 1;
|
|
10918
|
-
}
|
|
10924
|
+
function fetchUserMergedPRCounts(octokit, githubUsername) {
|
|
10925
|
+
return fetchUserPRCounts(octokit, githubUsername, "is:merged", "merged", (repos, repo, item) => {
|
|
10926
|
+
if (!item.pull_request?.merged_at) {
|
|
10927
|
+
warn(
|
|
10928
|
+
MODULE6,
|
|
10929
|
+
`merged_at missing for merged PR ${item.html_url}${item.closed_at ? ", falling back to closed_at" : ", no date available"}`
|
|
10930
|
+
);
|
|
10919
10931
|
}
|
|
10920
|
-
|
|
10921
|
-
|
|
10922
|
-
|
|
10932
|
+
const mergedAt = item.pull_request?.merged_at || item.closed_at || "";
|
|
10933
|
+
const existing = repos.get(repo);
|
|
10934
|
+
if (existing) {
|
|
10935
|
+
existing.count += 1;
|
|
10936
|
+
if (mergedAt && mergedAt > existing.lastMergedAt) {
|
|
10937
|
+
existing.lastMergedAt = mergedAt;
|
|
10938
|
+
}
|
|
10939
|
+
} else {
|
|
10940
|
+
repos.set(repo, { count: 1, lastMergedAt: mergedAt });
|
|
10923
10941
|
}
|
|
10924
|
-
|
|
10925
|
-
}
|
|
10926
|
-
|
|
10927
|
-
|
|
10942
|
+
return mergedAt;
|
|
10943
|
+
});
|
|
10944
|
+
}
|
|
10945
|
+
function fetchUserClosedPRCounts(octokit, githubUsername) {
|
|
10946
|
+
return fetchUserPRCounts(octokit, githubUsername, "is:closed is:unmerged", "closed", (repos, repo, item) => {
|
|
10947
|
+
repos.set(repo, (repos.get(repo) || 0) + 1);
|
|
10948
|
+
return item.closed_at || "";
|
|
10949
|
+
});
|
|
10928
10950
|
}
|
|
10929
10951
|
async function fetchRecentPRs(octokit, config, query, label, days, mapItem) {
|
|
10930
10952
|
if (!config.githubUsername) {
|
|
@@ -10949,7 +10971,7 @@ async function fetchRecentPRs(octokit, config, query, label, days, mapItem) {
|
|
|
10949
10971
|
continue;
|
|
10950
10972
|
}
|
|
10951
10973
|
const repo = `${parsed.owner}/${parsed.repo}`;
|
|
10952
|
-
if (parsed.owner
|
|
10974
|
+
if (isOwnRepo(parsed.owner, config.githubUsername)) continue;
|
|
10953
10975
|
if (config.excludeRepos.includes(repo)) continue;
|
|
10954
10976
|
if (config.excludeOrgs?.some((org) => parsed.owner.toLowerCase() === org.toLowerCase())) continue;
|
|
10955
10977
|
results.push(mapItem(item, { owner: parsed.owner, repo, number: parsed.number }));
|
|
@@ -10998,14 +11020,15 @@ async function fetchRecentlyMergedPRs(octokit, config, days = 7) {
|
|
|
10998
11020
|
}
|
|
10999
11021
|
);
|
|
11000
11022
|
}
|
|
11001
|
-
var MODULE6;
|
|
11023
|
+
var MODULE6, PR_COUNTS_CACHE_TTL_MS;
|
|
11002
11024
|
var init_github_stats = __esm({
|
|
11003
11025
|
"src/core/github-stats.ts"() {
|
|
11004
11026
|
"use strict";
|
|
11005
11027
|
init_utils();
|
|
11006
|
-
init_errors();
|
|
11007
11028
|
init_logger();
|
|
11029
|
+
init_http_cache();
|
|
11008
11030
|
MODULE6 = "github-stats";
|
|
11031
|
+
PR_COUNTS_CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
11009
11032
|
}
|
|
11010
11033
|
});
|
|
11011
11034
|
|
|
@@ -11106,9 +11129,9 @@ var init_pr_monitor = __esm({
|
|
|
11106
11129
|
const pr = await this.fetchPRDetails(item.html_url);
|
|
11107
11130
|
if (pr) prs.push(pr);
|
|
11108
11131
|
} catch (error) {
|
|
11109
|
-
const
|
|
11110
|
-
warn("pr-monitor", `Error fetching ${item.html_url}: ${
|
|
11111
|
-
failures.push({ prUrl: item.html_url, error:
|
|
11132
|
+
const errMsg = errorMessage(error);
|
|
11133
|
+
warn("pr-monitor", `Error fetching ${item.html_url}: ${errMsg}`);
|
|
11134
|
+
failures.push({ prUrl: item.html_url, error: errMsg });
|
|
11112
11135
|
}
|
|
11113
11136
|
},
|
|
11114
11137
|
MAX_CONCURRENT_REQUESTS
|
|
@@ -11155,12 +11178,12 @@ var init_pr_monitor = __esm({
|
|
|
11155
11178
|
paginateAll(
|
|
11156
11179
|
(page) => this.octokit.pulls.listReviewComments({ owner, repo, pull_number: number, per_page: 100, page })
|
|
11157
11180
|
).catch((err) => {
|
|
11158
|
-
const status2 = err
|
|
11181
|
+
const status2 = getHttpStatusCode(err);
|
|
11159
11182
|
if (status2 === 429) {
|
|
11160
11183
|
throw err;
|
|
11161
11184
|
}
|
|
11162
11185
|
if (status2 === 403) {
|
|
11163
|
-
const msg = (err
|
|
11186
|
+
const msg = errorMessage(err).toLowerCase();
|
|
11164
11187
|
if (msg.includes("rate limit") || msg.includes("abuse detection")) {
|
|
11165
11188
|
throw err;
|
|
11166
11189
|
}
|
|
@@ -11272,6 +11295,9 @@ var init_pr_monitor = __esm({
|
|
|
11272
11295
|
} = input;
|
|
11273
11296
|
if (hasUnrespondedComment) {
|
|
11274
11297
|
if (latestCommitDate && lastMaintainerCommentDate && latestCommitDate > lastMaintainerCommentDate) {
|
|
11298
|
+
if (latestChangesRequestedDate && latestCommitDate < latestChangesRequestedDate) {
|
|
11299
|
+
return "needs_response";
|
|
11300
|
+
}
|
|
11275
11301
|
if (ciStatus === "failing") return "failing_ci";
|
|
11276
11302
|
return "changes_addressed";
|
|
11277
11303
|
}
|
|
@@ -11327,7 +11353,7 @@ var init_pr_monitor = __esm({
|
|
|
11327
11353
|
this.octokit.repos.getCombinedStatusForRef({ owner, repo, ref: sha }),
|
|
11328
11354
|
// 404 is expected for repos without check runs configured; log other errors for debugging
|
|
11329
11355
|
this.octokit.checks.listForRef({ owner, repo, ref: sha }).catch((err) => {
|
|
11330
|
-
const status = err
|
|
11356
|
+
const status = getHttpStatusCode(err);
|
|
11331
11357
|
if (status === 404) {
|
|
11332
11358
|
debug("pr-monitor", `Check runs 404 for ${owner}/${repo}@${sha.slice(0, 7)} (no checks configured)`);
|
|
11333
11359
|
} else {
|
|
@@ -11353,8 +11379,8 @@ var init_pr_monitor = __esm({
|
|
|
11353
11379
|
const combinedAnalysis = analyzeCombinedStatus(combinedStatus);
|
|
11354
11380
|
return mergeStatuses(checkRunAnalysis, combinedAnalysis, checkRuns.length);
|
|
11355
11381
|
} catch (error) {
|
|
11356
|
-
const statusCode = error
|
|
11357
|
-
const
|
|
11382
|
+
const statusCode = getHttpStatusCode(error);
|
|
11383
|
+
const errMsg = errorMessage(error);
|
|
11358
11384
|
if (statusCode === 401) {
|
|
11359
11385
|
warn("pr-monitor", `CI check failed for ${owner}/${repo}: Invalid token`);
|
|
11360
11386
|
} else if (statusCode === 403) {
|
|
@@ -11363,7 +11389,7 @@ var init_pr_monitor = __esm({
|
|
|
11363
11389
|
debug("pr-monitor", `CI check 404 for ${owner}/${repo} (no CI configured)`);
|
|
11364
11390
|
return { status: "unknown", failingCheckNames: [], failingCheckConclusions: /* @__PURE__ */ new Map() };
|
|
11365
11391
|
} else {
|
|
11366
|
-
warn("pr-monitor", `Failed to check CI for ${owner}/${repo}@${sha.slice(0, 7)}: ${
|
|
11392
|
+
warn("pr-monitor", `Failed to check CI for ${owner}/${repo}@${sha.slice(0, 7)}: ${errMsg}`);
|
|
11367
11393
|
}
|
|
11368
11394
|
return { status: "unknown", failingCheckNames: [], failingCheckConclusions: /* @__PURE__ */ new Map() };
|
|
11369
11395
|
}
|
|
@@ -11424,10 +11450,7 @@ var init_pr_monitor = __esm({
|
|
|
11424
11450
|
results.set(result.value.repo, result.value.stars);
|
|
11425
11451
|
} else {
|
|
11426
11452
|
chunkFailures++;
|
|
11427
|
-
warn(
|
|
11428
|
-
MODULE7,
|
|
11429
|
-
`Failed to fetch stars for ${chunk[j]}: ${result.reason instanceof Error ? result.reason.message : result.reason}`
|
|
11430
|
-
);
|
|
11453
|
+
warn(MODULE7, `Failed to fetch stars for ${chunk[j]}: ${errorMessage(result.reason)}`);
|
|
11431
11454
|
}
|
|
11432
11455
|
}
|
|
11433
11456
|
if (chunkFailures === chunk.length && chunk.length > 0) {
|
|
@@ -11441,42 +11464,6 @@ var init_pr_monitor = __esm({
|
|
|
11441
11464
|
debug(MODULE7, `Fetched star counts for ${results.size}/${repos.length} repos`);
|
|
11442
11465
|
return results;
|
|
11443
11466
|
}
|
|
11444
|
-
/**
|
|
11445
|
-
* Shared helper: search for recent PRs and filter out own repos, excluded repos/orgs.
|
|
11446
|
-
* Returns parsed search results that pass all filters.
|
|
11447
|
-
*/
|
|
11448
|
-
async fetchRecentPRs(query, label, days, mapItem) {
|
|
11449
|
-
const config = this.stateManager.getState().config;
|
|
11450
|
-
if (!config.githubUsername) {
|
|
11451
|
-
warn(MODULE7, `Skipping recently ${label} PRs fetch: no githubUsername configured. Run /setup-oss to configure.`);
|
|
11452
|
-
return [];
|
|
11453
|
-
}
|
|
11454
|
-
const sinceDate = /* @__PURE__ */ new Date();
|
|
11455
|
-
sinceDate.setDate(sinceDate.getDate() - days);
|
|
11456
|
-
const since = sinceDate.toISOString().split("T")[0];
|
|
11457
|
-
debug(MODULE7, `Fetching recently ${label} PRs for @${config.githubUsername} (since ${since})...`);
|
|
11458
|
-
const { data } = await this.octokit.search.issuesAndPullRequests({
|
|
11459
|
-
q: query.replace("{username}", config.githubUsername).replace("{since}", since),
|
|
11460
|
-
sort: "updated",
|
|
11461
|
-
order: "desc",
|
|
11462
|
-
per_page: 100
|
|
11463
|
-
});
|
|
11464
|
-
const results = [];
|
|
11465
|
-
for (const item of data.items) {
|
|
11466
|
-
const parsed = parseGitHubUrl(item.html_url);
|
|
11467
|
-
if (!parsed) {
|
|
11468
|
-
warn(MODULE7, `Could not parse GitHub URL from API response: ${item.html_url}`);
|
|
11469
|
-
continue;
|
|
11470
|
-
}
|
|
11471
|
-
const repo = `${parsed.owner}/${parsed.repo}`;
|
|
11472
|
-
if (parsed.owner.toLowerCase() === config.githubUsername.toLowerCase()) continue;
|
|
11473
|
-
if (config.excludeRepos.includes(repo)) continue;
|
|
11474
|
-
if (config.excludeOrgs?.some((org) => parsed.owner.toLowerCase() === org.toLowerCase())) continue;
|
|
11475
|
-
results.push(mapItem(item, { owner: parsed.owner, repo, number: parsed.number }));
|
|
11476
|
-
}
|
|
11477
|
-
debug(MODULE7, `Found ${results.length} recently ${label} PRs`);
|
|
11478
|
-
return results;
|
|
11479
|
-
}
|
|
11480
11467
|
/**
|
|
11481
11468
|
* Fetch PRs closed without merge in the last N days.
|
|
11482
11469
|
* Delegates to github-stats module.
|
|
@@ -11901,7 +11888,7 @@ var init_issue_vetting = __esm({
|
|
|
11901
11888
|
if (_IssueVetter.isRateLimitError(error)) {
|
|
11902
11889
|
rateLimitFailures++;
|
|
11903
11890
|
}
|
|
11904
|
-
warn(MODULE8, `Error vetting issue ${url}:`, error
|
|
11891
|
+
warn(MODULE8, `Error vetting issue ${url}:`, errorMessage(error));
|
|
11905
11892
|
});
|
|
11906
11893
|
pending.push(task);
|
|
11907
11894
|
if (pending.length >= MAX_CONCURRENT_REQUESTS2) {
|
|
@@ -11921,10 +11908,10 @@ var init_issue_vetting = __esm({
|
|
|
11921
11908
|
}
|
|
11922
11909
|
/** Check if an error is a GitHub rate limit error (429 or rate-limit 403). */
|
|
11923
11910
|
static isRateLimitError(error) {
|
|
11924
|
-
const status = error
|
|
11911
|
+
const status = getHttpStatusCode(error);
|
|
11925
11912
|
if (status === 429) return true;
|
|
11926
11913
|
if (status === 403) {
|
|
11927
|
-
const msg =
|
|
11914
|
+
const msg = errorMessage(error).toLowerCase();
|
|
11928
11915
|
return msg.includes("rate limit");
|
|
11929
11916
|
}
|
|
11930
11917
|
return false;
|
|
@@ -11950,12 +11937,12 @@ var init_issue_vetting = __esm({
|
|
|
11950
11937
|
});
|
|
11951
11938
|
return { passed: data.total_count === 0 && linkedPRs.length === 0 };
|
|
11952
11939
|
} catch (error) {
|
|
11953
|
-
const
|
|
11940
|
+
const errMsg = errorMessage(error);
|
|
11954
11941
|
warn(
|
|
11955
11942
|
MODULE8,
|
|
11956
|
-
`Failed to check for existing PRs on ${owner}/${repo}#${issueNumber}: ${
|
|
11943
|
+
`Failed to check for existing PRs on ${owner}/${repo}#${issueNumber}: ${errMsg}. Assuming no existing PR.`
|
|
11957
11944
|
);
|
|
11958
|
-
return { passed: true, inconclusive: true, reason:
|
|
11945
|
+
return { passed: true, inconclusive: true, reason: errMsg };
|
|
11959
11946
|
}
|
|
11960
11947
|
}
|
|
11961
11948
|
/**
|
|
@@ -11971,8 +11958,8 @@ var init_issue_vetting = __esm({
|
|
|
11971
11958
|
});
|
|
11972
11959
|
return data.total_count;
|
|
11973
11960
|
} catch (error) {
|
|
11974
|
-
const
|
|
11975
|
-
warn(MODULE8, `Could not check merged PRs in ${owner}/${repo}: ${
|
|
11961
|
+
const errMsg = errorMessage(error);
|
|
11962
|
+
warn(MODULE8, `Could not check merged PRs in ${owner}/${repo}: ${errMsg}. Defaulting to 0.`);
|
|
11976
11963
|
return 0;
|
|
11977
11964
|
}
|
|
11978
11965
|
}
|
|
@@ -12015,12 +12002,9 @@ var init_issue_vetting = __esm({
|
|
|
12015
12002
|
}
|
|
12016
12003
|
return { passed: true };
|
|
12017
12004
|
} catch (error) {
|
|
12018
|
-
const
|
|
12019
|
-
warn(
|
|
12020
|
-
|
|
12021
|
-
`Failed to check claim status on ${owner}/${repo}#${issueNumber}: ${errorMessage}. Assuming not claimed.`
|
|
12022
|
-
);
|
|
12023
|
-
return { passed: true, inconclusive: true, reason: errorMessage };
|
|
12005
|
+
const errMsg = errorMessage(error);
|
|
12006
|
+
warn(MODULE8, `Failed to check claim status on ${owner}/${repo}#${issueNumber}: ${errMsg}. Assuming not claimed.`);
|
|
12007
|
+
return { passed: true, inconclusive: true, reason: errMsg };
|
|
12024
12008
|
}
|
|
12025
12009
|
}
|
|
12026
12010
|
async checkProjectHealth(owner, repo) {
|
|
@@ -12051,8 +12035,8 @@ var init_issue_vetting = __esm({
|
|
|
12051
12035
|
ciStatus = "passing";
|
|
12052
12036
|
}
|
|
12053
12037
|
} catch (error) {
|
|
12054
|
-
const
|
|
12055
|
-
warn(MODULE8, `Failed to check CI status for ${owner}/${repo}: ${
|
|
12038
|
+
const errMsg = errorMessage(error);
|
|
12039
|
+
warn(MODULE8, `Failed to check CI status for ${owner}/${repo}: ${errMsg}. Defaulting to unknown.`);
|
|
12056
12040
|
}
|
|
12057
12041
|
return {
|
|
12058
12042
|
repo: `${owner}/${repo}`,
|
|
@@ -12067,8 +12051,8 @@ var init_issue_vetting = __esm({
|
|
|
12067
12051
|
forksCount: repoData.forks_count
|
|
12068
12052
|
};
|
|
12069
12053
|
} catch (error) {
|
|
12070
|
-
const
|
|
12071
|
-
warn(MODULE8, `Error checking project health for ${owner}/${repo}: ${
|
|
12054
|
+
const errMsg = errorMessage(error);
|
|
12055
|
+
warn(MODULE8, `Error checking project health for ${owner}/${repo}: ${errMsg}`);
|
|
12072
12056
|
return {
|
|
12073
12057
|
repo: `${owner}/${repo}`,
|
|
12074
12058
|
lastCommitAt: "",
|
|
@@ -12078,7 +12062,7 @@ var init_issue_vetting = __esm({
|
|
|
12078
12062
|
ciStatus: "unknown",
|
|
12079
12063
|
isActive: false,
|
|
12080
12064
|
checkFailed: true,
|
|
12081
|
-
failureReason:
|
|
12065
|
+
failureReason: errMsg
|
|
12082
12066
|
};
|
|
12083
12067
|
}
|
|
12084
12068
|
}
|
|
@@ -12201,7 +12185,7 @@ var init_issue_discovery = __esm({
|
|
|
12201
12185
|
* Updates the state manager with the list and timestamp.
|
|
12202
12186
|
*/
|
|
12203
12187
|
async fetchStarredRepos() {
|
|
12204
|
-
|
|
12188
|
+
info(MODULE9, "Fetching starred repositories...");
|
|
12205
12189
|
const starredRepos = [];
|
|
12206
12190
|
try {
|
|
12207
12191
|
const iterator2 = this.octokit.paginate.iterator(this.octokit.activity.listReposStarredByAuthenticatedUser, {
|
|
@@ -12222,27 +12206,27 @@ var init_issue_discovery = __esm({
|
|
|
12222
12206
|
}
|
|
12223
12207
|
pageCount++;
|
|
12224
12208
|
if (pageCount >= 5) {
|
|
12225
|
-
|
|
12209
|
+
info(MODULE9, "Reached pagination limit for starred repos (500)");
|
|
12226
12210
|
break;
|
|
12227
12211
|
}
|
|
12228
12212
|
}
|
|
12229
|
-
|
|
12213
|
+
info(MODULE9, `Fetched ${starredRepos.length} starred repositories`);
|
|
12230
12214
|
this.stateManager.setStarredRepos(starredRepos);
|
|
12231
12215
|
return starredRepos;
|
|
12232
12216
|
} catch (error) {
|
|
12233
12217
|
const cachedRepos = this.stateManager.getStarredRepos();
|
|
12234
|
-
const
|
|
12235
|
-
warn(MODULE9, "Error fetching starred repos:",
|
|
12218
|
+
const errMsg = errorMessage(error);
|
|
12219
|
+
warn(MODULE9, "Error fetching starred repos:", errMsg);
|
|
12236
12220
|
if (cachedRepos.length === 0) {
|
|
12237
12221
|
warn(
|
|
12238
12222
|
MODULE9,
|
|
12239
|
-
`Failed to fetch starred repositories from GitHub API. No cached repos available. Error: ${
|
|
12223
|
+
`Failed to fetch starred repositories from GitHub API. No cached repos available. Error: ${errMsg}
|
|
12240
12224
|
Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
|
|
12241
12225
|
);
|
|
12242
12226
|
} else {
|
|
12243
12227
|
warn(
|
|
12244
12228
|
MODULE9,
|
|
12245
|
-
`Failed to fetch starred repositories from GitHub API. Using ${cachedRepos.length} cached repos instead. Error: ${
|
|
12229
|
+
`Failed to fetch starred repositories from GitHub API. Using ${cachedRepos.length} cached repos instead. Error: ${errMsg}`
|
|
12246
12230
|
);
|
|
12247
12231
|
}
|
|
12248
12232
|
return cachedRepos;
|
|
@@ -12257,6 +12241,48 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
|
|
|
12257
12241
|
}
|
|
12258
12242
|
return this.stateManager.getStarredRepos();
|
|
12259
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
|
+
}
|
|
12260
12286
|
/**
|
|
12261
12287
|
* Search for issues matching our criteria.
|
|
12262
12288
|
* Searches in priority order: merged-PR repos first (no label filter), then starred repos,
|
|
@@ -12268,6 +12294,7 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
|
|
|
12268
12294
|
const languages = options.languages || config.languages;
|
|
12269
12295
|
const labels = options.labels || config.labels;
|
|
12270
12296
|
const maxResults = options.maxResults || 10;
|
|
12297
|
+
const minStars = config.minStars ?? 50;
|
|
12271
12298
|
const allCandidates = [];
|
|
12272
12299
|
let phase0Error = null;
|
|
12273
12300
|
let phase1Error = null;
|
|
@@ -12281,10 +12308,10 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
|
|
|
12281
12308
|
warn(MODULE9, this.rateLimitWarning);
|
|
12282
12309
|
}
|
|
12283
12310
|
} catch (error) {
|
|
12284
|
-
if (error
|
|
12311
|
+
if (getHttpStatusCode(error) === 401) {
|
|
12285
12312
|
throw error;
|
|
12286
12313
|
}
|
|
12287
|
-
warn(MODULE9, "Could not check rate limit:", error
|
|
12314
|
+
warn(MODULE9, "Could not check rate limit:", errorMessage(error));
|
|
12288
12315
|
}
|
|
12289
12316
|
const mergedPRRepos = this.stateManager.getReposWithMergedPRs();
|
|
12290
12317
|
const mergedPRRepoSet = new Set(mergedPRRepos);
|
|
@@ -12304,7 +12331,8 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
|
|
|
12304
12331
|
const includeDocIssues = config.includeDocIssues ?? true;
|
|
12305
12332
|
const aiBlocklisted = new Set(config.aiPolicyBlocklist ?? DEFAULT_CONFIG.aiPolicyBlocklist ?? []);
|
|
12306
12333
|
if (aiBlocklisted.size > 0) {
|
|
12307
|
-
|
|
12334
|
+
debug(
|
|
12335
|
+
MODULE9,
|
|
12308
12336
|
`[AI_POLICY_FILTER] Filtering issues from ${aiBlocklisted.size} blocklisted repo(s): ${[...aiBlocklisted].join(", ")}`
|
|
12309
12337
|
);
|
|
12310
12338
|
}
|
|
@@ -12327,7 +12355,8 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
|
|
|
12327
12355
|
if (phase0Repos.length > 0) {
|
|
12328
12356
|
const mergedInPhase0 = Math.min(mergedPRRepos.length, phase0Repos.length);
|
|
12329
12357
|
const openInPhase0 = phase0Repos.length - mergedInPhase0;
|
|
12330
|
-
|
|
12358
|
+
info(
|
|
12359
|
+
MODULE9,
|
|
12331
12360
|
`Phase 0: Searching issues in ${phase0Repos.length} repos (${mergedInPhase0} merged-PR, ${openInPhase0} open-PR, no label filter)...`
|
|
12332
12361
|
);
|
|
12333
12362
|
const mergedPhase0Repos = phase0Repos.slice(0, mergedInPhase0);
|
|
@@ -12346,7 +12375,7 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
|
|
|
12346
12375
|
if (rateLimitHit) {
|
|
12347
12376
|
rateLimitHitDuringSearch = true;
|
|
12348
12377
|
}
|
|
12349
|
-
|
|
12378
|
+
info(MODULE9, `Found ${mergedCandidates.length} candidates from merged-PR repos`);
|
|
12350
12379
|
}
|
|
12351
12380
|
}
|
|
12352
12381
|
const openPhase0Repos = phase0Repos.slice(mergedInPhase0);
|
|
@@ -12366,14 +12395,14 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
|
|
|
12366
12395
|
if (rateLimitHit) {
|
|
12367
12396
|
rateLimitHitDuringSearch = true;
|
|
12368
12397
|
}
|
|
12369
|
-
|
|
12398
|
+
info(MODULE9, `Found ${openCandidates.length} candidates from open-PR repos`);
|
|
12370
12399
|
}
|
|
12371
12400
|
}
|
|
12372
12401
|
}
|
|
12373
12402
|
if (allCandidates.length < maxResults && starredRepos.length > 0) {
|
|
12374
12403
|
const reposToSearch = starredRepos.filter((r) => !phase0RepoSet.has(r));
|
|
12375
12404
|
if (reposToSearch.length > 0) {
|
|
12376
|
-
|
|
12405
|
+
info(MODULE9, `Phase 1: Searching issues in ${reposToSearch.length} starred repos...`);
|
|
12377
12406
|
const remainingNeeded = maxResults - allCandidates.length;
|
|
12378
12407
|
if (remainingNeeded > 0) {
|
|
12379
12408
|
const {
|
|
@@ -12388,13 +12417,13 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
|
|
|
12388
12417
|
if (rateLimitHit) {
|
|
12389
12418
|
rateLimitHitDuringSearch = true;
|
|
12390
12419
|
}
|
|
12391
|
-
|
|
12420
|
+
info(MODULE9, `Found ${starredCandidates.length} candidates from starred repos`);
|
|
12392
12421
|
}
|
|
12393
12422
|
}
|
|
12394
12423
|
}
|
|
12395
12424
|
let phase2Error = null;
|
|
12396
12425
|
if (allCandidates.length < maxResults) {
|
|
12397
|
-
|
|
12426
|
+
info(MODULE9, "Phase 2: General issue search...");
|
|
12398
12427
|
const remainingNeeded = maxResults - allCandidates.length;
|
|
12399
12428
|
try {
|
|
12400
12429
|
const { data } = await this.octokit.search.issuesAndPullRequests({
|
|
@@ -12404,43 +12433,20 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
|
|
|
12404
12433
|
per_page: remainingNeeded * 3
|
|
12405
12434
|
// Fetch extra since some will be filtered
|
|
12406
12435
|
});
|
|
12407
|
-
|
|
12408
|
-
const spamRepos = detectLabelFarmingRepos(data.items);
|
|
12409
|
-
if (spamRepos.size > 0) {
|
|
12410
|
-
const spamCount = data.items.filter(
|
|
12411
|
-
(i) => spamRepos.has(i.repository_url.split("/").slice(-2).join("/"))
|
|
12412
|
-
).length;
|
|
12413
|
-
console.log(
|
|
12414
|
-
`[SPAM_FILTER] Filtered ${spamCount} issues from ${spamRepos.size} label-farming repos: ${[...spamRepos].join(", ")}`
|
|
12415
|
-
);
|
|
12416
|
-
}
|
|
12436
|
+
info(MODULE9, `Found ${data.total_count} issues in general search, processing top ${data.items.length}...`);
|
|
12417
12437
|
const seenRepos = new Set(allCandidates.map((c) => c.issue.repo));
|
|
12418
|
-
const itemsToVet = filterIssues(data.items).filter((item) => {
|
|
12419
|
-
const repoFullName = item.repository_url.split("/").slice(-2).join("/");
|
|
12420
|
-
return !spamRepos.has(repoFullName);
|
|
12421
|
-
}).filter((item) => {
|
|
12422
|
-
const repoFullName = item.repository_url.split("/").slice(-2).join("/");
|
|
12423
|
-
return !phase0RepoSet.has(repoFullName) && !starredRepoSet.has(repoFullName) && !seenRepos.has(repoFullName);
|
|
12424
|
-
}).slice(0, remainingNeeded * 2);
|
|
12425
12438
|
const {
|
|
12426
|
-
candidates:
|
|
12427
|
-
|
|
12439
|
+
candidates: starFiltered,
|
|
12440
|
+
allVetFailed,
|
|
12428
12441
|
rateLimitHit: vetRateLimitHit
|
|
12429
|
-
} = await this.
|
|
12430
|
-
|
|
12442
|
+
} = await this.filterVetAndScore(
|
|
12443
|
+
data.items,
|
|
12444
|
+
filterIssues,
|
|
12445
|
+
[phase0RepoSet, starredRepoSet, seenRepos],
|
|
12431
12446
|
remainingNeeded,
|
|
12432
|
-
|
|
12447
|
+
minStars,
|
|
12448
|
+
"Phase 2"
|
|
12433
12449
|
);
|
|
12434
|
-
const minStars = config.minStars ?? 50;
|
|
12435
|
-
const starFiltered = results.filter((c) => {
|
|
12436
|
-
if (c.projectHealth.checkFailed) return true;
|
|
12437
|
-
const stars = c.projectHealth.stargazersCount ?? 0;
|
|
12438
|
-
return stars >= minStars;
|
|
12439
|
-
});
|
|
12440
|
-
const starFilteredCount = results.length - starFiltered.length;
|
|
12441
|
-
if (starFilteredCount > 0) {
|
|
12442
|
-
console.log(`[STAR_FILTER] Filtered ${starFilteredCount} candidates below ${minStars} stars`);
|
|
12443
|
-
}
|
|
12444
12450
|
allCandidates.push(...starFiltered);
|
|
12445
12451
|
if (allVetFailed) {
|
|
12446
12452
|
phase2Error = (phase2Error ? phase2Error + "; " : "") + "all vetting failed";
|
|
@@ -12448,25 +12454,24 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
|
|
|
12448
12454
|
if (vetRateLimitHit) {
|
|
12449
12455
|
rateLimitHitDuringSearch = true;
|
|
12450
12456
|
}
|
|
12451
|
-
|
|
12457
|
+
info(MODULE9, `Found ${starFiltered.length} candidates from general search`);
|
|
12452
12458
|
} catch (error) {
|
|
12453
|
-
const
|
|
12454
|
-
phase2Error =
|
|
12459
|
+
const errMsg = errorMessage(error);
|
|
12460
|
+
phase2Error = errMsg;
|
|
12455
12461
|
if (IssueVetter.isRateLimitError(error)) {
|
|
12456
12462
|
rateLimitHitDuringSearch = true;
|
|
12457
12463
|
}
|
|
12458
|
-
warn(MODULE9, `Error in general issue search: ${
|
|
12464
|
+
warn(MODULE9, `Error in general issue search: ${errMsg}`);
|
|
12459
12465
|
}
|
|
12460
12466
|
}
|
|
12461
12467
|
let phase3Error = null;
|
|
12462
12468
|
if (allCandidates.length < maxResults) {
|
|
12463
|
-
|
|
12469
|
+
info(MODULE9, "Phase 3: Searching actively maintained repos...");
|
|
12464
12470
|
const remainingNeeded = maxResults - allCandidates.length;
|
|
12465
12471
|
const thirtyDaysAgo = /* @__PURE__ */ new Date();
|
|
12466
12472
|
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
|
12467
12473
|
const pushedSince = thirtyDaysAgo.toISOString().split("T")[0];
|
|
12468
|
-
const
|
|
12469
|
-
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();
|
|
12470
12475
|
try {
|
|
12471
12476
|
const { data } = await this.octokit.search.issuesAndPullRequests({
|
|
12472
12477
|
q: phase3Query,
|
|
@@ -12474,42 +12479,23 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
|
|
|
12474
12479
|
order: "desc",
|
|
12475
12480
|
per_page: remainingNeeded * 3
|
|
12476
12481
|
});
|
|
12477
|
-
|
|
12482
|
+
info(
|
|
12483
|
+
MODULE9,
|
|
12478
12484
|
`Found ${data.total_count} issues in maintained-repo search, processing top ${data.items.length}...`
|
|
12479
12485
|
);
|
|
12480
|
-
const spamRepos = detectLabelFarmingRepos(data.items);
|
|
12481
|
-
if (spamRepos.size > 0) {
|
|
12482
|
-
const spamCount = data.items.filter(
|
|
12483
|
-
(i) => spamRepos.has(i.repository_url.split("/").slice(-2).join("/"))
|
|
12484
|
-
).length;
|
|
12485
|
-
console.log(
|
|
12486
|
-
`[SPAM_FILTER] Filtered ${spamCount} issues from ${spamRepos.size} label-farming repos: ${[...spamRepos].join(", ")}`
|
|
12487
|
-
);
|
|
12488
|
-
}
|
|
12489
12486
|
const seenRepos = new Set(allCandidates.map((c) => c.issue.repo));
|
|
12490
|
-
const itemsToVet = filterIssues(data.items).filter((item) => {
|
|
12491
|
-
const repoFullName = item.repository_url.split("/").slice(-2).join("/");
|
|
12492
|
-
return !spamRepos.has(repoFullName) && !phase0RepoSet.has(repoFullName) && !starredRepoSet.has(repoFullName) && !seenRepos.has(repoFullName);
|
|
12493
|
-
}).slice(0, remainingNeeded * 2);
|
|
12494
12487
|
const {
|
|
12495
|
-
candidates:
|
|
12496
|
-
|
|
12488
|
+
candidates: starFiltered,
|
|
12489
|
+
allVetFailed,
|
|
12497
12490
|
rateLimitHit: vetRateLimitHit
|
|
12498
|
-
} = await this.
|
|
12499
|
-
|
|
12491
|
+
} = await this.filterVetAndScore(
|
|
12492
|
+
data.items,
|
|
12493
|
+
filterIssues,
|
|
12494
|
+
[phase0RepoSet, starredRepoSet, seenRepos],
|
|
12500
12495
|
remainingNeeded,
|
|
12501
|
-
|
|
12496
|
+
minStars,
|
|
12497
|
+
"Phase 3"
|
|
12502
12498
|
);
|
|
12503
|
-
const minStars = config.minStars ?? 50;
|
|
12504
|
-
const starFiltered = results.filter((c) => {
|
|
12505
|
-
if (c.projectHealth.checkFailed) return true;
|
|
12506
|
-
const stars = c.projectHealth.stargazersCount ?? 0;
|
|
12507
|
-
return stars >= minStars;
|
|
12508
|
-
});
|
|
12509
|
-
const starFilteredCount = results.length - starFiltered.length;
|
|
12510
|
-
if (starFilteredCount > 0) {
|
|
12511
|
-
console.log(`[STAR_FILTER] Filtered ${starFilteredCount} Phase 3 candidates below ${minStars} stars`);
|
|
12512
|
-
}
|
|
12513
12499
|
allCandidates.push(...starFiltered);
|
|
12514
12500
|
if (allVetFailed) {
|
|
12515
12501
|
phase3Error = "all vetting failed";
|
|
@@ -12517,14 +12503,14 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
|
|
|
12517
12503
|
if (vetRateLimitHit) {
|
|
12518
12504
|
rateLimitHitDuringSearch = true;
|
|
12519
12505
|
}
|
|
12520
|
-
|
|
12506
|
+
info(MODULE9, `Found ${starFiltered.length} candidates from maintained-repo search`);
|
|
12521
12507
|
} catch (error) {
|
|
12522
|
-
const
|
|
12523
|
-
phase3Error =
|
|
12508
|
+
const errMsg = errorMessage(error);
|
|
12509
|
+
phase3Error = errMsg;
|
|
12524
12510
|
if (IssueVetter.isRateLimitError(error)) {
|
|
12525
12511
|
rateLimitHitDuringSearch = true;
|
|
12526
12512
|
}
|
|
12527
|
-
warn(MODULE9, `Error in maintained-repo search: ${
|
|
12513
|
+
warn(MODULE9, `Error in maintained-repo search: ${errMsg}`);
|
|
12528
12514
|
}
|
|
12529
12515
|
}
|
|
12530
12516
|
if (allCandidates.length === 0) {
|
|
@@ -12600,11 +12586,7 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
|
|
|
12600
12586
|
rateLimitFailures++;
|
|
12601
12587
|
}
|
|
12602
12588
|
const batchRepos = batch.join(", ");
|
|
12603
|
-
warn(
|
|
12604
|
-
MODULE9,
|
|
12605
|
-
`Error searching issues in batch [${batchRepos}]:`,
|
|
12606
|
-
error instanceof Error ? error.message : error
|
|
12607
|
-
);
|
|
12589
|
+
warn(MODULE9, `Error searching issues in batch [${batchRepos}]:`, errorMessage(error));
|
|
12608
12590
|
}
|
|
12609
12591
|
}
|
|
12610
12592
|
const allBatchesFailed = failedBatches === batches.length && batches.length > 0;
|
|
@@ -12692,7 +12674,7 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`
|
|
|
12692
12674
|
content += `- **Recommendation**: Y = approve, N = skip, ? = needs_review
|
|
12693
12675
|
`;
|
|
12694
12676
|
fs4.writeFileSync(outputFile, content, "utf-8");
|
|
12695
|
-
|
|
12677
|
+
info(MODULE9, `Saved ${sorted.length} issues to ${outputFile}`);
|
|
12696
12678
|
return outputFile;
|
|
12697
12679
|
}
|
|
12698
12680
|
/**
|
|
@@ -12798,7 +12780,7 @@ var init_issue_conversation = __esm({
|
|
|
12798
12780
|
}
|
|
12799
12781
|
const { owner, repo } = parsed;
|
|
12800
12782
|
const repoFullName = `${owner}/${repo}`;
|
|
12801
|
-
if (owner
|
|
12783
|
+
if (isOwnRepo(owner, username)) continue;
|
|
12802
12784
|
if (item.user?.login?.toLowerCase() === username.toLowerCase()) continue;
|
|
12803
12785
|
if (config.excludeRepos.includes(repoFullName)) continue;
|
|
12804
12786
|
if (config.excludeOrgs?.some((org) => owner.toLowerCase() === org.toLowerCase())) continue;
|
|
@@ -12823,7 +12805,7 @@ var init_issue_conversation = __esm({
|
|
|
12823
12805
|
});
|
|
12824
12806
|
}
|
|
12825
12807
|
} catch (error) {
|
|
12826
|
-
const msg =
|
|
12808
|
+
const msg = errorMessage(error);
|
|
12827
12809
|
failures.push({ issueUrl: item.html_url, error: msg });
|
|
12828
12810
|
warn(MODULE10, `Error analyzing issue ${item.html_url}: ${msg}`);
|
|
12829
12811
|
}
|
|
@@ -13377,6 +13359,7 @@ var init_core = __esm({
|
|
|
13377
13359
|
init_comment_utils();
|
|
13378
13360
|
init_github();
|
|
13379
13361
|
init_utils();
|
|
13362
|
+
init_errors();
|
|
13380
13363
|
init_logger();
|
|
13381
13364
|
init_http_cache();
|
|
13382
13365
|
init_daily_logic();
|
|
@@ -13478,15 +13461,15 @@ async function fetchPRData(prMonitor, token) {
|
|
|
13478
13461
|
prMonitor.fetchUserMergedPRCounts(),
|
|
13479
13462
|
prMonitor.fetchUserClosedPRCounts(),
|
|
13480
13463
|
prMonitor.fetchRecentlyClosedPRs().catch((err) => {
|
|
13481
|
-
console.error(`Warning: Failed to fetch recently closed PRs: ${err
|
|
13464
|
+
console.error(`Warning: Failed to fetch recently closed PRs: ${errorMessage(err)}`);
|
|
13482
13465
|
return [];
|
|
13483
13466
|
}),
|
|
13484
13467
|
prMonitor.fetchRecentlyMergedPRs().catch((err) => {
|
|
13485
|
-
console.error(`Warning: Failed to fetch recently merged PRs: ${err
|
|
13468
|
+
console.error(`Warning: Failed to fetch recently merged PRs: ${errorMessage(err)}`);
|
|
13486
13469
|
return [];
|
|
13487
13470
|
}),
|
|
13488
13471
|
issueMonitor.fetchCommentedIssues().catch((error) => {
|
|
13489
|
-
const msg =
|
|
13472
|
+
const msg = errorMessage(error);
|
|
13490
13473
|
if (msg.includes("No GitHub username configured")) {
|
|
13491
13474
|
console.error(`[DAILY] Issue conversation tracking requires setup: ${msg}`);
|
|
13492
13475
|
} else {
|
|
@@ -13543,10 +13526,7 @@ async function updateRepoScores(prMonitor, prs, mergedCounts, closedCounts) {
|
|
|
13543
13526
|
stateManager2.updateRepoScore(repo, { mergedPRCount: count, lastMergedAt: lastMergedAt || void 0 });
|
|
13544
13527
|
} catch (error) {
|
|
13545
13528
|
mergedCountFailures++;
|
|
13546
|
-
console.error(
|
|
13547
|
-
`[DAILY] Failed to update merged count for ${repo}:`,
|
|
13548
|
-
error instanceof Error ? error.message : error
|
|
13549
|
-
);
|
|
13529
|
+
console.error(`[DAILY] Failed to update merged count for ${repo}:`, errorMessage(error));
|
|
13550
13530
|
}
|
|
13551
13531
|
}
|
|
13552
13532
|
if (mergedCountFailures === mergedCounts.size && mergedCounts.size > 0) {
|
|
@@ -13566,10 +13546,7 @@ async function updateRepoScores(prMonitor, prs, mergedCounts, closedCounts) {
|
|
|
13566
13546
|
stateManager2.updateRepoScore(repo, { closedWithoutMergeCount: count });
|
|
13567
13547
|
} catch (error) {
|
|
13568
13548
|
closedCountFailures++;
|
|
13569
|
-
console.error(
|
|
13570
|
-
`[DAILY] Failed to update closed count for ${repo}:`,
|
|
13571
|
-
error instanceof Error ? error.message : error
|
|
13572
|
-
);
|
|
13549
|
+
console.error(`[DAILY] Failed to update closed count for ${repo}:`, errorMessage(error));
|
|
13573
13550
|
}
|
|
13574
13551
|
}
|
|
13575
13552
|
if (closedCountFailures === closedCounts.size && closedCounts.size > 0) {
|
|
@@ -13582,7 +13559,7 @@ async function updateRepoScores(prMonitor, prs, mergedCounts, closedCounts) {
|
|
|
13582
13559
|
stateManager2.updateRepoScore(repo, { signals });
|
|
13583
13560
|
} catch (error) {
|
|
13584
13561
|
signalUpdateFailures++;
|
|
13585
|
-
console.error(`[DAILY] Failed to update signals for ${repo}:`, error
|
|
13562
|
+
console.error(`[DAILY] Failed to update signals for ${repo}:`, errorMessage(error));
|
|
13586
13563
|
}
|
|
13587
13564
|
}
|
|
13588
13565
|
if (signalUpdateFailures === repoSignals.size && repoSignals.size > 0) {
|
|
@@ -13595,7 +13572,7 @@ async function updateRepoScores(prMonitor, prs, mergedCounts, closedCounts) {
|
|
|
13595
13572
|
try {
|
|
13596
13573
|
starCounts = await prMonitor.fetchRepoStarCounts(allRepos);
|
|
13597
13574
|
} catch (error) {
|
|
13598
|
-
console.error("[DAILY] Failed to fetch repo star counts:", error
|
|
13575
|
+
console.error("[DAILY] Failed to fetch repo star counts:", errorMessage(error));
|
|
13599
13576
|
console.error(
|
|
13600
13577
|
"[DAILY] Dashboard minStars filter will use cached star counts (or be skipped for repos without cached data)."
|
|
13601
13578
|
);
|
|
@@ -13607,7 +13584,7 @@ async function updateRepoScores(prMonitor, prs, mergedCounts, closedCounts) {
|
|
|
13607
13584
|
stateManager2.updateRepoScore(repo, { stargazersCount: stars });
|
|
13608
13585
|
} catch (error) {
|
|
13609
13586
|
starUpdateFailures++;
|
|
13610
|
-
console.error(`[DAILY] Failed to update star count for ${repo}:`, error
|
|
13587
|
+
console.error(`[DAILY] Failed to update star count for ${repo}:`, errorMessage(error));
|
|
13611
13588
|
}
|
|
13612
13589
|
}
|
|
13613
13590
|
if (starUpdateFailures === starCounts.size && starCounts.size > 0) {
|
|
@@ -13619,7 +13596,7 @@ async function updateRepoScores(prMonitor, prs, mergedCounts, closedCounts) {
|
|
|
13619
13596
|
stateManager2.addTrustedProject(repo);
|
|
13620
13597
|
} catch (error) {
|
|
13621
13598
|
trustSyncFailures++;
|
|
13622
|
-
console.error(`[DAILY] Failed to sync trusted project ${repo}:`, error
|
|
13599
|
+
console.error(`[DAILY] Failed to sync trusted project ${repo}:`, errorMessage(error));
|
|
13623
13600
|
}
|
|
13624
13601
|
}
|
|
13625
13602
|
if (trustSyncFailures === mergedCounts.size && mergedCounts.size > 0) {
|
|
@@ -13633,12 +13610,12 @@ function updateAnalytics(prs, monthlyCounts, monthlyClosedCounts, openedFromMerg
|
|
|
13633
13610
|
try {
|
|
13634
13611
|
stateManager2.setMonthlyMergedCounts(monthlyCounts);
|
|
13635
13612
|
} catch (error) {
|
|
13636
|
-
console.error("[DAILY] Failed to store monthly merged counts:", error
|
|
13613
|
+
console.error("[DAILY] Failed to store monthly merged counts:", errorMessage(error));
|
|
13637
13614
|
}
|
|
13638
13615
|
try {
|
|
13639
13616
|
stateManager2.setMonthlyClosedCounts(monthlyClosedCounts);
|
|
13640
13617
|
} catch (error) {
|
|
13641
|
-
console.error("[DAILY] Failed to store monthly closed counts:", error
|
|
13618
|
+
console.error("[DAILY] Failed to store monthly closed counts:", errorMessage(error));
|
|
13642
13619
|
}
|
|
13643
13620
|
try {
|
|
13644
13621
|
const combinedOpenedCounts = { ...openedFromMerged };
|
|
@@ -13653,10 +13630,7 @@ function updateAnalytics(prs, monthlyCounts, monthlyClosedCounts, openedFromMerg
|
|
|
13653
13630
|
}
|
|
13654
13631
|
stateManager2.setMonthlyOpenedCounts(combinedOpenedCounts);
|
|
13655
13632
|
} catch (error) {
|
|
13656
|
-
console.error(
|
|
13657
|
-
"[DAILY] Failed to compute/store monthly opened counts:",
|
|
13658
|
-
error instanceof Error ? error.message : error
|
|
13659
|
-
);
|
|
13633
|
+
console.error("[DAILY] Failed to compute/store monthly opened counts:", errorMessage(error));
|
|
13660
13634
|
}
|
|
13661
13635
|
}
|
|
13662
13636
|
function partitionPRs(prMonitor, prs, recentlyClosedPRs, recentlyMergedPRs) {
|
|
@@ -13671,7 +13645,7 @@ function partitionPRs(prMonitor, prs, recentlyClosedPRs, recentlyMergedPRs) {
|
|
|
13671
13645
|
stateManager2.save();
|
|
13672
13646
|
}
|
|
13673
13647
|
} catch (error) {
|
|
13674
|
-
console.error("[DAILY] Failed to expire/persist snoozes:", error
|
|
13648
|
+
console.error("[DAILY] Failed to expire/persist snoozes:", errorMessage(error));
|
|
13675
13649
|
}
|
|
13676
13650
|
const shelvedPRs = [];
|
|
13677
13651
|
const autoUnshelvedPRs = [];
|
|
@@ -13733,14 +13707,15 @@ function generateDigestOutput(digest, activePRs, shelvedPRs, commentedIssues, fa
|
|
|
13733
13707
|
const snoozedUrls = new Set(
|
|
13734
13708
|
Object.keys(stateManager2.getState().config.snoozedPRs ?? {}).filter((url) => stateManager2.isSnoozed(url))
|
|
13735
13709
|
);
|
|
13736
|
-
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);
|
|
13737
13713
|
digest.summary.totalNeedingAttention = actionableIssues.length;
|
|
13738
13714
|
const briefSummary = formatBriefSummary(digest, actionableIssues.length, issueResponses.length);
|
|
13739
13715
|
const actionMenu = computeActionMenu(actionableIssues, capacity, filteredCommentedIssues);
|
|
13740
13716
|
const repoGroups = groupPRsByRepo(activePRs);
|
|
13741
13717
|
return {
|
|
13742
13718
|
digest,
|
|
13743
|
-
updates: [],
|
|
13744
13719
|
capacity,
|
|
13745
13720
|
summary,
|
|
13746
13721
|
briefSummary,
|
|
@@ -13754,7 +13729,6 @@ function generateDigestOutput(digest, activePRs, shelvedPRs, commentedIssues, fa
|
|
|
13754
13729
|
function toDailyOutput(result) {
|
|
13755
13730
|
return {
|
|
13756
13731
|
digest: deduplicateDigest(result.digest),
|
|
13757
|
-
updates: result.updates,
|
|
13758
13732
|
capacity: result.capacity,
|
|
13759
13733
|
summary: result.summary,
|
|
13760
13734
|
briefSummary: result.briefSummary,
|
|
@@ -13801,6 +13775,7 @@ var init_daily = __esm({
|
|
|
13801
13775
|
"src/commands/daily.ts"() {
|
|
13802
13776
|
"use strict";
|
|
13803
13777
|
init_core();
|
|
13778
|
+
init_errors();
|
|
13804
13779
|
init_json();
|
|
13805
13780
|
init_core();
|
|
13806
13781
|
}
|
|
@@ -13890,18 +13865,22 @@ var init_search = __esm({
|
|
|
13890
13865
|
// src/commands/validation.ts
|
|
13891
13866
|
function validateGitHubUrl(url, pattern, entityType) {
|
|
13892
13867
|
if (pattern.test(url)) return;
|
|
13893
|
-
const
|
|
13894
|
-
|
|
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]}`);
|
|
13895
13874
|
}
|
|
13896
13875
|
function validateUrl(url) {
|
|
13897
13876
|
if (url.length > MAX_URL_LENGTH) {
|
|
13898
|
-
throw new
|
|
13877
|
+
throw new ValidationError(`URL exceeds maximum length of ${MAX_URL_LENGTH} characters`);
|
|
13899
13878
|
}
|
|
13900
13879
|
return url;
|
|
13901
13880
|
}
|
|
13902
13881
|
function validateMessage(message) {
|
|
13903
13882
|
if (message.length > MAX_MESSAGE_LENGTH) {
|
|
13904
|
-
throw new
|
|
13883
|
+
throw new ValidationError(`Message exceeds maximum length of ${MAX_MESSAGE_LENGTH} characters`);
|
|
13905
13884
|
}
|
|
13906
13885
|
return message;
|
|
13907
13886
|
}
|
|
@@ -13929,13 +13908,13 @@ function validateGitHubUsername(username) {
|
|
|
13929
13908
|
}
|
|
13930
13909
|
return trimmed;
|
|
13931
13910
|
}
|
|
13932
|
-
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;
|
|
13933
13912
|
var init_validation = __esm({
|
|
13934
13913
|
"src/commands/validation.ts"() {
|
|
13935
13914
|
"use strict";
|
|
13936
13915
|
init_errors();
|
|
13937
13916
|
PR_URL_PATTERN = /^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+$/;
|
|
13938
|
-
|
|
13917
|
+
ISSUE_OR_PR_URL_PATTERN = /^https:\/\/github\.com\/[^/]+\/[^/]+\/(issues|pull)\/\d+$/;
|
|
13939
13918
|
MAX_URL_LENGTH = 2048;
|
|
13940
13919
|
MAX_MESSAGE_LENGTH = 1e3;
|
|
13941
13920
|
MAX_USERNAME_LENGTH = 39;
|
|
@@ -14063,33 +14042,35 @@ async function runComments(options) {
|
|
|
14063
14042
|
}
|
|
14064
14043
|
const { owner, repo, number: pull_number } = parsed;
|
|
14065
14044
|
const { data: pr } = await octokit.pulls.get({ owner, repo, pull_number });
|
|
14066
|
-
const reviewComments = await
|
|
14067
|
-
(
|
|
14068
|
-
|
|
14069
|
-
|
|
14070
|
-
|
|
14071
|
-
|
|
14072
|
-
|
|
14073
|
-
|
|
14074
|
-
|
|
14075
|
-
|
|
14076
|
-
(
|
|
14077
|
-
|
|
14078
|
-
|
|
14079
|
-
|
|
14080
|
-
|
|
14081
|
-
|
|
14082
|
-
|
|
14083
|
-
|
|
14084
|
-
|
|
14085
|
-
(
|
|
14086
|
-
|
|
14087
|
-
|
|
14088
|
-
|
|
14089
|
-
|
|
14090
|
-
|
|
14091
|
-
|
|
14092
|
-
|
|
14045
|
+
const [reviewComments, issueComments, reviews] = await Promise.all([
|
|
14046
|
+
paginateAll(
|
|
14047
|
+
(page) => octokit.pulls.listReviewComments({
|
|
14048
|
+
owner,
|
|
14049
|
+
repo,
|
|
14050
|
+
pull_number,
|
|
14051
|
+
per_page: 100,
|
|
14052
|
+
page
|
|
14053
|
+
})
|
|
14054
|
+
),
|
|
14055
|
+
paginateAll(
|
|
14056
|
+
(page) => octokit.issues.listComments({
|
|
14057
|
+
owner,
|
|
14058
|
+
repo,
|
|
14059
|
+
issue_number: pull_number,
|
|
14060
|
+
per_page: 100,
|
|
14061
|
+
page
|
|
14062
|
+
})
|
|
14063
|
+
),
|
|
14064
|
+
paginateAll(
|
|
14065
|
+
(page) => octokit.pulls.listReviews({
|
|
14066
|
+
owner,
|
|
14067
|
+
repo,
|
|
14068
|
+
pull_number,
|
|
14069
|
+
per_page: 100,
|
|
14070
|
+
page
|
|
14071
|
+
})
|
|
14072
|
+
)
|
|
14073
|
+
]);
|
|
14093
14074
|
const username = stateManager2.getState().config.githubUsername;
|
|
14094
14075
|
const filterComment = (c) => {
|
|
14095
14076
|
if (!c.user) return false;
|
|
@@ -14487,17 +14468,17 @@ async function fetchDashboardData(token) {
|
|
|
14487
14468
|
const [{ prs, failures }, recentlyClosedPRs, recentlyMergedPRs, mergedResult, closedResult, fetchedIssues] = await Promise.all([
|
|
14488
14469
|
prMonitor.fetchUserOpenPRs(),
|
|
14489
14470
|
prMonitor.fetchRecentlyClosedPRs().catch((err) => {
|
|
14490
|
-
console.error(`Warning: Failed to fetch recently closed PRs: ${err
|
|
14471
|
+
console.error(`Warning: Failed to fetch recently closed PRs: ${errorMessage(err)}`);
|
|
14491
14472
|
return [];
|
|
14492
14473
|
}),
|
|
14493
14474
|
prMonitor.fetchRecentlyMergedPRs().catch((err) => {
|
|
14494
|
-
console.error(`Warning: Failed to fetch recently merged PRs: ${err
|
|
14475
|
+
console.error(`Warning: Failed to fetch recently merged PRs: ${errorMessage(err)}`);
|
|
14495
14476
|
return [];
|
|
14496
14477
|
}),
|
|
14497
14478
|
prMonitor.fetchUserMergedPRCounts(),
|
|
14498
14479
|
prMonitor.fetchUserClosedPRCounts(),
|
|
14499
14480
|
issueMonitor.fetchCommentedIssues().catch((error) => {
|
|
14500
|
-
const msg =
|
|
14481
|
+
const msg = errorMessage(error);
|
|
14501
14482
|
if (msg.includes("No GitHub username configured")) {
|
|
14502
14483
|
console.error(`[DASHBOARD] Issue conversation tracking requires setup: ${msg}`);
|
|
14503
14484
|
} else {
|
|
@@ -14521,12 +14502,12 @@ async function fetchDashboardData(token) {
|
|
|
14521
14502
|
try {
|
|
14522
14503
|
stateManager2.setMonthlyMergedCounts(monthlyCounts);
|
|
14523
14504
|
} catch (error) {
|
|
14524
|
-
console.error("[DASHBOARD] Failed to store monthly merged counts:", error
|
|
14505
|
+
console.error("[DASHBOARD] Failed to store monthly merged counts:", errorMessage(error));
|
|
14525
14506
|
}
|
|
14526
14507
|
try {
|
|
14527
14508
|
stateManager2.setMonthlyClosedCounts(monthlyClosedCounts);
|
|
14528
14509
|
} catch (error) {
|
|
14529
|
-
console.error("[DASHBOARD] Failed to store monthly closed counts:", error
|
|
14510
|
+
console.error("[DASHBOARD] Failed to store monthly closed counts:", errorMessage(error));
|
|
14530
14511
|
}
|
|
14531
14512
|
try {
|
|
14532
14513
|
const combinedOpenedCounts = { ...openedFromMerged };
|
|
@@ -14541,7 +14522,7 @@ async function fetchDashboardData(token) {
|
|
|
14541
14522
|
}
|
|
14542
14523
|
stateManager2.setMonthlyOpenedCounts(combinedOpenedCounts);
|
|
14543
14524
|
} catch (error) {
|
|
14544
|
-
console.error("[DASHBOARD] Failed to store monthly opened counts:", error
|
|
14525
|
+
console.error("[DASHBOARD] Failed to store monthly opened counts:", errorMessage(error));
|
|
14545
14526
|
}
|
|
14546
14527
|
const digest = prMonitor.generateDigest(prs, recentlyClosedPRs, recentlyMergedPRs);
|
|
14547
14528
|
const shelvedUrls = new Set(stateManager2.getState().config.shelvedPRUrls || []);
|
|
@@ -14581,11 +14562,12 @@ var init_dashboard_data = __esm({
|
|
|
14581
14562
|
"src/commands/dashboard-data.ts"() {
|
|
14582
14563
|
"use strict";
|
|
14583
14564
|
init_core();
|
|
14565
|
+
init_errors();
|
|
14584
14566
|
init_daily();
|
|
14585
14567
|
}
|
|
14586
14568
|
});
|
|
14587
14569
|
|
|
14588
|
-
// src/commands/dashboard-
|
|
14570
|
+
// src/commands/dashboard-formatters.ts
|
|
14589
14571
|
function escapeHtml(text) {
|
|
14590
14572
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
14591
14573
|
}
|
|
@@ -14604,78 +14586,18 @@ function buildDashboardStats(digest, state) {
|
|
|
14604
14586
|
mergeRate: `${(summary.mergeRate ?? 0).toFixed(1)}%`
|
|
14605
14587
|
};
|
|
14606
14588
|
}
|
|
14607
|
-
|
|
14608
|
-
|
|
14609
|
-
|
|
14610
|
-
|
|
14611
|
-
|
|
14612
|
-
|
|
14613
|
-
|
|
14614
|
-
|
|
14615
|
-
|
|
14616
|
-
|
|
14617
|
-
|
|
14618
|
-
|
|
14619
|
-
...digest.incompleteChecklistPRs || [],
|
|
14620
|
-
...digest.missingRequiredFilesPRs || [],
|
|
14621
|
-
...digest.needsRebasePRs || []
|
|
14622
|
-
];
|
|
14623
|
-
const waitingOnOthers = [
|
|
14624
|
-
...digest.changesAddressedPRs || [],
|
|
14625
|
-
...digest.waitingOnMaintainerPRs || [],
|
|
14626
|
-
...digest.ciBlockedPRs || [],
|
|
14627
|
-
...digest.ciNotRunningPRs || []
|
|
14628
|
-
];
|
|
14629
|
-
function truncateTitle(title, max = 50) {
|
|
14630
|
-
const truncated = title.length <= max ? title : title.slice(0, max) + "...";
|
|
14631
|
-
return escapeHtml(truncated);
|
|
14632
|
-
}
|
|
14633
|
-
function renderHealthItems(prs, cssClass, svgPaths, labelFn, metaFn) {
|
|
14634
|
-
return prs.map((pr) => {
|
|
14635
|
-
const rawLabel = typeof labelFn === "string" ? labelFn : labelFn(pr);
|
|
14636
|
-
const label = escapeHtml(rawLabel);
|
|
14637
|
-
return `
|
|
14638
|
-
<div class="health-item ${cssClass}" data-status="${cssClass}" data-repo="${escapeHtml(pr.repo)}" data-title="${escapeHtml(pr.title.toLowerCase())}">
|
|
14639
|
-
<div class="health-icon">
|
|
14640
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
14641
|
-
${svgPaths}
|
|
14642
|
-
</svg>
|
|
14643
|
-
</div>
|
|
14644
|
-
<div class="health-content">
|
|
14645
|
-
<div class="health-title"><a href="${escapeHtml(pr.url)}" target="_blank">${escapeHtml(pr.repo)}#${pr.number}</a> - ${label}</div>
|
|
14646
|
-
<div class="health-meta">${metaFn(pr)}</div>
|
|
14647
|
-
</div>
|
|
14648
|
-
</div>`;
|
|
14649
|
-
}).join("");
|
|
14650
|
-
}
|
|
14651
|
-
const SVG = {
|
|
14652
|
-
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"/>',
|
|
14653
|
-
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"/>',
|
|
14654
|
-
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"/>',
|
|
14655
|
-
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"/>',
|
|
14656
|
-
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"/>',
|
|
14657
|
-
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"/>',
|
|
14658
|
-
checkCircle: '<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>',
|
|
14659
|
-
clock: '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>',
|
|
14660
|
-
lock: '<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>',
|
|
14661
|
-
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"/>',
|
|
14662
|
-
refresh: '<polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>',
|
|
14663
|
-
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"/>',
|
|
14664
|
-
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"/>',
|
|
14665
|
-
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"/>'
|
|
14666
|
-
};
|
|
14667
|
-
const titleMeta = (pr) => truncateTitle(pr.title);
|
|
14668
|
-
return `<!DOCTYPE html>
|
|
14669
|
-
<html lang="en">
|
|
14670
|
-
<head>
|
|
14671
|
-
<meta charset="UTF-8">
|
|
14672
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
14673
|
-
<title>OSS Autopilot - Mission Control</title>
|
|
14674
|
-
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
14675
|
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
14676
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
14677
|
-
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
14678
|
-
<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 = `
|
|
14679
14601
|
:root, [data-theme="dark"] {
|
|
14680
14602
|
--bg-base: #080b10;
|
|
14681
14603
|
--bg-surface: rgba(22, 27, 34, 0.65);
|
|
@@ -15435,57 +15357,416 @@ function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpene
|
|
|
15435
15357
|
.health-item[data-hidden="true"] {
|
|
15436
15358
|
display: none;
|
|
15437
15359
|
}
|
|
15438
|
-
|
|
15439
|
-
|
|
15440
|
-
|
|
15441
|
-
|
|
15442
|
-
|
|
15443
|
-
|
|
15444
|
-
|
|
15445
|
-
|
|
15446
|
-
|
|
15447
|
-
|
|
15448
|
-
|
|
15449
|
-
|
|
15450
|
-
|
|
15451
|
-
|
|
15452
|
-
|
|
15453
|
-
|
|
15454
|
-
|
|
15455
|
-
|
|
15456
|
-
|
|
15457
|
-
|
|
15458
|
-
|
|
15459
|
-
|
|
15460
|
-
|
|
15461
|
-
|
|
15462
|
-
|
|
15463
|
-
|
|
15464
|
-
|
|
15465
|
-
|
|
15466
|
-
|
|
15467
|
-
|
|
15468
|
-
|
|
15469
|
-
|
|
15470
|
-
|
|
15471
|
-
|
|
15472
|
-
|
|
15473
|
-
|
|
15474
|
-
|
|
15475
|
-
|
|
15476
|
-
|
|
15477
|
-
|
|
15478
|
-
|
|
15479
|
-
|
|
15480
|
-
|
|
15481
|
-
|
|
15482
|
-
|
|
15483
|
-
|
|
15484
|
-
|
|
15485
|
-
|
|
15486
|
-
|
|
15487
|
-
|
|
15488
|
-
|
|
15360
|
+
`;
|
|
15361
|
+
}
|
|
15362
|
+
});
|
|
15363
|
+
|
|
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
|
+
});
|
|
15413
|
+
|
|
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;
|
|
15421
|
+
|
|
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">
|
|
15489
15770
|
<div class="stat-card active">
|
|
15490
15771
|
<div class="stat-value">${stats.activePRs}</div>
|
|
15491
15772
|
<div class="stat-label">Active PRs</div>
|
|
@@ -15562,31 +15843,31 @@ function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpene
|
|
|
15562
15843
|
${renderHealthItems(
|
|
15563
15844
|
digest.prsNeedingResponse || [],
|
|
15564
15845
|
"needs-response",
|
|
15565
|
-
|
|
15846
|
+
SVG_ICONS.comment,
|
|
15566
15847
|
"Needs Response",
|
|
15567
15848
|
(pr) => pr.lastMaintainerComment ? `@${escapeHtml(pr.lastMaintainerComment.author)}: ${truncateTitle(pr.lastMaintainerComment.body, 40)}` : truncateTitle(pr.title)
|
|
15568
15849
|
)}
|
|
15569
|
-
${renderHealthItems(digest.needsChangesPRs || [], "needs-changes",
|
|
15570
|
-
${renderHealthItems(digest.ciFailingPRs || [], "ci-failing",
|
|
15571
|
-
${renderHealthItems(digest.mergeConflictPRs || [], "conflict",
|
|
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)}
|
|
15572
15853
|
${renderHealthItems(
|
|
15573
15854
|
digest.incompleteChecklistPRs || [],
|
|
15574
15855
|
"incomplete-checklist",
|
|
15575
|
-
|
|
15856
|
+
SVG_ICONS.checklist,
|
|
15576
15857
|
(pr) => `Incomplete Checklist${pr.checklistStats ? ` (${pr.checklistStats.checked}/${pr.checklistStats.total})` : ""}`,
|
|
15577
15858
|
titleMeta
|
|
15578
15859
|
)}
|
|
15579
15860
|
${renderHealthItems(
|
|
15580
15861
|
digest.missingRequiredFilesPRs || [],
|
|
15581
15862
|
"missing-files",
|
|
15582
|
-
|
|
15863
|
+
SVG_ICONS.file,
|
|
15583
15864
|
"Missing Required Files",
|
|
15584
15865
|
(pr) => pr.missingRequiredFiles ? escapeHtml(pr.missingRequiredFiles.join(", ")) : truncateTitle(pr.title)
|
|
15585
15866
|
)}
|
|
15586
15867
|
${renderHealthItems(
|
|
15587
15868
|
digest.needsRebasePRs || [],
|
|
15588
15869
|
"needs-rebase",
|
|
15589
|
-
|
|
15870
|
+
SVG_ICONS.refresh,
|
|
15590
15871
|
(pr) => `Needs Rebase${pr.commitsBehindUpstream ? ` (${pr.commitsBehindUpstream} behind)` : ""}`,
|
|
15591
15872
|
titleMeta
|
|
15592
15873
|
)}
|
|
@@ -15608,13 +15889,13 @@ function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpene
|
|
|
15608
15889
|
${renderHealthItems(
|
|
15609
15890
|
digest.changesAddressedPRs || [],
|
|
15610
15891
|
"changes-addressed",
|
|
15611
|
-
|
|
15892
|
+
SVG_ICONS.checkCircle,
|
|
15612
15893
|
"Changes Addressed",
|
|
15613
15894
|
(pr) => `Awaiting re-review${pr.lastMaintainerComment ? ` from @${escapeHtml(pr.lastMaintainerComment.author)}` : ""}`
|
|
15614
15895
|
)}
|
|
15615
|
-
${renderHealthItems(digest.waitingOnMaintainerPRs || [], "waiting-maintainer",
|
|
15616
|
-
${renderHealthItems(digest.ciBlockedPRs || [], "ci-blocked",
|
|
15617
|
-
${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)}
|
|
15618
15899
|
</div>
|
|
15619
15900
|
</section>
|
|
15620
15901
|
` : ""}
|
|
@@ -15638,7 +15919,7 @@ function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpene
|
|
|
15638
15919
|
<section class="health-section" style="animation-delay: 0.15s;">
|
|
15639
15920
|
<div class="health-header">
|
|
15640
15921
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--accent-merged)" stroke-width="2">
|
|
15641
|
-
${
|
|
15922
|
+
${SVG_ICONS.gitMerge}
|
|
15642
15923
|
</svg>
|
|
15643
15924
|
<h2>Recently Merged</h2>
|
|
15644
15925
|
<span class="health-badge" style="background: var(--accent-merged-dim); color: var(--accent-merged);">${recentlyMerged.length} merged</span>
|
|
@@ -15649,7 +15930,7 @@ function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpene
|
|
|
15649
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())}">
|
|
15650
15931
|
<div class="health-icon" style="background: var(--accent-merged-dim); color: var(--accent-merged);">
|
|
15651
15932
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
15652
|
-
${
|
|
15933
|
+
${SVG_ICONS.gitMerge}
|
|
15653
15934
|
</svg>
|
|
15654
15935
|
</div>
|
|
15655
15936
|
<div class="health-content">
|
|
@@ -15662,500 +15943,237 @@ function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpene
|
|
|
15662
15943
|
</div>
|
|
15663
15944
|
</section>
|
|
15664
15945
|
` : ""}
|
|
15665
|
-
|
|
15666
|
-
${(digest.recentlyClosedPRs || []).length > 0 ? `
|
|
15667
|
-
<section class="health-section" style="animation-delay: 0.2s;">
|
|
15668
|
-
<div class="health-header">
|
|
15669
|
-
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--text-muted)" stroke-width="2">
|
|
15670
|
-
<circle cx="12" cy="12" r="10"/>
|
|
15671
|
-
<line x1="15" y1="9" x2="9" y2="15"/>
|
|
15672
|
-
<line x1="9" y1="9" x2="15" y2="15"/>
|
|
15673
|
-
</svg>
|
|
15674
|
-
<h2>Recently Closed</h2>
|
|
15675
|
-
<span class="health-badge" style="background: rgba(110, 118, 129, 0.15); color: var(--text-muted);">${(digest.recentlyClosedPRs || []).length} closed</span>
|
|
15676
|
-
</div>
|
|
15677
|
-
<div class="health-items">
|
|
15678
|
-
${(digest.recentlyClosedPRs || []).map(
|
|
15679
|
-
(pr) => `
|
|
15680
|
-
<div class="health-item" style="border-left-color: var(--text-muted); opacity: 0.7;" data-status="closed" data-repo="${escapeHtml(pr.repo)}" data-title="${escapeHtml(pr.title.toLowerCase())}">
|
|
15681
|
-
<div class="health-icon" style="background: rgba(110, 118, 129, 0.15); color: var(--text-muted);">
|
|
15682
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
15683
|
-
<circle cx="12" cy="12" r="10"/>
|
|
15684
|
-
<line x1="15" y1="9" x2="9" y2="15"/>
|
|
15685
|
-
<line x1="9" y1="9" x2="15" y2="15"/>
|
|
15686
|
-
</svg>
|
|
15687
|
-
</div>
|
|
15688
|
-
<div class="health-content">
|
|
15689
|
-
<div class="health-title"><a href="${escapeHtml(pr.url)}" target="_blank">${escapeHtml(pr.repo)}#${pr.number}</a> - Closed</div>
|
|
15690
|
-
<div class="health-meta">${truncateTitle(pr.title)}${pr.closedAt ? ` \xB7 ${new Date(pr.closedAt).toLocaleDateString()}` : ""}</div>
|
|
15691
|
-
</div>
|
|
15692
|
-
</div>
|
|
15693
|
-
`
|
|
15694
|
-
).join("")}
|
|
15695
|
-
</div>
|
|
15696
|
-
</section>
|
|
15697
|
-
` : ""}
|
|
15698
|
-
|
|
15699
|
-
${autoUnshelvedPRs.length > 0 ? `
|
|
15700
|
-
<section class="health-section" style="animation-delay: 0.25s;">
|
|
15701
|
-
<div class="health-header">
|
|
15702
|
-
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--accent-info)" stroke-width="2">
|
|
15703
|
-
${SVG.bell}
|
|
15704
|
-
</svg>
|
|
15705
|
-
<h2>Auto-Unshelved</h2>
|
|
15706
|
-
<span class="health-badge" style="background: var(--accent-info-dim); color: var(--accent-info);">${autoUnshelvedPRs.length} unshelved</span>
|
|
15707
|
-
</div>
|
|
15708
|
-
<div class="health-items">
|
|
15709
|
-
${renderHealthItems(
|
|
15710
|
-
autoUnshelvedPRs,
|
|
15711
|
-
"auto-unshelved",
|
|
15712
|
-
SVG.bell,
|
|
15713
|
-
(pr) => "Auto-Unshelved (" + pr.status.replace(/_/g, " ") + ")",
|
|
15714
|
-
titleMeta
|
|
15715
|
-
)}
|
|
15716
|
-
</div>
|
|
15717
|
-
</section>
|
|
15718
|
-
` : ""}
|
|
15719
|
-
|
|
15720
|
-
${issueResponses.length > 0 ? `
|
|
15721
|
-
<section class="health-section" style="animation-delay: 0.3s;">
|
|
15722
|
-
<div class="health-header">
|
|
15723
|
-
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--accent-info)" stroke-width="2">
|
|
15724
|
-
${SVG.comment}
|
|
15725
|
-
</svg>
|
|
15726
|
-
<h2>Issue Conversations</h2>
|
|
15727
|
-
<span class="health-badge" style="background: var(--accent-info-dim); color: var(--accent-info);">${issueResponses.length} repl${issueResponses.length !== 1 ? "ies" : "y"}</span>
|
|
15728
|
-
</div>
|
|
15729
|
-
<div class="health-items">
|
|
15730
|
-
${issueResponses.map(
|
|
15731
|
-
(issue) => `
|
|
15732
|
-
<div class="health-item changes-addressed">
|
|
15733
|
-
<div class="health-icon" style="background: var(--accent-info-dim); color: var(--accent-info);">
|
|
15734
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
15735
|
-
${SVG.comment}
|
|
15736
|
-
</svg>
|
|
15737
|
-
</div>
|
|
15738
|
-
<div class="health-content">
|
|
15739
|
-
<div class="health-title"><a href="${escapeHtml(issue.url)}" target="_blank">${escapeHtml(issue.repo)}#${issue.number}</a> - ${escapeHtml(issue.title.slice(0, 50))}${issue.title.length > 50 ? "..." : ""}</div>
|
|
15740
|
-
<div class="health-meta">@${escapeHtml(issue.lastResponseAuthor)}: ${escapeHtml(issue.lastResponseBody.slice(0, 60))}${issue.lastResponseBody.length > 60 ? "..." : ""}</div>
|
|
15741
|
-
</div>
|
|
15742
|
-
</div>
|
|
15743
|
-
`
|
|
15744
|
-
).join("")}
|
|
15745
|
-
</div>
|
|
15746
|
-
</section>
|
|
15747
|
-
` : ""}
|
|
15748
|
-
|
|
15749
|
-
<div class="main-grid">
|
|
15750
|
-
<div class="card">
|
|
15751
|
-
<div class="card-header">
|
|
15752
|
-
<span class="card-title">PR Status Distribution</span>
|
|
15753
|
-
</div>
|
|
15754
|
-
<div class="card-body">
|
|
15755
|
-
<div class="chart-container">
|
|
15756
|
-
<canvas id="statusChart"></canvas>
|
|
15757
|
-
</div>
|
|
15758
|
-
</div>
|
|
15759
|
-
</div>
|
|
15760
|
-
|
|
15761
|
-
<div class="card">
|
|
15762
|
-
<div class="card-header">
|
|
15763
|
-
<span class="card-title">Repository Breakdown</span>
|
|
15764
|
-
</div>
|
|
15765
|
-
<div class="card-body">
|
|
15766
|
-
<div class="chart-container">
|
|
15767
|
-
<canvas id="reposChart"></canvas>
|
|
15768
|
-
</div>
|
|
15769
|
-
</div>
|
|
15770
|
-
</div>
|
|
15771
|
-
</div>
|
|
15772
|
-
|
|
15773
|
-
<div class="card" style="margin-bottom: 1.25rem;">
|
|
15774
|
-
<div class="card-header">
|
|
15775
|
-
<span class="card-title">Contribution Timeline</span>
|
|
15776
|
-
</div>
|
|
15777
|
-
<div class="card-body">
|
|
15778
|
-
<div class="chart-container" style="height: 250px;">
|
|
15779
|
-
<canvas id="monthlyChart"></canvas>
|
|
15780
|
-
</div>
|
|
15781
|
-
</div>
|
|
15782
|
-
</div>
|
|
15783
|
-
|
|
15784
|
-
|
|
15785
|
-
${activePRList.length > 0 ? `
|
|
15786
|
-
<section class="pr-list-section">
|
|
15787
|
-
<div class="pr-list-header">
|
|
15788
|
-
<h2 class="pr-list-title">Active Pull Requests</h2>
|
|
15789
|
-
<span class="pr-count">${activePRList.length} open</span>
|
|
15790
|
-
</div>
|
|
15791
|
-
<div class="pr-list">
|
|
15792
|
-
${activePRList.map((pr) => {
|
|
15793
|
-
const hasIssues = pr.ciStatus === "failing" || pr.hasMergeConflict || pr.hasUnrespondedComment && pr.status !== "changes_addressed" || pr.status === "needs_changes";
|
|
15794
|
-
const isStale = pr.daysSinceActivity >= approachingDormantDays;
|
|
15795
|
-
const itemClass = hasIssues ? "has-issues" : isStale ? "stale" : "";
|
|
15796
|
-
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";
|
|
15797
|
-
return `
|
|
15798
|
-
<div class="pr-item ${itemClass}" data-status="${prStatus}" data-repo="${escapeHtml(pr.repo)}" data-title="${escapeHtml(pr.title.toLowerCase())}">
|
|
15799
|
-
<div class="pr-status-indicator">
|
|
15800
|
-
${hasIssues ? `
|
|
15801
|
-
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
15802
|
-
<circle cx="12" cy="12" r="10"/>
|
|
15803
|
-
<line x1="12" y1="8" x2="12" y2="12"/>
|
|
15804
|
-
<line x1="12" y1="16" x2="12.01" y2="16"/>
|
|
15805
|
-
</svg>
|
|
15806
|
-
` : `
|
|
15807
|
-
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
15808
|
-
<circle cx="12" cy="12" r="10"/>
|
|
15809
|
-
<line x1="12" y1="16" x2="12" y2="12"/>
|
|
15810
|
-
<line x1="12" y1="8" x2="12.01" y2="8"/>
|
|
15811
|
-
</svg>
|
|
15812
|
-
`}
|
|
15813
|
-
</div>
|
|
15814
|
-
<div class="pr-content">
|
|
15815
|
-
<div class="pr-title-row">
|
|
15816
|
-
<a href="${escapeHtml(pr.url)}" target="_blank" class="pr-title">${escapeHtml(pr.title)}</a>
|
|
15817
|
-
<span class="pr-repo">${escapeHtml(pr.repo)}#${pr.number}</span>
|
|
15818
|
-
</div>
|
|
15819
|
-
<div class="pr-badges">
|
|
15820
|
-
${pr.ciStatus === "failing" ? '<span class="badge badge-ci-failing">CI Failing</span>' : ""}
|
|
15821
|
-
${pr.ciStatus === "passing" ? '<span class="badge badge-passing">CI Passing</span>' : ""}
|
|
15822
|
-
${pr.ciStatus === "pending" ? '<span class="badge badge-pending">CI Pending</span>' : ""}
|
|
15823
|
-
${pr.hasMergeConflict ? '<span class="badge badge-conflict">Merge Conflict</span>' : ""}
|
|
15824
|
-
${pr.hasUnrespondedComment && pr.status === "changes_addressed" ? '<span class="badge badge-changes-addressed">Changes Addressed</span>' : ""}
|
|
15825
|
-
${pr.hasUnrespondedComment && pr.status !== "changes_addressed" && pr.status !== "failing_ci" ? '<span class="badge badge-needs-response">Needs Response</span>' : ""}
|
|
15826
|
-
${pr.reviewDecision === "changes_requested" ? '<span class="badge badge-changes-requested">Changes Requested</span>' : ""}
|
|
15827
|
-
${isStale ? `<span class="badge badge-stale">${pr.daysSinceActivity}d inactive</span>` : ""}
|
|
15828
|
-
</div>
|
|
15829
|
-
</div>
|
|
15830
|
-
<div class="pr-activity">
|
|
15831
|
-
${pr.daysSinceActivity === 0 ? "Today" : pr.daysSinceActivity === 1 ? "Yesterday" : pr.daysSinceActivity + "d ago"}
|
|
15832
|
-
</div>
|
|
15833
|
-
</div>`;
|
|
15834
|
-
}).join("")}
|
|
15835
|
-
</div>
|
|
15836
|
-
</section>
|
|
15837
|
-
` : `
|
|
15838
|
-
<section class="pr-list-section">
|
|
15839
|
-
<div class="pr-list-header">
|
|
15840
|
-
<h2 class="pr-list-title">Active Pull Requests</h2>
|
|
15841
|
-
<span class="pr-count">0 open</span>
|
|
15842
|
-
</div>
|
|
15843
|
-
<div class="empty-state">
|
|
15844
|
-
<div class="empty-state-icon">
|
|
15845
|
-
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
15846
|
-
<circle cx="12" cy="12" r="10"/>
|
|
15847
|
-
<path d="M8 12h8"/>
|
|
15848
|
-
</svg>
|
|
15849
|
-
</div>
|
|
15850
|
-
<p>No active pull requests</p>
|
|
15851
|
-
</div>
|
|
15852
|
-
</section>
|
|
15853
|
-
`}
|
|
15854
|
-
|
|
15855
|
-
${shelvedPRs.length > 0 ? `
|
|
15856
|
-
<section class="pr-list-section" style="margin-top: 1.25rem; opacity: 0.7;">
|
|
15857
|
-
<div class="pr-list-header">
|
|
15858
|
-
<h2 class="pr-list-title">Shelved Pull Requests</h2>
|
|
15859
|
-
<span class="pr-count" style="background: rgba(110, 118, 129, 0.15); color: var(--text-muted);">${shelvedPRs.length} shelved</span>
|
|
15946
|
+
|
|
15947
|
+
${(digest.recentlyClosedPRs || []).length > 0 ? `
|
|
15948
|
+
<section class="health-section" style="animation-delay: 0.2s;">
|
|
15949
|
+
<div class="health-header">
|
|
15950
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--text-muted)" stroke-width="2">
|
|
15951
|
+
<circle cx="12" cy="12" r="10"/>
|
|
15952
|
+
<line x1="15" y1="9" x2="9" y2="15"/>
|
|
15953
|
+
<line x1="9" y1="9" x2="15" y2="15"/>
|
|
15954
|
+
</svg>
|
|
15955
|
+
<h2>Recently Closed</h2>
|
|
15956
|
+
<span class="health-badge" style="background: rgba(110, 118, 129, 0.15); color: var(--text-muted);">${(digest.recentlyClosedPRs || []).length} closed</span>
|
|
15860
15957
|
</div>
|
|
15861
|
-
<div class="
|
|
15862
|
-
${
|
|
15958
|
+
<div class="health-items">
|
|
15959
|
+
${(digest.recentlyClosedPRs || []).map(
|
|
15863
15960
|
(pr) => `
|
|
15864
|
-
<div class="
|
|
15865
|
-
<div class="
|
|
15866
|
-
<svg width="
|
|
15867
|
-
|
|
15961
|
+
<div class="health-item" style="border-left-color: var(--text-muted); opacity: 0.7;" data-status="closed" data-repo="${escapeHtml(pr.repo)}" data-title="${escapeHtml(pr.title.toLowerCase())}">
|
|
15962
|
+
<div class="health-icon" style="background: rgba(110, 118, 129, 0.15); color: var(--text-muted);">
|
|
15963
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
15964
|
+
<circle cx="12" cy="12" r="10"/>
|
|
15965
|
+
<line x1="15" y1="9" x2="9" y2="15"/>
|
|
15966
|
+
<line x1="9" y1="9" x2="15" y2="15"/>
|
|
15868
15967
|
</svg>
|
|
15869
15968
|
</div>
|
|
15870
|
-
<div class="
|
|
15871
|
-
<div class="
|
|
15872
|
-
|
|
15873
|
-
<span class="pr-repo">${escapeHtml(pr.repo)}#${pr.number}</span>
|
|
15874
|
-
</div>
|
|
15875
|
-
<div class="pr-badges">
|
|
15876
|
-
<span class="badge badge-days">${pr.daysSinceActivity}d inactive</span>
|
|
15877
|
-
</div>
|
|
15878
|
-
</div>
|
|
15879
|
-
<div class="pr-activity">
|
|
15880
|
-
${pr.daysSinceActivity === 0 ? "Today" : pr.daysSinceActivity === 1 ? "Yesterday" : pr.daysSinceActivity + "d ago"}
|
|
15969
|
+
<div class="health-content">
|
|
15970
|
+
<div class="health-title"><a href="${escapeHtml(pr.url)}" target="_blank">${escapeHtml(pr.repo)}#${pr.number}</a> - Closed</div>
|
|
15971
|
+
<div class="health-meta">${truncateTitle(pr.title)}${pr.closedAt ? ` \xB7 ${new Date(pr.closedAt).toLocaleDateString()}` : ""}</div>
|
|
15881
15972
|
</div>
|
|
15882
|
-
</div
|
|
15973
|
+
</div>
|
|
15974
|
+
`
|
|
15883
15975
|
).join("")}
|
|
15884
15976
|
</div>
|
|
15885
15977
|
</section>
|
|
15886
15978
|
` : ""}
|
|
15887
15979
|
|
|
15888
|
-
|
|
15889
|
-
|
|
15890
|
-
<
|
|
15891
|
-
|
|
15892
|
-
|
|
15893
|
-
|
|
15894
|
-
|
|
15895
|
-
|
|
15896
|
-
|
|
15897
|
-
|
|
15898
|
-
|
|
15899
|
-
|
|
15900
|
-
|
|
15901
|
-
|
|
15902
|
-
|
|
15903
|
-
|
|
15904
|
-
|
|
15905
|
-
|
|
15906
|
-
|
|
15907
|
-
|
|
15908
|
-
return window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
|
|
15909
|
-
}
|
|
15910
|
-
|
|
15911
|
-
function applyTheme(theme) {
|
|
15912
|
-
html.setAttribute('data-theme', theme);
|
|
15913
|
-
if (theme === 'light') {
|
|
15914
|
-
sunIcon.style.display = 'none';
|
|
15915
|
-
moonIcon.style.display = 'block';
|
|
15916
|
-
label.textContent = 'Dark';
|
|
15917
|
-
} else {
|
|
15918
|
-
sunIcon.style.display = 'block';
|
|
15919
|
-
moonIcon.style.display = 'none';
|
|
15920
|
-
label.textContent = 'Light';
|
|
15921
|
-
}
|
|
15922
|
-
}
|
|
15923
|
-
|
|
15924
|
-
applyTheme(getEffectiveTheme());
|
|
15925
|
-
|
|
15926
|
-
toggle.addEventListener('click', function() {
|
|
15927
|
-
var current = html.getAttribute('data-theme');
|
|
15928
|
-
var next = current === 'dark' ? 'light' : 'dark';
|
|
15929
|
-
try { localStorage.setItem('oss-dashboard-theme', next); } catch (e) { /* private browsing */ }
|
|
15930
|
-
applyTheme(next);
|
|
15931
|
-
});
|
|
15932
|
-
})();
|
|
15933
|
-
|
|
15934
|
-
// === Filtering & Search ===
|
|
15935
|
-
(function() {
|
|
15936
|
-
var searchInput = document.getElementById('searchInput');
|
|
15937
|
-
var statusFilter = document.getElementById('statusFilter');
|
|
15938
|
-
var repoFilter = document.getElementById('repoFilter');
|
|
15939
|
-
var filterCount = document.getElementById('filterCount');
|
|
15940
|
-
|
|
15941
|
-
function applyFilters() {
|
|
15942
|
-
var query = searchInput.value.toLowerCase().trim();
|
|
15943
|
-
var status = statusFilter.value;
|
|
15944
|
-
var repo = repoFilter.value;
|
|
15945
|
-
var allItems = document.querySelectorAll('.health-item[data-status], .pr-item[data-status]');
|
|
15946
|
-
var visible = 0;
|
|
15947
|
-
var total = allItems.length;
|
|
15948
|
-
|
|
15949
|
-
allItems.forEach(function(item) {
|
|
15950
|
-
var itemStatus = item.getAttribute('data-status') || '';
|
|
15951
|
-
var itemRepo = item.getAttribute('data-repo') || '';
|
|
15952
|
-
var itemTitle = item.getAttribute('data-title') || '';
|
|
15953
|
-
|
|
15954
|
-
var matchesStatus = (status === 'all') || (itemStatus === status);
|
|
15955
|
-
var matchesRepo = (repo === 'all') || (itemRepo === repo);
|
|
15956
|
-
var matchesSearch = !query || itemTitle.indexOf(query) !== -1;
|
|
15957
|
-
|
|
15958
|
-
if (matchesStatus && matchesRepo && matchesSearch) {
|
|
15959
|
-
item.setAttribute('data-hidden', 'false');
|
|
15960
|
-
visible++;
|
|
15961
|
-
} else {
|
|
15962
|
-
item.setAttribute('data-hidden', 'true');
|
|
15963
|
-
}
|
|
15964
|
-
});
|
|
15965
|
-
|
|
15966
|
-
// Show/hide parent sections if all children are hidden
|
|
15967
|
-
var sections = document.querySelectorAll('.health-section, .pr-list-section');
|
|
15968
|
-
sections.forEach(function(section) {
|
|
15969
|
-
var items = section.querySelectorAll('.health-item[data-status], .pr-item[data-status]');
|
|
15970
|
-
if (items.length === 0) return; // sections without filterable items (e.g. empty state)
|
|
15971
|
-
var anyVisible = false;
|
|
15972
|
-
items.forEach(function(item) {
|
|
15973
|
-
if (item.getAttribute('data-hidden') !== 'true') anyVisible = true;
|
|
15974
|
-
});
|
|
15975
|
-
section.style.display = anyVisible ? '' : 'none';
|
|
15976
|
-
});
|
|
15977
|
-
|
|
15978
|
-
var isFiltering = (status !== 'all' || repo !== 'all' || query.length > 0);
|
|
15979
|
-
filterCount.textContent = isFiltering ? (visible + ' of ' + total + ' items') : '';
|
|
15980
|
-
}
|
|
15981
|
-
|
|
15982
|
-
searchInput.addEventListener('input', applyFilters);
|
|
15983
|
-
statusFilter.addEventListener('change', applyFilters);
|
|
15984
|
-
repoFilter.addEventListener('change', applyFilters);
|
|
15985
|
-
})();
|
|
15986
|
-
|
|
15987
|
-
// === Chart.js Configuration ===
|
|
15988
|
-
Chart.defaults.color = '#6e7681';
|
|
15989
|
-
Chart.defaults.borderColor = 'rgba(48, 54, 61, 0.4)';
|
|
15990
|
-
Chart.defaults.font.family = "'Geist', sans-serif";
|
|
15991
|
-
Chart.defaults.font.size = 11;
|
|
15992
|
-
|
|
15993
|
-
// === Status Doughnut ===
|
|
15994
|
-
new Chart(document.getElementById('statusChart'), {
|
|
15995
|
-
type: 'doughnut',
|
|
15996
|
-
data: {
|
|
15997
|
-
labels: ['Active', 'Shelved', 'Merged', 'Closed'],
|
|
15998
|
-
datasets: [{
|
|
15999
|
-
data: [${stats.activePRs}, ${stats.shelvedPRs}, ${stats.mergedPRs}, ${stats.closedPRs}],
|
|
16000
|
-
backgroundColor: ['#3fb950', '#6e7681', '#a855f7', '#484f58'],
|
|
16001
|
-
borderColor: 'rgba(8, 11, 16, 0.8)',
|
|
16002
|
-
borderWidth: 2,
|
|
16003
|
-
hoverOffset: 8
|
|
16004
|
-
}]
|
|
16005
|
-
},
|
|
16006
|
-
options: {
|
|
16007
|
-
responsive: true,
|
|
16008
|
-
maintainAspectRatio: false,
|
|
16009
|
-
cutout: '65%',
|
|
16010
|
-
plugins: {
|
|
16011
|
-
legend: {
|
|
16012
|
-
position: 'bottom',
|
|
16013
|
-
labels: { padding: 16, usePointStyle: true, pointStyle: 'circle', font: { size: 11 } }
|
|
16014
|
-
}
|
|
16015
|
-
}
|
|
16016
|
-
}
|
|
16017
|
-
});
|
|
16018
|
-
|
|
16019
|
-
// === Repository Breakdown (with "Other" bucket + percentage tooltips) ===
|
|
16020
|
-
${(() => {
|
|
16021
|
-
const { excludeRepos: exRepos = [], excludeOrgs: exOrgs, minStars } = state.config;
|
|
16022
|
-
const starThreshold = minStars ?? 50;
|
|
16023
|
-
const shouldExcludeRepo = (repo) => {
|
|
16024
|
-
const repoLower = repo.toLowerCase();
|
|
16025
|
-
if (exRepos.some((r) => r.toLowerCase() === repoLower)) return true;
|
|
16026
|
-
if (exOrgs?.some((o) => o.toLowerCase() === repoLower.split("/")[0])) return true;
|
|
16027
|
-
const score = (state.repoScores || {})[repo];
|
|
16028
|
-
if (score?.stargazersCount !== void 0 && score.stargazersCount < starThreshold) return true;
|
|
16029
|
-
return false;
|
|
16030
|
-
};
|
|
16031
|
-
const allRepoEntries = Object.entries(
|
|
16032
|
-
// Rebuild from full prsByRepo to get all repos, not just top 10
|
|
16033
|
-
(() => {
|
|
16034
|
-
const all = {};
|
|
16035
|
-
for (const pr of digest.openPRs || []) {
|
|
16036
|
-
if (shouldExcludeRepo(pr.repo)) continue;
|
|
16037
|
-
if (!all[pr.repo]) all[pr.repo] = { active: 0, merged: 0, closed: 0 };
|
|
16038
|
-
all[pr.repo].active++;
|
|
16039
|
-
}
|
|
16040
|
-
for (const [repo, score] of Object.entries(state.repoScores || {})) {
|
|
16041
|
-
if (shouldExcludeRepo(repo)) continue;
|
|
16042
|
-
if (!all[repo]) all[repo] = { active: 0, merged: 0, closed: 0 };
|
|
16043
|
-
all[repo].merged = score.mergedPRCount;
|
|
16044
|
-
all[repo].closed = score.closedWithoutMergeCount;
|
|
16045
|
-
}
|
|
16046
|
-
return all;
|
|
16047
|
-
})()
|
|
16048
|
-
).sort((a, b) => {
|
|
16049
|
-
const totalA = a[1].merged + a[1].active + a[1].closed;
|
|
16050
|
-
const totalB = b[1].merged + b[1].active + b[1].closed;
|
|
16051
|
-
return totalB - totalA;
|
|
16052
|
-
});
|
|
16053
|
-
const displayRepos = allRepoEntries.slice(0, 10);
|
|
16054
|
-
const otherRepos = allRepoEntries.slice(10);
|
|
16055
|
-
const grandTotal = allRepoEntries.reduce((sum, [, d]) => sum + d.merged + d.active + d.closed, 0);
|
|
16056
|
-
if (otherRepos.length > 0) {
|
|
16057
|
-
const otherData = otherRepos.reduce(
|
|
16058
|
-
(acc, [, d]) => ({
|
|
16059
|
-
active: acc.active + d.active,
|
|
16060
|
-
merged: acc.merged + d.merged,
|
|
16061
|
-
closed: acc.closed + d.closed
|
|
16062
|
-
}),
|
|
16063
|
-
{ active: 0, merged: 0, closed: 0 }
|
|
16064
|
-
);
|
|
16065
|
-
displayRepos.push(["Other", otherData]);
|
|
16066
|
-
}
|
|
16067
|
-
const repoLabels = displayRepos.map(([repo]) => repo === "Other" ? "Other" : repo.split("/")[1] || repo);
|
|
16068
|
-
const mergedData = displayRepos.map(([, d]) => d.merged);
|
|
16069
|
-
const activeData = displayRepos.map(([, d]) => d.active);
|
|
16070
|
-
const closedData = displayRepos.map(([, d]) => d.closed);
|
|
16071
|
-
return `
|
|
16072
|
-
new Chart(document.getElementById('reposChart'), {
|
|
16073
|
-
type: 'bar',
|
|
16074
|
-
data: {
|
|
16075
|
-
labels: ${JSON.stringify(repoLabels)},
|
|
16076
|
-
datasets: [
|
|
16077
|
-
{ label: 'Merged', data: ${JSON.stringify(mergedData)}, backgroundColor: '#a855f7', borderRadius: 3 },
|
|
16078
|
-
{ label: 'Active', data: ${JSON.stringify(activeData)}, backgroundColor: '#3fb950', borderRadius: 3 },
|
|
16079
|
-
{ label: 'Closed', data: ${JSON.stringify(closedData)}, backgroundColor: '#484f58', borderRadius: 3 }
|
|
16080
|
-
]
|
|
16081
|
-
},
|
|
16082
|
-
options: {
|
|
16083
|
-
responsive: true,
|
|
16084
|
-
maintainAspectRatio: false,
|
|
16085
|
-
scales: {
|
|
16086
|
-
x: { stacked: true, grid: { display: false }, ticks: { font: { size: 10 } } },
|
|
16087
|
-
y: { stacked: true, grid: { color: 'rgba(48, 54, 61, 0.3)' }, ticks: { stepSize: 1 } }
|
|
16088
|
-
},
|
|
16089
|
-
plugins: {
|
|
16090
|
-
legend: { position: 'bottom', labels: { padding: 16, usePointStyle: true, pointStyle: 'circle', font: { size: 11 } } },
|
|
16091
|
-
tooltip: {
|
|
16092
|
-
callbacks: {
|
|
16093
|
-
afterBody: function(context) {
|
|
16094
|
-
const idx = context[0].dataIndex;
|
|
16095
|
-
const total = ${JSON.stringify(mergedData)}[idx] + ${JSON.stringify(activeData)}[idx] + ${JSON.stringify(closedData)}[idx];
|
|
16096
|
-
const pct = ${grandTotal} > 0 ? ((total / ${grandTotal}) * 100).toFixed(1) : '0.0';
|
|
16097
|
-
return pct + '% of all PRs';
|
|
16098
|
-
}
|
|
16099
|
-
}
|
|
16100
|
-
}
|
|
16101
|
-
}
|
|
16102
|
-
}
|
|
16103
|
-
});`;
|
|
16104
|
-
})()}
|
|
15980
|
+
${autoUnshelvedPRs.length > 0 ? `
|
|
15981
|
+
<section class="health-section" style="animation-delay: 0.25s;">
|
|
15982
|
+
<div class="health-header">
|
|
15983
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--accent-info)" stroke-width="2">
|
|
15984
|
+
${SVG_ICONS.bell}
|
|
15985
|
+
</svg>
|
|
15986
|
+
<h2>Auto-Unshelved</h2>
|
|
15987
|
+
<span class="health-badge" style="background: var(--accent-info-dim); color: var(--accent-info);">${autoUnshelvedPRs.length} unshelved</span>
|
|
15988
|
+
</div>
|
|
15989
|
+
<div class="health-items">
|
|
15990
|
+
${renderHealthItems(
|
|
15991
|
+
autoUnshelvedPRs,
|
|
15992
|
+
"auto-unshelved",
|
|
15993
|
+
SVG_ICONS.bell,
|
|
15994
|
+
(pr) => "Auto-Unshelved (" + pr.status.replace(/_/g, " ") + ")",
|
|
15995
|
+
titleMeta
|
|
15996
|
+
)}
|
|
15997
|
+
</div>
|
|
15998
|
+
</section>
|
|
15999
|
+
` : ""}
|
|
16105
16000
|
|
|
16106
|
-
|
|
16107
|
-
|
|
16108
|
-
|
|
16109
|
-
|
|
16110
|
-
|
|
16111
|
-
|
|
16112
|
-
|
|
16113
|
-
|
|
16001
|
+
${issueResponses.length > 0 ? `
|
|
16002
|
+
<section class="health-section" style="animation-delay: 0.3s;">
|
|
16003
|
+
<div class="health-header">
|
|
16004
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--accent-info)" stroke-width="2">
|
|
16005
|
+
${SVG_ICONS.comment}
|
|
16006
|
+
</svg>
|
|
16007
|
+
<h2>Issue Conversations</h2>
|
|
16008
|
+
<span class="health-badge" style="background: var(--accent-info-dim); color: var(--accent-info);">${issueResponses.length} repl${issueResponses.length !== 1 ? "ies" : "y"}</span>
|
|
16009
|
+
</div>
|
|
16010
|
+
<div class="health-items">
|
|
16011
|
+
${issueResponses.map(
|
|
16012
|
+
(issue) => `
|
|
16013
|
+
<div class="health-item changes-addressed">
|
|
16014
|
+
<div class="health-icon" style="background: var(--accent-info-dim); color: var(--accent-info);">
|
|
16015
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
16016
|
+
${SVG_ICONS.comment}
|
|
16017
|
+
</svg>
|
|
16018
|
+
</div>
|
|
16019
|
+
<div class="health-content">
|
|
16020
|
+
<div class="health-title"><a href="${escapeHtml(issue.url)}" target="_blank">${escapeHtml(issue.repo)}#${issue.number}</a> - ${escapeHtml(issue.title.slice(0, 50))}${issue.title.length > 50 ? "..." : ""}</div>
|
|
16021
|
+
<div class="health-meta">@${escapeHtml(issue.lastResponseAuthor)}: ${escapeHtml(issue.lastResponseBody.slice(0, 60))}${issue.lastResponseBody.length > 60 ? "..." : ""}</div>
|
|
16022
|
+
</div>
|
|
16023
|
+
</div>
|
|
16024
|
+
`
|
|
16025
|
+
).join("")}
|
|
16026
|
+
</div>
|
|
16027
|
+
</section>
|
|
16028
|
+
` : ""}
|
|
16029
|
+
|
|
16030
|
+
<div class="main-grid">
|
|
16031
|
+
<div class="card">
|
|
16032
|
+
<div class="card-header">
|
|
16033
|
+
<span class="card-title">PR Status Distribution</span>
|
|
16034
|
+
</div>
|
|
16035
|
+
<div class="card-body">
|
|
16036
|
+
<div class="chart-container">
|
|
16037
|
+
<canvas id="statusChart"></canvas>
|
|
16038
|
+
</div>
|
|
16039
|
+
</div>
|
|
16040
|
+
</div>
|
|
16041
|
+
|
|
16042
|
+
<div class="card">
|
|
16043
|
+
<div class="card-header">
|
|
16044
|
+
<span class="card-title">Repository Breakdown</span>
|
|
16045
|
+
</div>
|
|
16046
|
+
<div class="card-body">
|
|
16047
|
+
<div class="chart-container">
|
|
16048
|
+
<canvas id="reposChart"></canvas>
|
|
16049
|
+
</div>
|
|
16050
|
+
</div>
|
|
16051
|
+
</div>
|
|
16052
|
+
</div>
|
|
16053
|
+
|
|
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>
|
|
16064
|
+
|
|
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";
|
|
16114
16078
|
return `
|
|
16115
|
-
|
|
16116
|
-
|
|
16117
|
-
|
|
16118
|
-
|
|
16119
|
-
|
|
16120
|
-
|
|
16121
|
-
|
|
16122
|
-
|
|
16123
|
-
|
|
16124
|
-
|
|
16125
|
-
|
|
16126
|
-
|
|
16127
|
-
|
|
16128
|
-
|
|
16129
|
-
|
|
16130
|
-
|
|
16131
|
-
|
|
16132
|
-
|
|
16133
|
-
|
|
16134
|
-
|
|
16135
|
-
|
|
16136
|
-
|
|
16137
|
-
|
|
16138
|
-
|
|
16139
|
-
|
|
16140
|
-
|
|
16141
|
-
|
|
16142
|
-
|
|
16143
|
-
|
|
16144
|
-
|
|
16145
|
-
|
|
16146
|
-
|
|
16147
|
-
|
|
16148
|
-
|
|
16149
|
-
|
|
16150
|
-
|
|
16151
|
-
|
|
16152
|
-
|
|
16153
|
-
|
|
16154
|
-
|
|
16155
|
-
|
|
16156
|
-
|
|
16157
|
-
|
|
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>
|
|
16158
16174
|
|
|
16175
|
+
<script>
|
|
16176
|
+
${generateDashboardScripts(stats, monthlyMerged, monthlyClosed, monthlyOpened, digest, state)}
|
|
16159
16177
|
</script>
|
|
16160
16178
|
</body>
|
|
16161
16179
|
</html>`;
|
|
@@ -16163,6 +16181,11 @@ function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpene
|
|
|
16163
16181
|
var init_dashboard_templates = __esm({
|
|
16164
16182
|
"src/commands/dashboard-templates.ts"() {
|
|
16165
16183
|
"use strict";
|
|
16184
|
+
init_dashboard_formatters();
|
|
16185
|
+
init_dashboard_styles();
|
|
16186
|
+
init_dashboard_components();
|
|
16187
|
+
init_dashboard_scripts();
|
|
16188
|
+
init_dashboard_formatters();
|
|
16166
16189
|
}
|
|
16167
16190
|
});
|
|
16168
16191
|
|
|
@@ -16320,7 +16343,7 @@ async function startDashboardServer(options) {
|
|
|
16320
16343
|
stateManager2.save();
|
|
16321
16344
|
} catch (error) {
|
|
16322
16345
|
console.error("Action failed:", body.action, body.url, error);
|
|
16323
|
-
sendError(res, 500, `Action failed: ${
|
|
16346
|
+
sendError(res, 500, `Action failed: ${errorMessage(error)}`);
|
|
16324
16347
|
return;
|
|
16325
16348
|
}
|
|
16326
16349
|
if (cachedDigest) {
|
|
@@ -16343,7 +16366,7 @@ async function startDashboardServer(options) {
|
|
|
16343
16366
|
sendJson(res, 200, cachedJsonData);
|
|
16344
16367
|
} catch (error) {
|
|
16345
16368
|
console.error("Dashboard refresh failed:", error);
|
|
16346
|
-
sendError(res, 500, `Refresh failed: ${
|
|
16369
|
+
sendError(res, 500, `Refresh failed: ${errorMessage(error)}`);
|
|
16347
16370
|
}
|
|
16348
16371
|
}
|
|
16349
16372
|
function serveStaticFile(requestUrl, res) {
|
|
@@ -16464,6 +16487,7 @@ var init_dashboard_server = __esm({
|
|
|
16464
16487
|
fs5 = __toESM(require("fs"), 1);
|
|
16465
16488
|
path5 = __toESM(require("path"), 1);
|
|
16466
16489
|
init_core();
|
|
16490
|
+
init_errors();
|
|
16467
16491
|
init_dashboard_data();
|
|
16468
16492
|
init_dashboard_templates();
|
|
16469
16493
|
VALID_ACTIONS = /* @__PURE__ */ new Set(["shelve", "unshelve", "snooze", "unsnooze"]);
|
|
@@ -16512,7 +16536,7 @@ async function runDashboard(options) {
|
|
|
16512
16536
|
digest = result.digest;
|
|
16513
16537
|
commentedIssues = result.commentedIssues;
|
|
16514
16538
|
} catch (error) {
|
|
16515
|
-
console.error("Failed to fetch fresh data:", error
|
|
16539
|
+
console.error("Failed to fetch fresh data:", errorMessage(error));
|
|
16516
16540
|
console.error("Falling back to cached data (issue conversations unavailable)...");
|
|
16517
16541
|
digest = stateManager2.getState().lastDigest;
|
|
16518
16542
|
}
|
|
@@ -16642,6 +16666,7 @@ var init_dashboard = __esm({
|
|
|
16642
16666
|
path6 = __toESM(require("path"), 1);
|
|
16643
16667
|
import_child_process2 = require("child_process");
|
|
16644
16668
|
init_core();
|
|
16669
|
+
init_errors();
|
|
16645
16670
|
init_json();
|
|
16646
16671
|
init_dashboard_data();
|
|
16647
16672
|
init_dashboard_templates();
|
|
@@ -16727,7 +16752,7 @@ async function runParseList(options) {
|
|
|
16727
16752
|
try {
|
|
16728
16753
|
content = fs7.readFileSync(filePath, "utf-8");
|
|
16729
16754
|
} catch (error) {
|
|
16730
|
-
const msg =
|
|
16755
|
+
const msg = errorMessage(error);
|
|
16731
16756
|
throw new Error(`Failed to read file: ${msg}`, { cause: error });
|
|
16732
16757
|
}
|
|
16733
16758
|
return parseIssueList(content);
|
|
@@ -16738,6 +16763,7 @@ var init_parse_list = __esm({
|
|
|
16738
16763
|
"use strict";
|
|
16739
16764
|
fs7 = __toESM(require("fs"), 1);
|
|
16740
16765
|
path7 = __toESM(require("path"), 1);
|
|
16766
|
+
init_errors();
|
|
16741
16767
|
}
|
|
16742
16768
|
});
|
|
16743
16769
|
|
|
@@ -16782,7 +16808,7 @@ async function runCheckIntegration(options) {
|
|
|
16782
16808
|
}).trim();
|
|
16783
16809
|
newFiles = output ? output.split("\n").filter(Boolean) : [];
|
|
16784
16810
|
} catch (error) {
|
|
16785
|
-
const msg =
|
|
16811
|
+
const msg = errorMessage(error);
|
|
16786
16812
|
throw new Error(`Failed to run git diff: ${msg}`, { cause: error });
|
|
16787
16813
|
}
|
|
16788
16814
|
const codeFiles = newFiles.filter((f) => {
|
|
@@ -16826,24 +16852,24 @@ async function runCheckIntegration(options) {
|
|
|
16826
16852
|
referencedBy.push(...matches);
|
|
16827
16853
|
}
|
|
16828
16854
|
} catch (error) {
|
|
16829
|
-
const exitCode = error && typeof error === "object" && "status" in error ? error.status :
|
|
16830
|
-
if (exitCode !==
|
|
16831
|
-
const msg =
|
|
16855
|
+
const exitCode = error && typeof error === "object" && "status" in error ? error.status : void 0;
|
|
16856
|
+
if (exitCode !== void 0 && exitCode !== 1) {
|
|
16857
|
+
const msg = errorMessage(error);
|
|
16832
16858
|
debug("check-integration", `git grep failed for "${pattern}": ${msg}`);
|
|
16833
16859
|
}
|
|
16834
16860
|
}
|
|
16835
16861
|
}
|
|
16836
16862
|
referencedBy = [...new Set(referencedBy)];
|
|
16837
16863
|
const isIntegrated = referencedBy.length > 0;
|
|
16838
|
-
const
|
|
16864
|
+
const info2 = {
|
|
16839
16865
|
path: newFile,
|
|
16840
16866
|
referencedBy,
|
|
16841
16867
|
isIntegrated
|
|
16842
16868
|
};
|
|
16843
16869
|
if (!isIntegrated) {
|
|
16844
|
-
|
|
16870
|
+
info2.suggestedEntryPoints = suggestEntryPoints(newFile, allFiles);
|
|
16845
16871
|
}
|
|
16846
|
-
results.push(
|
|
16872
|
+
results.push(info2);
|
|
16847
16873
|
}
|
|
16848
16874
|
const unreferencedCount = results.filter((r) => !r.isIntegrated).length;
|
|
16849
16875
|
return { newFiles: results, unreferencedCount };
|
|
@@ -16855,6 +16881,7 @@ var init_check_integration = __esm({
|
|
|
16855
16881
|
path8 = __toESM(require("path"), 1);
|
|
16856
16882
|
import_child_process3 = require("child_process");
|
|
16857
16883
|
init_core();
|
|
16884
|
+
init_errors();
|
|
16858
16885
|
CODE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
16859
16886
|
".ts",
|
|
16860
16887
|
".tsx",
|
|
@@ -16976,7 +17003,7 @@ async function runLocalRepos(options) {
|
|
|
16976
17003
|
stateManager2.setLocalRepoCache({ repos, scanPaths, cachedAt });
|
|
16977
17004
|
stateManager2.save();
|
|
16978
17005
|
} catch (error) {
|
|
16979
|
-
const msg =
|
|
17006
|
+
const msg = errorMessage(error);
|
|
16980
17007
|
debug("local-repos", `Failed to cache scan results: ${msg}`);
|
|
16981
17008
|
}
|
|
16982
17009
|
return {
|
|
@@ -16995,6 +17022,7 @@ var init_local_repos = __esm({
|
|
|
16995
17022
|
os2 = __toESM(require("os"), 1);
|
|
16996
17023
|
import_child_process4 = require("child_process");
|
|
16997
17024
|
init_core();
|
|
17025
|
+
init_errors();
|
|
16998
17026
|
DEFAULT_SCAN_PATHS = [
|
|
16999
17027
|
path9.join(os2.homedir(), "Documents", "oss"),
|
|
17000
17028
|
path9.join(os2.homedir(), "dev"),
|
|
@@ -17014,15 +17042,6 @@ __export(startup_exports, {
|
|
|
17014
17042
|
parseIssueListPathFromConfig: () => parseIssueListPathFromConfig,
|
|
17015
17043
|
runStartup: () => runStartup
|
|
17016
17044
|
});
|
|
17017
|
-
function getVersion() {
|
|
17018
|
-
try {
|
|
17019
|
-
const pkgPath = path10.join(path10.dirname(process.argv[1]), "..", "package.json");
|
|
17020
|
-
return JSON.parse(fs9.readFileSync(pkgPath, "utf-8")).version;
|
|
17021
|
-
} catch (error) {
|
|
17022
|
-
console.error("[STARTUP] Failed to detect CLI version:", error instanceof Error ? error.message : error);
|
|
17023
|
-
return "0.0.0";
|
|
17024
|
-
}
|
|
17025
|
-
}
|
|
17026
17045
|
function parseIssueListPathFromConfig(configContent) {
|
|
17027
17046
|
const match = configContent.match(/^---\n([\s\S]*?)\n---/);
|
|
17028
17047
|
if (!match) return void 0;
|
|
@@ -17058,7 +17077,7 @@ function detectIssueList() {
|
|
|
17058
17077
|
source = "configured";
|
|
17059
17078
|
}
|
|
17060
17079
|
} catch (error) {
|
|
17061
|
-
console.error("[STARTUP] Failed to read config:", error
|
|
17080
|
+
console.error("[STARTUP] Failed to read config:", errorMessage(error));
|
|
17062
17081
|
}
|
|
17063
17082
|
}
|
|
17064
17083
|
if (!issueListPath) {
|
|
@@ -17077,10 +17096,7 @@ function detectIssueList() {
|
|
|
17077
17096
|
const { availableCount, completedCount } = countIssueListItems(content);
|
|
17078
17097
|
return { path: issueListPath, source, availableCount, completedCount };
|
|
17079
17098
|
} catch (error) {
|
|
17080
|
-
console.error(
|
|
17081
|
-
`[STARTUP] Failed to read issue list at ${issueListPath}:`,
|
|
17082
|
-
error instanceof Error ? error.message : error
|
|
17083
|
-
);
|
|
17099
|
+
console.error(`[STARTUP] Failed to read issue list at ${issueListPath}:`, errorMessage(error));
|
|
17084
17100
|
return { path: issueListPath, source, availableCount: 0, completedCount: 0 };
|
|
17085
17101
|
}
|
|
17086
17102
|
}
|
|
@@ -17095,7 +17111,7 @@ function openInBrowser(filePath) {
|
|
|
17095
17111
|
});
|
|
17096
17112
|
}
|
|
17097
17113
|
async function runStartup() {
|
|
17098
|
-
const version =
|
|
17114
|
+
const version = getCLIVersion();
|
|
17099
17115
|
const stateManager2 = getStateManager();
|
|
17100
17116
|
if (!stateManager2.isSetupComplete()) {
|
|
17101
17117
|
return { version, setupComplete: false };
|
|
@@ -17118,7 +17134,7 @@ async function runStartup() {
|
|
|
17118
17134
|
dashboardOpened = true;
|
|
17119
17135
|
}
|
|
17120
17136
|
} catch (error) {
|
|
17121
|
-
console.error("[STARTUP] Dashboard generation failed:", error
|
|
17137
|
+
console.error("[STARTUP] Dashboard generation failed:", errorMessage(error));
|
|
17122
17138
|
}
|
|
17123
17139
|
if (dashboardOpened) {
|
|
17124
17140
|
daily.briefSummary += " | Dashboard opened in browser";
|
|
@@ -17132,14 +17148,14 @@ async function runStartup() {
|
|
|
17132
17148
|
issueList
|
|
17133
17149
|
};
|
|
17134
17150
|
}
|
|
17135
|
-
var fs9,
|
|
17151
|
+
var fs9, import_child_process5;
|
|
17136
17152
|
var init_startup = __esm({
|
|
17137
17153
|
"src/commands/startup.ts"() {
|
|
17138
17154
|
"use strict";
|
|
17139
17155
|
fs9 = __toESM(require("fs"), 1);
|
|
17140
|
-
path10 = __toESM(require("path"), 1);
|
|
17141
17156
|
import_child_process5 = require("child_process");
|
|
17142
17157
|
init_core();
|
|
17158
|
+
init_errors();
|
|
17143
17159
|
init_daily();
|
|
17144
17160
|
init_dashboard();
|
|
17145
17161
|
}
|
|
@@ -17183,29 +17199,29 @@ var init_shelve = __esm({
|
|
|
17183
17199
|
// src/commands/dismiss.ts
|
|
17184
17200
|
var dismiss_exports = {};
|
|
17185
17201
|
__export(dismiss_exports, {
|
|
17186
|
-
|
|
17202
|
+
ISSUE_OR_PR_URL_PATTERN: () => ISSUE_OR_PR_URL_PATTERN,
|
|
17187
17203
|
runDismiss: () => runDismiss,
|
|
17188
17204
|
runUndismiss: () => runUndismiss
|
|
17189
17205
|
});
|
|
17190
17206
|
async function runDismiss(options) {
|
|
17191
|
-
validateUrl(options.
|
|
17192
|
-
validateGitHubUrl(options.
|
|
17207
|
+
validateUrl(options.url);
|
|
17208
|
+
validateGitHubUrl(options.url, ISSUE_OR_PR_URL_PATTERN, "issue or PR");
|
|
17193
17209
|
const stateManager2 = getStateManager();
|
|
17194
|
-
const added = stateManager2.dismissIssue(options.
|
|
17210
|
+
const added = stateManager2.dismissIssue(options.url, (/* @__PURE__ */ new Date()).toISOString());
|
|
17195
17211
|
if (added) {
|
|
17196
17212
|
stateManager2.save();
|
|
17197
17213
|
}
|
|
17198
|
-
return { dismissed: added, url: options.
|
|
17214
|
+
return { dismissed: added, url: options.url };
|
|
17199
17215
|
}
|
|
17200
17216
|
async function runUndismiss(options) {
|
|
17201
|
-
validateUrl(options.
|
|
17202
|
-
validateGitHubUrl(options.
|
|
17217
|
+
validateUrl(options.url);
|
|
17218
|
+
validateGitHubUrl(options.url, ISSUE_OR_PR_URL_PATTERN, "issue or PR");
|
|
17203
17219
|
const stateManager2 = getStateManager();
|
|
17204
|
-
const removed = stateManager2.undismissIssue(options.
|
|
17220
|
+
const removed = stateManager2.undismissIssue(options.url);
|
|
17205
17221
|
if (removed) {
|
|
17206
17222
|
stateManager2.save();
|
|
17207
17223
|
}
|
|
17208
|
-
return { undismissed: removed, url: options.
|
|
17224
|
+
return { undismissed: removed, url: options.url };
|
|
17209
17225
|
}
|
|
17210
17226
|
var init_dismiss = __esm({
|
|
17211
17227
|
"src/commands/dismiss.ts"() {
|
|
@@ -17282,17 +17298,18 @@ var {
|
|
|
17282
17298
|
|
|
17283
17299
|
// src/cli.ts
|
|
17284
17300
|
init_core();
|
|
17301
|
+
init_errors();
|
|
17285
17302
|
init_json();
|
|
17286
17303
|
function printRepos(repos) {
|
|
17287
17304
|
const entries = Object.entries(repos).sort(([a], [b]) => a.localeCompare(b));
|
|
17288
|
-
for (const [remote,
|
|
17289
|
-
const branch =
|
|
17305
|
+
for (const [remote, info2] of entries) {
|
|
17306
|
+
const branch = info2.currentBranch ? ` (${info2.currentBranch})` : "";
|
|
17290
17307
|
console.log(` ${remote}${branch}`);
|
|
17291
|
-
console.log(` ${
|
|
17308
|
+
console.log(` ${info2.path}`);
|
|
17292
17309
|
}
|
|
17293
17310
|
}
|
|
17294
17311
|
function handleCommandError(err, json) {
|
|
17295
|
-
const msg =
|
|
17312
|
+
const msg = errorMessage(err);
|
|
17296
17313
|
if (json) {
|
|
17297
17314
|
outputJsonError(msg);
|
|
17298
17315
|
} else {
|
|
@@ -17300,16 +17317,7 @@ function handleCommandError(err, json) {
|
|
|
17300
17317
|
}
|
|
17301
17318
|
process.exit(1);
|
|
17302
17319
|
}
|
|
17303
|
-
var VERSION10 = (
|
|
17304
|
-
try {
|
|
17305
|
-
const fs10 = require("fs");
|
|
17306
|
-
const path11 = require("path");
|
|
17307
|
-
const pkgPath = path11.join(path11.dirname(process.argv[1]), "..", "package.json");
|
|
17308
|
-
return JSON.parse(fs10.readFileSync(pkgPath, "utf-8")).version;
|
|
17309
|
-
} catch (_err) {
|
|
17310
|
-
return "0.0.0";
|
|
17311
|
-
}
|
|
17312
|
-
})();
|
|
17320
|
+
var VERSION10 = getCLIVersion();
|
|
17313
17321
|
var LOCAL_ONLY_COMMANDS = [
|
|
17314
17322
|
"help",
|
|
17315
17323
|
"status",
|
|
@@ -17701,8 +17709,8 @@ program2.command("parse-issue-list <path>").description("Parse a markdown issue
|
|
|
17701
17709
|
if (options.json) {
|
|
17702
17710
|
outputJson(data);
|
|
17703
17711
|
} else {
|
|
17704
|
-
const
|
|
17705
|
-
const resolvedPath =
|
|
17712
|
+
const path10 = await import("path");
|
|
17713
|
+
const resolvedPath = path10.resolve(filePath);
|
|
17706
17714
|
console.log(`
|
|
17707
17715
|
\u{1F4CB} Issue List: ${resolvedPath}
|
|
17708
17716
|
`);
|
|
@@ -17830,34 +17838,34 @@ program2.command("unshelve <pr-url>").description("Unshelve a PR (include in cap
|
|
|
17830
17838
|
handleCommandError(err, options.json);
|
|
17831
17839
|
}
|
|
17832
17840
|
});
|
|
17833
|
-
program2.command("dismiss <
|
|
17841
|
+
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) => {
|
|
17834
17842
|
try {
|
|
17835
17843
|
const { runDismiss: runDismiss2 } = await Promise.resolve().then(() => (init_dismiss(), dismiss_exports));
|
|
17836
|
-
const data = await runDismiss2({
|
|
17844
|
+
const data = await runDismiss2({ url });
|
|
17837
17845
|
if (options.json) {
|
|
17838
17846
|
outputJson(data);
|
|
17839
17847
|
} else if (data.dismissed) {
|
|
17840
|
-
console.log(`Dismissed: ${
|
|
17841
|
-
console.log("
|
|
17848
|
+
console.log(`Dismissed: ${url}`);
|
|
17849
|
+
console.log("Notifications are now muted.");
|
|
17842
17850
|
console.log("New responses after this point will resurface automatically.");
|
|
17843
17851
|
} else {
|
|
17844
|
-
console.log("
|
|
17852
|
+
console.log("Already dismissed.");
|
|
17845
17853
|
}
|
|
17846
17854
|
} catch (err) {
|
|
17847
17855
|
handleCommandError(err, options.json);
|
|
17848
17856
|
}
|
|
17849
17857
|
});
|
|
17850
|
-
program2.command("undismiss <
|
|
17858
|
+
program2.command("undismiss <url>").description("Undismiss an issue or PR (re-enable notifications)").option("--json", "Output as JSON").action(async (url, options) => {
|
|
17851
17859
|
try {
|
|
17852
17860
|
const { runUndismiss: runUndismiss2 } = await Promise.resolve().then(() => (init_dismiss(), dismiss_exports));
|
|
17853
|
-
const data = await runUndismiss2({
|
|
17861
|
+
const data = await runUndismiss2({ url });
|
|
17854
17862
|
if (options.json) {
|
|
17855
17863
|
outputJson(data);
|
|
17856
17864
|
} else if (data.undismissed) {
|
|
17857
|
-
console.log(`Undismissed: ${
|
|
17858
|
-
console.log("
|
|
17865
|
+
console.log(`Undismissed: ${url}`);
|
|
17866
|
+
console.log("Notifications are active again.");
|
|
17859
17867
|
} else {
|
|
17860
|
-
console.log("
|
|
17868
|
+
console.log("Was not dismissed.");
|
|
17861
17869
|
}
|
|
17862
17870
|
} catch (err) {
|
|
17863
17871
|
handleCommandError(err, options.json);
|