@oss-autopilot/mcp 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -235,10 +235,10 @@ function assignProp(target, prop, value) {
235
235
  configurable: true
236
236
  });
237
237
  }
238
- function getElementAtPath(obj, path7) {
239
- if (!path7)
238
+ function getElementAtPath(obj, path6) {
239
+ if (!path6)
240
240
  return obj;
241
- return path7.reduce((acc, key) => acc?.[key], obj);
241
+ return path6.reduce((acc, key) => acc?.[key], obj);
242
242
  }
243
243
  function promiseAllObject(promisesObj) {
244
244
  const keys = Object.keys(promisesObj);
@@ -487,11 +487,11 @@ function aborted(x, startIndex = 0) {
487
487
  }
488
488
  return false;
489
489
  }
490
- function prefixIssues(path7, issues) {
490
+ function prefixIssues(path6, issues) {
491
491
  return issues.map((iss) => {
492
492
  var _a;
493
493
  (_a = iss).path ?? (_a.path = []);
494
- iss.path.unshift(path7);
494
+ iss.path.unshift(path6);
495
495
  return iss;
496
496
  });
497
497
  }
@@ -9454,8 +9454,8 @@ var require_utils = __commonJS({
9454
9454
  }
9455
9455
  return ind;
9456
9456
  }
9457
- function removeDotSegments(path7) {
9458
- let input = path7;
9457
+ function removeDotSegments(path6) {
9458
+ let input = path6;
9459
9459
  const output = [];
9460
9460
  let nextSlash = -1;
9461
9461
  let len = 0;
@@ -9654,8 +9654,8 @@ var require_schemes = __commonJS({
9654
9654
  wsComponent.secure = void 0;
9655
9655
  }
9656
9656
  if (wsComponent.resourceName) {
9657
- const [path7, query] = wsComponent.resourceName.split("?");
9658
- wsComponent.path = path7 && path7 !== "/" ? path7 : void 0;
9657
+ const [path6, query] = wsComponent.resourceName.split("?");
9658
+ wsComponent.path = path6 && path6 !== "/" ? path6 : void 0;
9659
9659
  wsComponent.query = query;
9660
9660
  wsComponent.resourceName = void 0;
9661
9661
  }
@@ -13069,6 +13069,16 @@ var init_types2 = __esm({
13069
13069
  });
13070
13070
 
13071
13071
  // ../core/dist/core/errors.js
13072
+ function errorMessage(e) {
13073
+ return e instanceof Error ? e.message : String(e);
13074
+ }
13075
+ function getHttpStatusCode(error2) {
13076
+ if (error2 && typeof error2 === "object" && "status" in error2) {
13077
+ const status = error2.status;
13078
+ return typeof status === "number" && Number.isFinite(status) ? status : void 0;
13079
+ }
13080
+ return void 0;
13081
+ }
13072
13082
  var OssAutopilotError, ConfigurationError, ValidationError;
13073
13083
  var init_errors3 = __esm({
13074
13084
  "../core/dist/core/errors.js"() {
@@ -13103,6 +13113,10 @@ function debug(module2, message, ...args) {
13103
13113
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
13104
13114
  console.error(`[${timestamp}] [DEBUG] [${module2}] ${message}`, ...args);
13105
13115
  }
13116
+ function info(module2, message, ...args) {
13117
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
13118
+ console.error(`[${timestamp}] [INFO] [${module2}] ${message}`, ...args);
13119
+ }
13106
13120
  function warn(module2, message, ...args) {
13107
13121
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
13108
13122
  console.error(`[${timestamp}] [WARN] [${module2}] ${message}`, ...args);
@@ -13214,6 +13228,17 @@ function splitRepo(repoFullName) {
13214
13228
  const [owner, repo] = repoFullName.split("/");
13215
13229
  return { owner, repo };
13216
13230
  }
13231
+ function isOwnRepo(owner, username) {
13232
+ return owner.toLowerCase() === username.toLowerCase();
13233
+ }
13234
+ function getCLIVersion() {
13235
+ try {
13236
+ const pkgPath = path.join(path.dirname(process.argv[1]), "..", "package.json");
13237
+ return JSON.parse(fs.readFileSync(pkgPath, "utf-8")).version;
13238
+ } catch {
13239
+ return "0.0.0";
13240
+ }
13241
+ }
13217
13242
  function formatRelativeTime(dateStr) {
13218
13243
  const date3 = new Date(dateStr);
13219
13244
  const diffMs = Date.now() - date3.getTime();
@@ -13503,8 +13528,7 @@ var init_state = __esm({
13503
13528
  debug(MODULE2, "Migration complete!");
13504
13529
  return true;
13505
13530
  } catch (error2) {
13506
- const errorMessage2 = error2 instanceof Error ? error2.message : String(error2);
13507
- warn(MODULE2, `Failed to migrate state: ${errorMessage2}`);
13531
+ warn(MODULE2, `Failed to migrate state: ${errorMessage(error2)}`);
13508
13532
  const newStatePath2 = getStatePath();
13509
13533
  if (fs2.existsSync(newStatePath2) && fs2.existsSync(LEGACY_STATE_FILE)) {
13510
13534
  try {
@@ -13658,11 +13682,11 @@ var init_state = __esm({
13658
13682
  try {
13659
13683
  fs2.unlinkSync(path2.join(backupDir, file));
13660
13684
  } catch (error2) {
13661
- warn(MODULE2, `Could not delete old backup ${file}:`, error2 instanceof Error ? error2.message : error2);
13685
+ warn(MODULE2, `Could not delete old backup ${file}:`, errorMessage(error2));
13662
13686
  }
13663
13687
  }
13664
13688
  } catch (error2) {
13665
- warn(MODULE2, "Could not clean up backups:", error2 instanceof Error ? error2.message : error2);
13689
+ warn(MODULE2, "Could not clean up backups:", errorMessage(error2));
13666
13690
  }
13667
13691
  }
13668
13692
  /**
@@ -13989,12 +14013,12 @@ var init_state = __esm({
13989
14013
  * @returns true if the PR is snoozed and the snooze has not expired.
13990
14014
  */
13991
14015
  isSnoozed(url) {
13992
- const info = this.getSnoozeInfo(url);
13993
- if (!info)
14016
+ const info2 = this.getSnoozeInfo(url);
14017
+ if (!info2)
13994
14018
  return false;
13995
- const expiresAtMs = new Date(info.expiresAt).getTime();
14019
+ const expiresAtMs = new Date(info2.expiresAt).getTime();
13996
14020
  if (isNaN(expiresAtMs)) {
13997
- warn(MODULE2, `Invalid expiresAt for snoozed PR ${url}: "${info.expiresAt}". Treating as not snoozed.`);
14021
+ warn(MODULE2, `Invalid expiresAt for snoozed PR ${url}: "${info2.expiresAt}". Treating as not snoozed.`);
13998
14022
  return false;
13999
14023
  }
14000
14024
  return expiresAtMs > Date.now();
@@ -14016,8 +14040,8 @@ var init_state = __esm({
14016
14040
  return [];
14017
14041
  const expired = [];
14018
14042
  const now = Date.now();
14019
- for (const [url, info] of Object.entries(this.state.config.snoozedPRs)) {
14020
- const expiresAtMs = new Date(info.expiresAt).getTime();
14043
+ for (const [url, info2] of Object.entries(this.state.config.snoozedPRs)) {
14044
+ const expiresAtMs = new Date(info2.expiresAt).getTime();
14021
14045
  if (isNaN(expiresAtMs) || expiresAtMs <= now) {
14022
14046
  expired.push(url);
14023
14047
  }
@@ -15444,17 +15468,17 @@ function requestLog(octokit) {
15444
15468
  octokit.log.debug("request", options);
15445
15469
  const start = Date.now();
15446
15470
  const requestOptions = octokit.request.endpoint.parse(options);
15447
- const path7 = requestOptions.url.replace(options.baseUrl, "");
15471
+ const path6 = requestOptions.url.replace(options.baseUrl, "");
15448
15472
  return request2(options).then((response) => {
15449
15473
  const requestId = response.headers["x-github-request-id"];
15450
15474
  octokit.log.info(
15451
- `${requestOptions.method} ${path7} - ${response.status} with id ${requestId} in ${Date.now() - start}ms`
15475
+ `${requestOptions.method} ${path6} - ${response.status} with id ${requestId} in ${Date.now() - start}ms`
15452
15476
  );
15453
15477
  return response;
15454
15478
  }).catch((error2) => {
15455
15479
  const requestId = error2.response?.headers["x-github-request-id"] || "UNKNOWN";
15456
15480
  octokit.log.error(
15457
- `${requestOptions.method} ${path7} - ${error2.status} with id ${requestId} in ${Date.now() - start}ms`
15481
+ `${requestOptions.method} ${path6} - ${error2.status} with id ${requestId} in ${Date.now() - start}ms`
15458
15482
  );
15459
15483
  throw error2;
15460
15484
  });
@@ -19435,7 +19459,7 @@ function isAuthRequest(method, pathname) {
19435
19459
  }
19436
19460
  function routeMatcher(paths) {
19437
19461
  const regexes = paths.map(
19438
- (path7) => path7.split("/").map((c) => c.startsWith("{") ? "(?:.+?)" : c).join("/")
19462
+ (path6) => path6.split("/").map((c) => c.startsWith("{") ? "(?:.+?)" : c).join("/")
19439
19463
  );
19440
19464
  const regex2 = `^(?:${regexes.map((r) => `(?:${r})`).join("|")})[^/]*$`;
19441
19465
  return new RegExp(regex2, "i");
@@ -19492,8 +19516,8 @@ function throttling(octokit, octokitOptions) {
19492
19516
  "error",
19493
19517
  (e) => octokit.log.warn("Error in throttling-plugin limit handler", e)
19494
19518
  );
19495
- state.retryLimiter.on("failed", async function(error2, info) {
19496
- const [state2, request2, options] = info.args;
19519
+ state.retryLimiter.on("failed", async function(error2, info2) {
19520
+ const [state2, request2, options] = info2.args;
19497
19521
  const { pathname } = new URL(options.url, "http://github.test");
19498
19522
  const shouldRetryGraphQL = pathname.startsWith("/graphql") && error2.status !== 401;
19499
19523
  if (!(shouldRetryGraphQL || error2.status === 403 || error2.status === 429)) {
@@ -19753,10 +19777,7 @@ async function cachedRequest(cache, url, fetcher) {
19753
19777
  }
19754
19778
  }
19755
19779
  function isNotModifiedError(err2) {
19756
- if (err2 && typeof err2 === "object" && "status" in err2) {
19757
- return err2.status === 304;
19758
- }
19759
- return false;
19780
+ return getHttpStatusCode(err2) === 304;
19760
19781
  }
19761
19782
  var fs3, path3, crypto2, MODULE4, DEFAULT_MAX_AGE_MS, HttpCache, _httpCache;
19762
19783
  var init_http_cache = __esm({
@@ -19767,6 +19788,7 @@ var init_http_cache = __esm({
19767
19788
  crypto2 = __toESM(require("crypto"), 1);
19768
19789
  init_utils();
19769
19790
  init_logger();
19791
+ init_errors3();
19770
19792
  MODULE4 = "http-cache";
19771
19793
  DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
19772
19794
  HttpCache = class {
@@ -19784,6 +19806,20 @@ var init_http_cache = __esm({
19784
19806
  pathFor(url) {
19785
19807
  return path3.join(this.cacheDir, `${this.keyFor(url)}.json`);
19786
19808
  }
19809
+ /**
19810
+ * Return the cached body if the entry exists and is younger than `maxAgeMs`.
19811
+ * Useful for time-based caching where ETag validation isn't applicable
19812
+ * (e.g., caching aggregated results from paginated API calls).
19813
+ */
19814
+ getIfFresh(key, maxAgeMs) {
19815
+ const entry = this.get(key);
19816
+ if (!entry)
19817
+ return null;
19818
+ const age = Date.now() - new Date(entry.cachedAt).getTime();
19819
+ if (!Number.isFinite(age) || age < 0 || age > maxAgeMs)
19820
+ return null;
19821
+ return entry.body;
19822
+ }
19787
19823
  /**
19788
19824
  * Look up a cached response. Returns `null` if no cache entry exists.
19789
19825
  */
@@ -20135,7 +20171,7 @@ function checkUnrespondedComments(comments, reviews, reviewComments, username) {
20135
20171
  if (!review.submitted_at)
20136
20172
  continue;
20137
20173
  const body = (review.body || "").trim();
20138
- if (!body && review.state !== "COMMENTED")
20174
+ if (!body && review.state !== "COMMENTED" && review.state !== "CHANGES_REQUESTED")
20139
20175
  continue;
20140
20176
  const author = review.user?.login || "unknown";
20141
20177
  if (!body && review.state === "COMMENTED" && review.id != null) {
@@ -20143,7 +20179,7 @@ function checkUnrespondedComments(comments, reviews, reviewComments, username) {
20143
20179
  continue;
20144
20180
  }
20145
20181
  }
20146
- const resolvedBody = body || (review.id != null ? getInlineCommentBody(review.id, reviewComments) : void 0) || "(posted inline review comments)";
20182
+ 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)");
20147
20183
  timeline.push({
20148
20184
  author,
20149
20185
  body: resolvedBody,
@@ -20369,11 +20405,29 @@ var init_display_utils = __esm({
20369
20405
  });
20370
20406
 
20371
20407
  // ../core/dist/core/github-stats.js
20372
- async function fetchUserMergedPRCounts(octokit, githubUsername) {
20408
+ function isCachedPRCounts(v) {
20409
+ if (typeof v !== "object" || v === null)
20410
+ return false;
20411
+ const obj = v;
20412
+ 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;
20413
+ }
20414
+ async function fetchUserPRCounts(octokit, githubUsername, query, label, accumulateRepo) {
20373
20415
  if (!githubUsername) {
20374
20416
  return { repos: /* @__PURE__ */ new Map(), monthlyCounts: {}, monthlyOpenedCounts: {}, dailyActivityCounts: {} };
20375
20417
  }
20376
- debug(MODULE6, `Fetching merged PR counts for @${githubUsername}...`);
20418
+ const cache = getHttpCache();
20419
+ const cacheKey2 = `pr-counts:${label}:${githubUsername}`;
20420
+ const cached2 = cache.getIfFresh(cacheKey2, PR_COUNTS_CACHE_TTL_MS);
20421
+ if (cached2 && isCachedPRCounts(cached2)) {
20422
+ debug(MODULE6, `Using cached ${label} PR counts for @${githubUsername}`);
20423
+ return {
20424
+ repos: new Map(cached2.reposEntries),
20425
+ monthlyCounts: cached2.monthlyCounts,
20426
+ monthlyOpenedCounts: cached2.monthlyOpenedCounts,
20427
+ dailyActivityCounts: cached2.dailyActivityCounts
20428
+ };
20429
+ }
20430
+ debug(MODULE6, `Fetching ${label} PR counts for @${githubUsername}...`);
20377
20431
  const repos = /* @__PURE__ */ new Map();
20378
20432
  const monthlyCounts = {};
20379
20433
  const monthlyOpenedCounts = {};
@@ -20382,7 +20436,7 @@ async function fetchUserMergedPRCounts(octokit, githubUsername) {
20382
20436
  let fetched = 0;
20383
20437
  while (true) {
20384
20438
  const { data } = await octokit.search.issuesAndPullRequests({
20385
- q: `is:pr is:merged author:${githubUsername}`,
20439
+ q: `is:pr ${query} author:${githubUsername}`,
20386
20440
  sort: "updated",
20387
20441
  order: "desc",
20388
20442
  per_page: 100,
@@ -20391,26 +20445,20 @@ async function fetchUserMergedPRCounts(octokit, githubUsername) {
20391
20445
  for (const item of data.items) {
20392
20446
  const parsed = extractOwnerRepo(item.html_url);
20393
20447
  if (!parsed) {
20394
- warn(MODULE6, `Skipping merged PR with unparseable URL: ${item.html_url}`);
20448
+ warn(MODULE6, `Skipping ${label} PR with unparseable URL: ${item.html_url}`);
20395
20449
  continue;
20396
20450
  }
20397
20451
  const { owner } = parsed;
20398
20452
  const repo = `${owner}/${parsed.repo}`;
20399
- if (owner.toLowerCase() === githubUsername.toLowerCase())
20453
+ if (isOwnRepo(owner, githubUsername))
20400
20454
  continue;
20401
- const mergedAt = item.pull_request?.merged_at || item.closed_at || "";
20402
- const existing = repos.get(repo);
20403
- if (existing) {
20404
- existing.count += 1;
20405
- if (mergedAt && mergedAt > existing.lastMergedAt) {
20406
- existing.lastMergedAt = mergedAt;
20407
- }
20408
- } else {
20409
- repos.set(repo, { count: 1, lastMergedAt: mergedAt });
20410
- }
20411
- if (mergedAt) {
20412
- const month = mergedAt.slice(0, 7);
20455
+ const primaryDate = accumulateRepo(repos, repo, item);
20456
+ if (primaryDate) {
20457
+ const month = primaryDate.slice(0, 7);
20413
20458
  monthlyCounts[month] = (monthlyCounts[month] || 0) + 1;
20459
+ const day = primaryDate.slice(0, 10);
20460
+ if (day.length === 10)
20461
+ dailyActivityCounts[day] = (dailyActivityCounts[day] || 0) + 1;
20414
20462
  }
20415
20463
  if (item.created_at) {
20416
20464
  const openedMonth = item.created_at.slice(0, 7);
@@ -20419,11 +20467,6 @@ async function fetchUserMergedPRCounts(octokit, githubUsername) {
20419
20467
  if (openedDay.length === 10)
20420
20468
  dailyActivityCounts[openedDay] = (dailyActivityCounts[openedDay] || 0) + 1;
20421
20469
  }
20422
- if (mergedAt) {
20423
- const mergedDay = mergedAt.slice(0, 10);
20424
- if (mergedDay.length === 10)
20425
- dailyActivityCounts[mergedDay] = (dailyActivityCounts[mergedDay] || 0) + 1;
20426
- }
20427
20470
  }
20428
20471
  fetched += data.items.length;
20429
20472
  if (fetched >= data.total_count || fetched >= 1e3 || data.items.length === 0) {
@@ -20431,62 +20474,38 @@ async function fetchUserMergedPRCounts(octokit, githubUsername) {
20431
20474
  }
20432
20475
  page++;
20433
20476
  }
20434
- debug(MODULE6, `Found ${fetched} merged PRs across ${repos.size} repos`);
20477
+ debug(MODULE6, `Found ${fetched} ${label} PRs across ${repos.size} repos`);
20478
+ cache.set(cacheKey2, "", {
20479
+ reposEntries: Array.from(repos.entries()),
20480
+ monthlyCounts,
20481
+ monthlyOpenedCounts,
20482
+ dailyActivityCounts
20483
+ });
20435
20484
  return { repos, monthlyCounts, monthlyOpenedCounts, dailyActivityCounts };
20436
20485
  }
20437
- async function fetchUserClosedPRCounts(octokit, githubUsername) {
20438
- if (!githubUsername) {
20439
- return { repos: /* @__PURE__ */ new Map(), monthlyCounts: {}, monthlyOpenedCounts: {}, dailyActivityCounts: {} };
20440
- }
20441
- debug(MODULE6, `Fetching closed PR counts for @${githubUsername}...`);
20442
- const repos = /* @__PURE__ */ new Map();
20443
- const monthlyCounts = {};
20444
- const monthlyOpenedCounts = {};
20445
- const dailyActivityCounts = {};
20446
- let page = 1;
20447
- let fetched = 0;
20448
- while (true) {
20449
- const { data } = await octokit.search.issuesAndPullRequests({
20450
- q: `is:pr is:closed is:unmerged author:${githubUsername}`,
20451
- sort: "updated",
20452
- order: "desc",
20453
- per_page: 100,
20454
- page
20455
- });
20456
- for (const item of data.items) {
20457
- const parsed = extractOwnerRepo(item.html_url);
20458
- if (!parsed) {
20459
- warn(MODULE6, `Skipping closed PR with unparseable URL: ${item.html_url}`);
20460
- continue;
20461
- }
20462
- const { owner } = parsed;
20463
- const repo = `${owner}/${parsed.repo}`;
20464
- if (owner.toLowerCase() === githubUsername.toLowerCase())
20465
- continue;
20466
- repos.set(repo, (repos.get(repo) || 0) + 1);
20467
- if (item.closed_at) {
20468
- const closedMonth = item.closed_at.slice(0, 7);
20469
- monthlyCounts[closedMonth] = (monthlyCounts[closedMonth] || 0) + 1;
20470
- const closedDay = item.closed_at.slice(0, 10);
20471
- if (closedDay.length === 10)
20472
- dailyActivityCounts[closedDay] = (dailyActivityCounts[closedDay] || 0) + 1;
20473
- }
20474
- if (item.created_at) {
20475
- const openedMonth = item.created_at.slice(0, 7);
20476
- monthlyOpenedCounts[openedMonth] = (monthlyOpenedCounts[openedMonth] || 0) + 1;
20477
- const openedDay = item.created_at.slice(0, 10);
20478
- if (openedDay.length === 10)
20479
- dailyActivityCounts[openedDay] = (dailyActivityCounts[openedDay] || 0) + 1;
20480
- }
20486
+ function fetchUserMergedPRCounts(octokit, githubUsername) {
20487
+ return fetchUserPRCounts(octokit, githubUsername, "is:merged", "merged", (repos, repo, item) => {
20488
+ if (!item.pull_request?.merged_at) {
20489
+ warn(MODULE6, `merged_at missing for merged PR ${item.html_url}${item.closed_at ? ", falling back to closed_at" : ", no date available"}`);
20481
20490
  }
20482
- fetched += data.items.length;
20483
- if (fetched >= data.total_count || fetched >= 1e3 || data.items.length === 0) {
20484
- break;
20491
+ const mergedAt = item.pull_request?.merged_at || item.closed_at || "";
20492
+ const existing = repos.get(repo);
20493
+ if (existing) {
20494
+ existing.count += 1;
20495
+ if (mergedAt && mergedAt > existing.lastMergedAt) {
20496
+ existing.lastMergedAt = mergedAt;
20497
+ }
20498
+ } else {
20499
+ repos.set(repo, { count: 1, lastMergedAt: mergedAt });
20485
20500
  }
20486
- page++;
20487
- }
20488
- debug(MODULE6, `Found ${fetched} closed (unmerged) PRs across ${repos.size} repos`);
20489
- return { repos, monthlyCounts, monthlyOpenedCounts, dailyActivityCounts };
20501
+ return mergedAt;
20502
+ });
20503
+ }
20504
+ function fetchUserClosedPRCounts(octokit, githubUsername) {
20505
+ return fetchUserPRCounts(octokit, githubUsername, "is:closed is:unmerged", "closed", (repos, repo, item) => {
20506
+ repos.set(repo, (repos.get(repo) || 0) + 1);
20507
+ return item.closed_at || "";
20508
+ });
20490
20509
  }
20491
20510
  async function fetchRecentPRs(octokit, config2, query, label, days, mapItem) {
20492
20511
  if (!config2.githubUsername) {
@@ -20511,7 +20530,7 @@ async function fetchRecentPRs(octokit, config2, query, label, days, mapItem) {
20511
20530
  continue;
20512
20531
  }
20513
20532
  const repo = `${parsed.owner}/${parsed.repo}`;
20514
- if (parsed.owner.toLowerCase() === config2.githubUsername.toLowerCase())
20533
+ if (isOwnRepo(parsed.owner, config2.githubUsername))
20515
20534
  continue;
20516
20535
  if (config2.excludeRepos.includes(repo))
20517
20536
  continue;
@@ -20546,14 +20565,15 @@ async function fetchRecentlyMergedPRs(octokit, config2, days = 7) {
20546
20565
  };
20547
20566
  });
20548
20567
  }
20549
- var MODULE6;
20568
+ var MODULE6, PR_COUNTS_CACHE_TTL_MS;
20550
20569
  var init_github_stats = __esm({
20551
20570
  "../core/dist/core/github-stats.js"() {
20552
20571
  "use strict";
20553
20572
  init_utils();
20554
- init_errors3();
20555
20573
  init_logger();
20574
+ init_http_cache();
20556
20575
  MODULE6 = "github-stats";
20576
+ PR_COUNTS_CACHE_TTL_MS = 60 * 60 * 1e3;
20557
20577
  }
20558
20578
  });
20559
20579
 
@@ -20654,9 +20674,9 @@ var init_pr_monitor = __esm({
20654
20674
  if (pr)
20655
20675
  prs.push(pr);
20656
20676
  } catch (error2) {
20657
- const errorMessage2 = error2 instanceof Error ? error2.message : String(error2);
20658
- warn("pr-monitor", `Error fetching ${item.html_url}: ${errorMessage2}`);
20659
- failures.push({ prUrl: item.html_url, error: errorMessage2 });
20677
+ const errMsg = errorMessage(error2);
20678
+ warn("pr-monitor", `Error fetching ${item.html_url}: ${errMsg}`);
20679
+ failures.push({ prUrl: item.html_url, error: errMsg });
20660
20680
  }
20661
20681
  }, MAX_CONCURRENT_REQUESTS);
20662
20682
  });
@@ -20697,12 +20717,12 @@ var init_pr_monitor = __esm({
20697
20717
  paginateAll((page) => this.octokit.issues.listComments({ owner, repo, issue_number: number3, per_page: 100, page })),
20698
20718
  this.octokit.pulls.listReviews({ owner, repo, pull_number: number3 }),
20699
20719
  paginateAll((page) => this.octokit.pulls.listReviewComments({ owner, repo, pull_number: number3, per_page: 100, page })).catch((err2) => {
20700
- const status2 = err2?.status;
20720
+ const status2 = getHttpStatusCode(err2);
20701
20721
  if (status2 === 429) {
20702
20722
  throw err2;
20703
20723
  }
20704
20724
  if (status2 === 403) {
20705
- const msg = (err2?.message ?? "").toLowerCase();
20725
+ const msg = errorMessage(err2).toLowerCase();
20706
20726
  if (msg.includes("rate limit") || msg.includes("abuse detection")) {
20707
20727
  throw err2;
20708
20728
  }
@@ -20794,6 +20814,9 @@ var init_pr_monitor = __esm({
20794
20814
  const { ciStatus, hasMergeConflict, hasUnrespondedComment, hasIncompleteChecklist, reviewDecision, daysSinceActivity, dormantThreshold, approachingThreshold, latestCommitDate, lastMaintainerCommentDate, latestChangesRequestedDate } = input;
20795
20815
  if (hasUnrespondedComment) {
20796
20816
  if (latestCommitDate && lastMaintainerCommentDate && latestCommitDate > lastMaintainerCommentDate) {
20817
+ if (latestChangesRequestedDate && latestCommitDate < latestChangesRequestedDate) {
20818
+ return "needs_response";
20819
+ }
20797
20820
  if (ciStatus === "failing")
20798
20821
  return "failing_ci";
20799
20822
  return "changes_addressed";
@@ -20854,7 +20877,7 @@ var init_pr_monitor = __esm({
20854
20877
  this.octokit.repos.getCombinedStatusForRef({ owner, repo, ref: sha }),
20855
20878
  // 404 is expected for repos without check runs configured; log other errors for debugging
20856
20879
  this.octokit.checks.listForRef({ owner, repo, ref: sha }).catch((err2) => {
20857
- const status = err2?.status;
20880
+ const status = getHttpStatusCode(err2);
20858
20881
  if (status === 404) {
20859
20882
  debug("pr-monitor", `Check runs 404 for ${owner}/${repo}@${sha.slice(0, 7)} (no checks configured)`);
20860
20883
  } else {
@@ -20877,8 +20900,8 @@ var init_pr_monitor = __esm({
20877
20900
  const combinedAnalysis = analyzeCombinedStatus(combinedStatus);
20878
20901
  return mergeStatuses(checkRunAnalysis, combinedAnalysis, checkRuns.length);
20879
20902
  } catch (error2) {
20880
- const statusCode = error2.status;
20881
- const errorMessage2 = error2 instanceof Error ? error2.message : String(error2);
20903
+ const statusCode = getHttpStatusCode(error2);
20904
+ const errMsg = errorMessage(error2);
20882
20905
  if (statusCode === 401) {
20883
20906
  warn("pr-monitor", `CI check failed for ${owner}/${repo}: Invalid token`);
20884
20907
  } else if (statusCode === 403) {
@@ -20887,7 +20910,7 @@ var init_pr_monitor = __esm({
20887
20910
  debug("pr-monitor", `CI check 404 for ${owner}/${repo} (no CI configured)`);
20888
20911
  return { status: "unknown", failingCheckNames: [], failingCheckConclusions: /* @__PURE__ */ new Map() };
20889
20912
  } else {
20890
- warn("pr-monitor", `Failed to check CI for ${owner}/${repo}@${sha.slice(0, 7)}: ${errorMessage2}`);
20913
+ warn("pr-monitor", `Failed to check CI for ${owner}/${repo}@${sha.slice(0, 7)}: ${errMsg}`);
20891
20914
  }
20892
20915
  return { status: "unknown", failingCheckNames: [], failingCheckConclusions: /* @__PURE__ */ new Map() };
20893
20916
  }
@@ -20943,7 +20966,7 @@ var init_pr_monitor = __esm({
20943
20966
  results.set(result.value.repo, result.value.stars);
20944
20967
  } else {
20945
20968
  chunkFailures++;
20946
- warn(MODULE7, `Failed to fetch stars for ${chunk[j]}: ${result.reason instanceof Error ? result.reason.message : result.reason}`);
20969
+ warn(MODULE7, `Failed to fetch stars for ${chunk[j]}: ${errorMessage(result.reason)}`);
20947
20970
  }
20948
20971
  }
20949
20972
  if (chunkFailures === chunk.length && chunk.length > 0) {
@@ -20957,45 +20980,6 @@ var init_pr_monitor = __esm({
20957
20980
  debug(MODULE7, `Fetched star counts for ${results.size}/${repos.length} repos`);
20958
20981
  return results;
20959
20982
  }
20960
- /**
20961
- * Shared helper: search for recent PRs and filter out own repos, excluded repos/orgs.
20962
- * Returns parsed search results that pass all filters.
20963
- */
20964
- async fetchRecentPRs(query, label, days, mapItem) {
20965
- const config2 = this.stateManager.getState().config;
20966
- if (!config2.githubUsername) {
20967
- warn(MODULE7, `Skipping recently ${label} PRs fetch: no githubUsername configured. Run /setup-oss to configure.`);
20968
- return [];
20969
- }
20970
- const sinceDate = /* @__PURE__ */ new Date();
20971
- sinceDate.setDate(sinceDate.getDate() - days);
20972
- const since = sinceDate.toISOString().split("T")[0];
20973
- debug(MODULE7, `Fetching recently ${label} PRs for @${config2.githubUsername} (since ${since})...`);
20974
- const { data } = await this.octokit.search.issuesAndPullRequests({
20975
- q: query.replace("{username}", config2.githubUsername).replace("{since}", since),
20976
- sort: "updated",
20977
- order: "desc",
20978
- per_page: 100
20979
- });
20980
- const results = [];
20981
- for (const item of data.items) {
20982
- const parsed = parseGitHubUrl(item.html_url);
20983
- if (!parsed) {
20984
- warn(MODULE7, `Could not parse GitHub URL from API response: ${item.html_url}`);
20985
- continue;
20986
- }
20987
- const repo = `${parsed.owner}/${parsed.repo}`;
20988
- if (parsed.owner.toLowerCase() === config2.githubUsername.toLowerCase())
20989
- continue;
20990
- if (config2.excludeRepos.includes(repo))
20991
- continue;
20992
- if (config2.excludeOrgs?.some((org) => parsed.owner.toLowerCase() === org.toLowerCase()))
20993
- continue;
20994
- results.push(mapItem(item, { owner: parsed.owner, repo, number: parsed.number }));
20995
- }
20996
- debug(MODULE7, `Found ${results.length} recently ${label} PRs`);
20997
- return results;
20998
- }
20999
20983
  /**
21000
20984
  * Fetch PRs closed without merge in the last N days.
21001
20985
  * Delegates to github-stats module.
@@ -21432,7 +21416,7 @@ var init_issue_vetting = __esm({
21432
21416
  if (_IssueVetter.isRateLimitError(error2)) {
21433
21417
  rateLimitFailures++;
21434
21418
  }
21435
- warn(MODULE8, `Error vetting issue ${url}:`, error2 instanceof Error ? error2.message : error2);
21419
+ warn(MODULE8, `Error vetting issue ${url}:`, errorMessage(error2));
21436
21420
  });
21437
21421
  pending.push(task);
21438
21422
  if (pending.length >= MAX_CONCURRENT_REQUESTS2) {
@@ -21449,11 +21433,11 @@ var init_issue_vetting = __esm({
21449
21433
  }
21450
21434
  /** Check if an error is a GitHub rate limit error (429 or rate-limit 403). */
21451
21435
  static isRateLimitError(error2) {
21452
- const status = error2?.status;
21436
+ const status = getHttpStatusCode(error2);
21453
21437
  if (status === 429)
21454
21438
  return true;
21455
21439
  if (status === 403) {
21456
- const msg = error2 instanceof Error ? error2.message.toLowerCase() : String(error2).toLowerCase();
21440
+ const msg = errorMessage(error2).toLowerCase();
21457
21441
  return msg.includes("rate limit");
21458
21442
  }
21459
21443
  return false;
@@ -21477,9 +21461,9 @@ var init_issue_vetting = __esm({
21477
21461
  });
21478
21462
  return { passed: data.total_count === 0 && linkedPRs.length === 0 };
21479
21463
  } catch (error2) {
21480
- const errorMessage2 = error2 instanceof Error ? error2.message : String(error2);
21481
- warn(MODULE8, `Failed to check for existing PRs on ${owner}/${repo}#${issueNumber}: ${errorMessage2}. Assuming no existing PR.`);
21482
- return { passed: true, inconclusive: true, reason: errorMessage2 };
21464
+ const errMsg = errorMessage(error2);
21465
+ warn(MODULE8, `Failed to check for existing PRs on ${owner}/${repo}#${issueNumber}: ${errMsg}. Assuming no existing PR.`);
21466
+ return { passed: true, inconclusive: true, reason: errMsg };
21483
21467
  }
21484
21468
  }
21485
21469
  /**
@@ -21495,8 +21479,8 @@ var init_issue_vetting = __esm({
21495
21479
  });
21496
21480
  return data.total_count;
21497
21481
  } catch (error2) {
21498
- const errorMessage2 = error2 instanceof Error ? error2.message : String(error2);
21499
- warn(MODULE8, `Could not check merged PRs in ${owner}/${repo}: ${errorMessage2}. Defaulting to 0.`);
21482
+ const errMsg = errorMessage(error2);
21483
+ warn(MODULE8, `Could not check merged PRs in ${owner}/${repo}: ${errMsg}. Defaulting to 0.`);
21500
21484
  return 0;
21501
21485
  }
21502
21486
  }
@@ -21536,9 +21520,9 @@ var init_issue_vetting = __esm({
21536
21520
  }
21537
21521
  return { passed: true };
21538
21522
  } catch (error2) {
21539
- const errorMessage2 = error2 instanceof Error ? error2.message : String(error2);
21540
- warn(MODULE8, `Failed to check claim status on ${owner}/${repo}#${issueNumber}: ${errorMessage2}. Assuming not claimed.`);
21541
- return { passed: true, inconclusive: true, reason: errorMessage2 };
21523
+ const errMsg = errorMessage(error2);
21524
+ warn(MODULE8, `Failed to check claim status on ${owner}/${repo}#${issueNumber}: ${errMsg}. Assuming not claimed.`);
21525
+ return { passed: true, inconclusive: true, reason: errMsg };
21542
21526
  }
21543
21527
  }
21544
21528
  async checkProjectHealth(owner, repo) {
@@ -21565,8 +21549,8 @@ var init_issue_vetting = __esm({
21565
21549
  ciStatus = "passing";
21566
21550
  }
21567
21551
  } catch (error2) {
21568
- const errorMessage2 = error2 instanceof Error ? error2.message : String(error2);
21569
- warn(MODULE8, `Failed to check CI status for ${owner}/${repo}: ${errorMessage2}. Defaulting to unknown.`);
21552
+ const errMsg = errorMessage(error2);
21553
+ warn(MODULE8, `Failed to check CI status for ${owner}/${repo}: ${errMsg}. Defaulting to unknown.`);
21570
21554
  }
21571
21555
  return {
21572
21556
  repo: `${owner}/${repo}`,
@@ -21581,8 +21565,8 @@ var init_issue_vetting = __esm({
21581
21565
  forksCount: repoData.forks_count
21582
21566
  };
21583
21567
  } catch (error2) {
21584
- const errorMessage2 = error2 instanceof Error ? error2.message : String(error2);
21585
- warn(MODULE8, `Error checking project health for ${owner}/${repo}: ${errorMessage2}`);
21568
+ const errMsg = errorMessage(error2);
21569
+ warn(MODULE8, `Error checking project health for ${owner}/${repo}: ${errMsg}`);
21586
21570
  return {
21587
21571
  repo: `${owner}/${repo}`,
21588
21572
  lastCommitAt: "",
@@ -21592,7 +21576,7 @@ var init_issue_vetting = __esm({
21592
21576
  ciStatus: "unknown",
21593
21577
  isActive: false,
21594
21578
  checkFailed: true,
21595
- failureReason: errorMessage2
21579
+ failureReason: errMsg
21596
21580
  };
21597
21581
  }
21598
21582
  }
@@ -21723,7 +21707,7 @@ var init_issue_discovery = __esm({
21723
21707
  * Updates the state manager with the list and timestamp.
21724
21708
  */
21725
21709
  async fetchStarredRepos() {
21726
- console.log("Fetching starred repositories...");
21710
+ info(MODULE9, "Fetching starred repositories...");
21727
21711
  const starredRepos = [];
21728
21712
  try {
21729
21713
  const iterator2 = this.octokit.paginate.iterator(this.octokit.activity.listReposStarredByAuthenticatedUser, {
@@ -21744,22 +21728,22 @@ var init_issue_discovery = __esm({
21744
21728
  }
21745
21729
  pageCount++;
21746
21730
  if (pageCount >= 5) {
21747
- console.log("Reached pagination limit for starred repos (500)");
21731
+ info(MODULE9, "Reached pagination limit for starred repos (500)");
21748
21732
  break;
21749
21733
  }
21750
21734
  }
21751
- console.log(`Fetched ${starredRepos.length} starred repositories`);
21735
+ info(MODULE9, `Fetched ${starredRepos.length} starred repositories`);
21752
21736
  this.stateManager.setStarredRepos(starredRepos);
21753
21737
  return starredRepos;
21754
21738
  } catch (error2) {
21755
21739
  const cachedRepos = this.stateManager.getStarredRepos();
21756
- const errorMessage2 = error2 instanceof Error ? error2.message : String(error2);
21757
- warn(MODULE9, "Error fetching starred repos:", errorMessage2);
21740
+ const errMsg = errorMessage(error2);
21741
+ warn(MODULE9, "Error fetching starred repos:", errMsg);
21758
21742
  if (cachedRepos.length === 0) {
21759
- warn(MODULE9, `Failed to fetch starred repositories from GitHub API. No cached repos available. Error: ${errorMessage2}
21743
+ warn(MODULE9, `Failed to fetch starred repositories from GitHub API. No cached repos available. Error: ${errMsg}
21760
21744
  Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`);
21761
21745
  } else {
21762
- warn(MODULE9, `Failed to fetch starred repositories from GitHub API. Using ${cachedRepos.length} cached repos instead. Error: ${errorMessage2}`);
21746
+ warn(MODULE9, `Failed to fetch starred repositories from GitHub API. Using ${cachedRepos.length} cached repos instead. Error: ${errMsg}`);
21763
21747
  }
21764
21748
  return cachedRepos;
21765
21749
  }
@@ -21773,6 +21757,39 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`);
21773
21757
  }
21774
21758
  return this.stateManager.getStarredRepos();
21775
21759
  }
21760
+ /**
21761
+ * Shared pipeline for Phases 2 and 3: spam-filter, repo-exclusion, vetting, and star-count filter.
21762
+ * Extracts the common logic so each phase only needs to supply search results and context.
21763
+ */
21764
+ async filterVetAndScore(items, filterIssues, excludedRepoSets, remainingNeeded, minStars, phaseLabel) {
21765
+ const spamRepos = detectLabelFarmingRepos(items);
21766
+ if (spamRepos.size > 0) {
21767
+ const spamCount = items.filter((i) => spamRepos.has(i.repository_url.split("/").slice(-2).join("/"))).length;
21768
+ debug(MODULE9, `[SPAM_FILTER] Filtered ${spamCount} issues from ${spamRepos.size} label-farming repos: ${[...spamRepos].join(", ")}`);
21769
+ }
21770
+ const itemsToVet = filterIssues(items).filter((item) => {
21771
+ const repoFullName = item.repository_url.split("/").slice(-2).join("/");
21772
+ if (spamRepos.has(repoFullName))
21773
+ return false;
21774
+ return excludedRepoSets.every((s) => !s.has(repoFullName));
21775
+ }).slice(0, remainingNeeded * 2);
21776
+ if (itemsToVet.length === 0) {
21777
+ debug(MODULE9, `[${phaseLabel}] All ${items.length} items filtered before vetting`);
21778
+ return { candidates: [], allVetFailed: false, rateLimitHit: false };
21779
+ }
21780
+ const { candidates: results, allFailed: allVetFailed, rateLimitHit } = await this.vetter.vetIssuesParallel(itemsToVet.map((i) => i.html_url), remainingNeeded, "normal");
21781
+ const starFiltered = results.filter((c) => {
21782
+ if (c.projectHealth.checkFailed)
21783
+ return true;
21784
+ const stars = c.projectHealth.stargazersCount ?? 0;
21785
+ return stars >= minStars;
21786
+ });
21787
+ const starFilteredCount = results.length - starFiltered.length;
21788
+ if (starFilteredCount > 0) {
21789
+ debug(MODULE9, `[STAR_FILTER] Filtered ${starFilteredCount} ${phaseLabel} candidates below ${minStars} stars`);
21790
+ }
21791
+ return { candidates: starFiltered, allVetFailed, rateLimitHit };
21792
+ }
21776
21793
  /**
21777
21794
  * Search for issues matching our criteria.
21778
21795
  * Searches in priority order: merged-PR repos first (no label filter), then starred repos,
@@ -21784,6 +21801,7 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`);
21784
21801
  const languages = options.languages || config2.languages;
21785
21802
  const labels = options.labels || config2.labels;
21786
21803
  const maxResults = options.maxResults || 10;
21804
+ const minStars = config2.minStars ?? 50;
21787
21805
  const allCandidates = [];
21788
21806
  let phase0Error = null;
21789
21807
  let phase1Error = null;
@@ -21797,10 +21815,10 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`);
21797
21815
  warn(MODULE9, this.rateLimitWarning);
21798
21816
  }
21799
21817
  } catch (error2) {
21800
- if (error2?.status === 401) {
21818
+ if (getHttpStatusCode(error2) === 401) {
21801
21819
  throw error2;
21802
21820
  }
21803
- warn(MODULE9, "Could not check rate limit:", error2 instanceof Error ? error2.message : error2);
21821
+ warn(MODULE9, "Could not check rate limit:", errorMessage(error2));
21804
21822
  }
21805
21823
  const mergedPRRepos = this.stateManager.getReposWithMergedPRs();
21806
21824
  const mergedPRRepoSet = new Set(mergedPRRepos);
@@ -21820,7 +21838,7 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`);
21820
21838
  const includeDocIssues = config2.includeDocIssues ?? true;
21821
21839
  const aiBlocklisted = new Set(config2.aiPolicyBlocklist ?? DEFAULT_CONFIG.aiPolicyBlocklist ?? []);
21822
21840
  if (aiBlocklisted.size > 0) {
21823
- console.log(`[AI_POLICY_FILTER] Filtering issues from ${aiBlocklisted.size} blocklisted repo(s): ${[...aiBlocklisted].join(", ")}`);
21841
+ debug(MODULE9, `[AI_POLICY_FILTER] Filtering issues from ${aiBlocklisted.size} blocklisted repo(s): ${[...aiBlocklisted].join(", ")}`);
21824
21842
  }
21825
21843
  const filterIssues = (items) => {
21826
21844
  return items.filter((item) => {
@@ -21847,7 +21865,7 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`);
21847
21865
  if (phase0Repos.length > 0) {
21848
21866
  const mergedInPhase0 = Math.min(mergedPRRepos.length, phase0Repos.length);
21849
21867
  const openInPhase0 = phase0Repos.length - mergedInPhase0;
21850
- console.log(`Phase 0: Searching issues in ${phase0Repos.length} repos (${mergedInPhase0} merged-PR, ${openInPhase0} open-PR, no label filter)...`);
21868
+ info(MODULE9, `Phase 0: Searching issues in ${phase0Repos.length} repos (${mergedInPhase0} merged-PR, ${openInPhase0} open-PR, no label filter)...`);
21851
21869
  const mergedPhase0Repos = phase0Repos.slice(0, mergedInPhase0);
21852
21870
  if (mergedPhase0Repos.length > 0) {
21853
21871
  const remainingNeeded = maxResults - allCandidates.length;
@@ -21860,7 +21878,7 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`);
21860
21878
  if (rateLimitHit) {
21861
21879
  rateLimitHitDuringSearch = true;
21862
21880
  }
21863
- console.log(`Found ${mergedCandidates.length} candidates from merged-PR repos`);
21881
+ info(MODULE9, `Found ${mergedCandidates.length} candidates from merged-PR repos`);
21864
21882
  }
21865
21883
  }
21866
21884
  const openPhase0Repos = phase0Repos.slice(mergedInPhase0);
@@ -21876,14 +21894,14 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`);
21876
21894
  if (rateLimitHit) {
21877
21895
  rateLimitHitDuringSearch = true;
21878
21896
  }
21879
- console.log(`Found ${openCandidates.length} candidates from open-PR repos`);
21897
+ info(MODULE9, `Found ${openCandidates.length} candidates from open-PR repos`);
21880
21898
  }
21881
21899
  }
21882
21900
  }
21883
21901
  if (allCandidates.length < maxResults && starredRepos.length > 0) {
21884
21902
  const reposToSearch = starredRepos.filter((r) => !phase0RepoSet.has(r));
21885
21903
  if (reposToSearch.length > 0) {
21886
- console.log(`Phase 1: Searching issues in ${reposToSearch.length} starred repos...`);
21904
+ info(MODULE9, `Phase 1: Searching issues in ${reposToSearch.length} starred repos...`);
21887
21905
  const remainingNeeded = maxResults - allCandidates.length;
21888
21906
  if (remainingNeeded > 0) {
21889
21907
  const { candidates: starredCandidates, allBatchesFailed, rateLimitHit } = await this.searchInRepos(reposToSearch.slice(0, 10), baseQuery, remainingNeeded, "starred", filterIssues);
@@ -21894,13 +21912,13 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`);
21894
21912
  if (rateLimitHit) {
21895
21913
  rateLimitHitDuringSearch = true;
21896
21914
  }
21897
- console.log(`Found ${starredCandidates.length} candidates from starred repos`);
21915
+ info(MODULE9, `Found ${starredCandidates.length} candidates from starred repos`);
21898
21916
  }
21899
21917
  }
21900
21918
  }
21901
21919
  let phase2Error = null;
21902
21920
  if (allCandidates.length < maxResults) {
21903
- console.log("Phase 2: General issue search...");
21921
+ info(MODULE9, "Phase 2: General issue search...");
21904
21922
  const remainingNeeded = maxResults - allCandidates.length;
21905
21923
  try {
21906
21924
  const { data } = await this.octokit.search.issuesAndPullRequests({
@@ -21910,32 +21928,9 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`);
21910
21928
  per_page: remainingNeeded * 3
21911
21929
  // Fetch extra since some will be filtered
21912
21930
  });
21913
- console.log(`Found ${data.total_count} issues in general search, processing top ${data.items.length}...`);
21914
- const spamRepos = detectLabelFarmingRepos(data.items);
21915
- if (spamRepos.size > 0) {
21916
- const spamCount = data.items.filter((i) => spamRepos.has(i.repository_url.split("/").slice(-2).join("/"))).length;
21917
- console.log(`[SPAM_FILTER] Filtered ${spamCount} issues from ${spamRepos.size} label-farming repos: ${[...spamRepos].join(", ")}`);
21918
- }
21931
+ info(MODULE9, `Found ${data.total_count} issues in general search, processing top ${data.items.length}...`);
21919
21932
  const seenRepos = new Set(allCandidates.map((c) => c.issue.repo));
21920
- const itemsToVet = filterIssues(data.items).filter((item) => {
21921
- const repoFullName = item.repository_url.split("/").slice(-2).join("/");
21922
- return !spamRepos.has(repoFullName);
21923
- }).filter((item) => {
21924
- const repoFullName = item.repository_url.split("/").slice(-2).join("/");
21925
- return !phase0RepoSet.has(repoFullName) && !starredRepoSet.has(repoFullName) && !seenRepos.has(repoFullName);
21926
- }).slice(0, remainingNeeded * 2);
21927
- const { candidates: results, allFailed: allVetFailed, rateLimitHit: vetRateLimitHit } = await this.vetter.vetIssuesParallel(itemsToVet.map((i) => i.html_url), remainingNeeded, "normal");
21928
- const minStars = config2.minStars ?? 50;
21929
- const starFiltered = results.filter((c) => {
21930
- if (c.projectHealth.checkFailed)
21931
- return true;
21932
- const stars = c.projectHealth.stargazersCount ?? 0;
21933
- return stars >= minStars;
21934
- });
21935
- const starFilteredCount = results.length - starFiltered.length;
21936
- if (starFilteredCount > 0) {
21937
- console.log(`[STAR_FILTER] Filtered ${starFilteredCount} candidates below ${minStars} stars`);
21938
- }
21933
+ const { candidates: starFiltered, allVetFailed, rateLimitHit: vetRateLimitHit } = await this.filterVetAndScore(data.items, filterIssues, [phase0RepoSet, starredRepoSet, seenRepos], remainingNeeded, minStars, "Phase 2");
21939
21934
  allCandidates.push(...starFiltered);
21940
21935
  if (allVetFailed) {
21941
21936
  phase2Error = (phase2Error ? phase2Error + "; " : "") + "all vetting failed";
@@ -21943,25 +21938,24 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`);
21943
21938
  if (vetRateLimitHit) {
21944
21939
  rateLimitHitDuringSearch = true;
21945
21940
  }
21946
- console.log(`Found ${starFiltered.length} candidates from general search`);
21941
+ info(MODULE9, `Found ${starFiltered.length} candidates from general search`);
21947
21942
  } catch (error2) {
21948
- const errorMessage2 = error2 instanceof Error ? error2.message : String(error2);
21949
- phase2Error = errorMessage2;
21943
+ const errMsg = errorMessage(error2);
21944
+ phase2Error = errMsg;
21950
21945
  if (IssueVetter.isRateLimitError(error2)) {
21951
21946
  rateLimitHitDuringSearch = true;
21952
21947
  }
21953
- warn(MODULE9, `Error in general issue search: ${errorMessage2}`);
21948
+ warn(MODULE9, `Error in general issue search: ${errMsg}`);
21954
21949
  }
21955
21950
  }
21956
21951
  let phase3Error = null;
21957
21952
  if (allCandidates.length < maxResults) {
21958
- console.log("Phase 3: Searching actively maintained repos...");
21953
+ info(MODULE9, "Phase 3: Searching actively maintained repos...");
21959
21954
  const remainingNeeded = maxResults - allCandidates.length;
21960
21955
  const thirtyDaysAgo = /* @__PURE__ */ new Date();
21961
21956
  thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
21962
21957
  const pushedSince = thirtyDaysAgo.toISOString().split("T")[0];
21963
- const phase3MinStars = config2.minStars ?? 50;
21964
- const phase3Query = `is:issue is:open no:assignee ${langQuery} stars:>=${phase3MinStars} pushed:>=${pushedSince} archived:false`.replace(/ +/g, " ").trim();
21958
+ const phase3Query = `is:issue is:open no:assignee ${langQuery} stars:>=${minStars} pushed:>=${pushedSince} archived:false`.replace(/ +/g, " ").trim();
21965
21959
  try {
21966
21960
  const { data } = await this.octokit.search.issuesAndPullRequests({
21967
21961
  q: phase3Query,
@@ -21969,29 +21963,9 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`);
21969
21963
  order: "desc",
21970
21964
  per_page: remainingNeeded * 3
21971
21965
  });
21972
- console.log(`Found ${data.total_count} issues in maintained-repo search, processing top ${data.items.length}...`);
21973
- const spamRepos = detectLabelFarmingRepos(data.items);
21974
- if (spamRepos.size > 0) {
21975
- const spamCount = data.items.filter((i) => spamRepos.has(i.repository_url.split("/").slice(-2).join("/"))).length;
21976
- console.log(`[SPAM_FILTER] Filtered ${spamCount} issues from ${spamRepos.size} label-farming repos: ${[...spamRepos].join(", ")}`);
21977
- }
21966
+ info(MODULE9, `Found ${data.total_count} issues in maintained-repo search, processing top ${data.items.length}...`);
21978
21967
  const seenRepos = new Set(allCandidates.map((c) => c.issue.repo));
21979
- const itemsToVet = filterIssues(data.items).filter((item) => {
21980
- const repoFullName = item.repository_url.split("/").slice(-2).join("/");
21981
- return !spamRepos.has(repoFullName) && !phase0RepoSet.has(repoFullName) && !starredRepoSet.has(repoFullName) && !seenRepos.has(repoFullName);
21982
- }).slice(0, remainingNeeded * 2);
21983
- const { candidates: results, allFailed: allVetFailed, rateLimitHit: vetRateLimitHit } = await this.vetter.vetIssuesParallel(itemsToVet.map((i) => i.html_url), remainingNeeded, "normal");
21984
- const minStars = config2.minStars ?? 50;
21985
- const starFiltered = results.filter((c) => {
21986
- if (c.projectHealth.checkFailed)
21987
- return true;
21988
- const stars = c.projectHealth.stargazersCount ?? 0;
21989
- return stars >= minStars;
21990
- });
21991
- const starFilteredCount = results.length - starFiltered.length;
21992
- if (starFilteredCount > 0) {
21993
- console.log(`[STAR_FILTER] Filtered ${starFilteredCount} Phase 3 candidates below ${minStars} stars`);
21994
- }
21968
+ const { candidates: starFiltered, allVetFailed, rateLimitHit: vetRateLimitHit } = await this.filterVetAndScore(data.items, filterIssues, [phase0RepoSet, starredRepoSet, seenRepos], remainingNeeded, minStars, "Phase 3");
21995
21969
  allCandidates.push(...starFiltered);
21996
21970
  if (allVetFailed) {
21997
21971
  phase3Error = "all vetting failed";
@@ -21999,14 +21973,14 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`);
21999
21973
  if (vetRateLimitHit) {
22000
21974
  rateLimitHitDuringSearch = true;
22001
21975
  }
22002
- console.log(`Found ${starFiltered.length} candidates from maintained-repo search`);
21976
+ info(MODULE9, `Found ${starFiltered.length} candidates from maintained-repo search`);
22003
21977
  } catch (error2) {
22004
- const errorMessage2 = error2 instanceof Error ? error2.message : String(error2);
22005
- phase3Error = errorMessage2;
21978
+ const errMsg = errorMessage(error2);
21979
+ phase3Error = errMsg;
22006
21980
  if (IssueVetter.isRateLimitError(error2)) {
22007
21981
  rateLimitHitDuringSearch = true;
22008
21982
  }
22009
- warn(MODULE9, `Error in maintained-repo search: ${errorMessage2}`);
21983
+ warn(MODULE9, `Error in maintained-repo search: ${errMsg}`);
22010
21984
  }
22011
21985
  }
22012
21986
  if (allCandidates.length === 0) {
@@ -22079,7 +22053,7 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`);
22079
22053
  rateLimitFailures++;
22080
22054
  }
22081
22055
  const batchRepos = batch.join(", ");
22082
- warn(MODULE9, `Error searching issues in batch [${batchRepos}]:`, error2 instanceof Error ? error2.message : error2);
22056
+ warn(MODULE9, `Error searching issues in batch [${batchRepos}]:`, errorMessage(error2));
22083
22057
  }
22084
22058
  }
22085
22059
  const allBatchesFailed = failedBatches === batches.length && batches.length > 0;
@@ -22164,7 +22138,7 @@ Tip: Ensure your GITHUB_TOKEN has the 'read:user' scope and try again.`);
22164
22138
  content += `- **Recommendation**: Y = approve, N = skip, ? = needs_review
22165
22139
  `;
22166
22140
  fs4.writeFileSync(outputFile, content, "utf-8");
22167
- console.log(`Saved ${sorted.length} issues to ${outputFile}`);
22141
+ info(MODULE9, `Saved ${sorted.length} issues to ${outputFile}`);
22168
22142
  return outputFile;
22169
22143
  }
22170
22144
  /**
@@ -22266,7 +22240,7 @@ var init_issue_conversation = __esm({
22266
22240
  }
22267
22241
  const { owner, repo } = parsed;
22268
22242
  const repoFullName = `${owner}/${repo}`;
22269
- if (owner.toLowerCase() === username.toLowerCase())
22243
+ if (isOwnRepo(owner, username))
22270
22244
  continue;
22271
22245
  if (item.user?.login?.toLowerCase() === username.toLowerCase())
22272
22246
  continue;
@@ -22295,7 +22269,7 @@ var init_issue_conversation = __esm({
22295
22269
  });
22296
22270
  }
22297
22271
  } catch (error2) {
22298
- const msg = error2 instanceof Error ? error2.message : String(error2);
22272
+ const msg = errorMessage(error2);
22299
22273
  failures.push({ issueUrl: item.html_url, error: msg });
22300
22274
  warn(MODULE10, `Error analyzing issue ${item.html_url}: ${msg}`);
22301
22275
  }
@@ -22725,6 +22699,7 @@ var init_core3 = __esm({
22725
22699
  init_comment_utils();
22726
22700
  init_github();
22727
22701
  init_utils();
22702
+ init_errors3();
22728
22703
  init_logger();
22729
22704
  init_http_cache();
22730
22705
  init_daily_logic();
@@ -22789,15 +22764,15 @@ async function fetchPRData(prMonitor, token) {
22789
22764
  prMonitor.fetchUserMergedPRCounts(),
22790
22765
  prMonitor.fetchUserClosedPRCounts(),
22791
22766
  prMonitor.fetchRecentlyClosedPRs().catch((err2) => {
22792
- console.error(`Warning: Failed to fetch recently closed PRs: ${err2 instanceof Error ? err2.message : err2}`);
22767
+ console.error(`Warning: Failed to fetch recently closed PRs: ${errorMessage(err2)}`);
22793
22768
  return [];
22794
22769
  }),
22795
22770
  prMonitor.fetchRecentlyMergedPRs().catch((err2) => {
22796
- console.error(`Warning: Failed to fetch recently merged PRs: ${err2 instanceof Error ? err2.message : err2}`);
22771
+ console.error(`Warning: Failed to fetch recently merged PRs: ${errorMessage(err2)}`);
22797
22772
  return [];
22798
22773
  }),
22799
22774
  issueMonitor.fetchCommentedIssues().catch((error2) => {
22800
- const msg = error2 instanceof Error ? error2.message : String(error2);
22775
+ const msg = errorMessage(error2);
22801
22776
  if (msg.includes("No GitHub username configured")) {
22802
22777
  console.error(`[DAILY] Issue conversation tracking requires setup: ${msg}`);
22803
22778
  } else {
@@ -22847,7 +22822,7 @@ async function updateRepoScores(prMonitor, prs, mergedCounts, closedCounts) {
22847
22822
  stateManager2.updateRepoScore(repo, { mergedPRCount: count, lastMergedAt: lastMergedAt || void 0 });
22848
22823
  } catch (error2) {
22849
22824
  mergedCountFailures++;
22850
- console.error(`[DAILY] Failed to update merged count for ${repo}:`, error2 instanceof Error ? error2.message : error2);
22825
+ console.error(`[DAILY] Failed to update merged count for ${repo}:`, errorMessage(error2));
22851
22826
  }
22852
22827
  }
22853
22828
  if (mergedCountFailures === mergedCounts.size && mergedCounts.size > 0) {
@@ -22863,7 +22838,7 @@ async function updateRepoScores(prMonitor, prs, mergedCounts, closedCounts) {
22863
22838
  stateManager2.updateRepoScore(repo, { closedWithoutMergeCount: count });
22864
22839
  } catch (error2) {
22865
22840
  closedCountFailures++;
22866
- console.error(`[DAILY] Failed to update closed count for ${repo}:`, error2 instanceof Error ? error2.message : error2);
22841
+ console.error(`[DAILY] Failed to update closed count for ${repo}:`, errorMessage(error2));
22867
22842
  }
22868
22843
  }
22869
22844
  if (closedCountFailures === closedCounts.size && closedCounts.size > 0) {
@@ -22876,7 +22851,7 @@ async function updateRepoScores(prMonitor, prs, mergedCounts, closedCounts) {
22876
22851
  stateManager2.updateRepoScore(repo, { signals });
22877
22852
  } catch (error2) {
22878
22853
  signalUpdateFailures++;
22879
- console.error(`[DAILY] Failed to update signals for ${repo}:`, error2 instanceof Error ? error2.message : error2);
22854
+ console.error(`[DAILY] Failed to update signals for ${repo}:`, errorMessage(error2));
22880
22855
  }
22881
22856
  }
22882
22857
  if (signalUpdateFailures === repoSignals.size && repoSignals.size > 0) {
@@ -22887,7 +22862,7 @@ async function updateRepoScores(prMonitor, prs, mergedCounts, closedCounts) {
22887
22862
  try {
22888
22863
  starCounts = await prMonitor.fetchRepoStarCounts(allRepos);
22889
22864
  } catch (error2) {
22890
- console.error("[DAILY] Failed to fetch repo star counts:", error2 instanceof Error ? error2.message : error2);
22865
+ console.error("[DAILY] Failed to fetch repo star counts:", errorMessage(error2));
22891
22866
  console.error("[DAILY] Dashboard minStars filter will use cached star counts (or be skipped for repos without cached data).");
22892
22867
  starCounts = /* @__PURE__ */ new Map();
22893
22868
  }
@@ -22897,7 +22872,7 @@ async function updateRepoScores(prMonitor, prs, mergedCounts, closedCounts) {
22897
22872
  stateManager2.updateRepoScore(repo, { stargazersCount: stars });
22898
22873
  } catch (error2) {
22899
22874
  starUpdateFailures++;
22900
- console.error(`[DAILY] Failed to update star count for ${repo}:`, error2 instanceof Error ? error2.message : error2);
22875
+ console.error(`[DAILY] Failed to update star count for ${repo}:`, errorMessage(error2));
22901
22876
  }
22902
22877
  }
22903
22878
  if (starUpdateFailures === starCounts.size && starCounts.size > 0) {
@@ -22909,7 +22884,7 @@ async function updateRepoScores(prMonitor, prs, mergedCounts, closedCounts) {
22909
22884
  stateManager2.addTrustedProject(repo);
22910
22885
  } catch (error2) {
22911
22886
  trustSyncFailures++;
22912
- console.error(`[DAILY] Failed to sync trusted project ${repo}:`, error2 instanceof Error ? error2.message : error2);
22887
+ console.error(`[DAILY] Failed to sync trusted project ${repo}:`, errorMessage(error2));
22913
22888
  }
22914
22889
  }
22915
22890
  if (trustSyncFailures === mergedCounts.size && mergedCounts.size > 0) {
@@ -22921,12 +22896,12 @@ function updateAnalytics(prs, monthlyCounts, monthlyClosedCounts, openedFromMerg
22921
22896
  try {
22922
22897
  stateManager2.setMonthlyMergedCounts(monthlyCounts);
22923
22898
  } catch (error2) {
22924
- console.error("[DAILY] Failed to store monthly merged counts:", error2 instanceof Error ? error2.message : error2);
22899
+ console.error("[DAILY] Failed to store monthly merged counts:", errorMessage(error2));
22925
22900
  }
22926
22901
  try {
22927
22902
  stateManager2.setMonthlyClosedCounts(monthlyClosedCounts);
22928
22903
  } catch (error2) {
22929
- console.error("[DAILY] Failed to store monthly closed counts:", error2 instanceof Error ? error2.message : error2);
22904
+ console.error("[DAILY] Failed to store monthly closed counts:", errorMessage(error2));
22930
22905
  }
22931
22906
  try {
22932
22907
  const combinedOpenedCounts = { ...openedFromMerged };
@@ -22941,7 +22916,7 @@ function updateAnalytics(prs, monthlyCounts, monthlyClosedCounts, openedFromMerg
22941
22916
  }
22942
22917
  stateManager2.setMonthlyOpenedCounts(combinedOpenedCounts);
22943
22918
  } catch (error2) {
22944
- console.error("[DAILY] Failed to compute/store monthly opened counts:", error2 instanceof Error ? error2.message : error2);
22919
+ console.error("[DAILY] Failed to compute/store monthly opened counts:", errorMessage(error2));
22945
22920
  }
22946
22921
  }
22947
22922
  function partitionPRs(prMonitor, prs, recentlyClosedPRs, recentlyMergedPRs) {
@@ -22956,7 +22931,7 @@ function partitionPRs(prMonitor, prs, recentlyClosedPRs, recentlyMergedPRs) {
22956
22931
  stateManager2.save();
22957
22932
  }
22958
22933
  } catch (error2) {
22959
- console.error("[DAILY] Failed to expire/persist snoozes:", error2 instanceof Error ? error2.message : error2);
22934
+ console.error("[DAILY] Failed to expire/persist snoozes:", errorMessage(error2));
22960
22935
  }
22961
22936
  const shelvedPRs = [];
22962
22937
  const autoUnshelvedPRs = [];
@@ -23015,14 +22990,15 @@ function generateDigestOutput(digest, activePRs, shelvedPRs, commentedIssues, fa
23015
22990
  const issueResponses = filteredCommentedIssues.filter((i) => i.status === "new_response");
23016
22991
  const summary = formatSummary(digest, capacity, issueResponses);
23017
22992
  const snoozedUrls = new Set(Object.keys(stateManager2.getState().config.snoozedPRs ?? {}).filter((url) => stateManager2.isSnoozed(url)));
23018
- const actionableIssues = collectActionableIssues(activePRs, snoozedUrls);
22993
+ const dismissedUrls = new Set(Object.keys(stateManager2.getState().config.dismissedIssues ?? {}));
22994
+ const nonDismissedPRs = activePRs.filter((pr) => !dismissedUrls.has(pr.url));
22995
+ const actionableIssues = collectActionableIssues(nonDismissedPRs, snoozedUrls);
23019
22996
  digest.summary.totalNeedingAttention = actionableIssues.length;
23020
22997
  const briefSummary = formatBriefSummary(digest, actionableIssues.length, issueResponses.length);
23021
22998
  const actionMenu = computeActionMenu(actionableIssues, capacity, filteredCommentedIssues);
23022
22999
  const repoGroups = groupPRsByRepo(activePRs);
23023
23000
  return {
23024
23001
  digest,
23025
- updates: [],
23026
23002
  capacity,
23027
23003
  summary,
23028
23004
  briefSummary,
@@ -23036,7 +23012,6 @@ function generateDigestOutput(digest, activePRs, shelvedPRs, commentedIssues, fa
23036
23012
  function toDailyOutput(result) {
23037
23013
  return {
23038
23014
  digest: deduplicateDigest(result.digest),
23039
- updates: result.updates,
23040
23015
  capacity: result.capacity,
23041
23016
  summary: result.summary,
23042
23017
  briefSummary: result.briefSummary,
@@ -23067,6 +23042,7 @@ var init_daily = __esm({
23067
23042
  "../core/dist/commands/daily.js"() {
23068
23043
  "use strict";
23069
23044
  init_core3();
23045
+ init_errors3();
23070
23046
  init_json();
23071
23047
  init_core3();
23072
23048
  }
@@ -23084,11 +23060,12 @@ var init_dashboard_data = __esm({
23084
23060
  "../core/dist/commands/dashboard-data.js"() {
23085
23061
  "use strict";
23086
23062
  init_core3();
23063
+ init_errors3();
23087
23064
  init_daily();
23088
23065
  }
23089
23066
  });
23090
23067
 
23091
- // ../core/dist/commands/dashboard-templates.js
23068
+ // ../core/dist/commands/dashboard-formatters.js
23092
23069
  function escapeHtml(text) {
23093
23070
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
23094
23071
  }
@@ -23107,78 +23084,18 @@ function buildDashboardStats(digest, state) {
23107
23084
  mergeRate: `${(summary.mergeRate ?? 0).toFixed(1)}%`
23108
23085
  };
23109
23086
  }
23110
- function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpened, digest, state, issueResponses = []) {
23111
- const approachingDormantDays = state.config?.approachingDormantDays ?? 25;
23112
- const shelvedPRs = digest.shelvedPRs || [];
23113
- const autoUnshelvedPRs = digest.autoUnshelvedPRs || [];
23114
- const recentlyMerged = digest.recentlyMergedPRs || [];
23115
- const shelvedUrls = new Set(shelvedPRs.map((pr) => pr.url));
23116
- const activePRList = (digest.openPRs || []).filter((pr) => !shelvedUrls.has(pr.url));
23117
- const actionRequired = [
23118
- ...digest.prsNeedingResponse || [],
23119
- ...digest.needsChangesPRs || [],
23120
- ...digest.ciFailingPRs || [],
23121
- ...digest.mergeConflictPRs || [],
23122
- ...digest.incompleteChecklistPRs || [],
23123
- ...digest.missingRequiredFilesPRs || [],
23124
- ...digest.needsRebasePRs || []
23125
- ];
23126
- const waitingOnOthers = [
23127
- ...digest.changesAddressedPRs || [],
23128
- ...digest.waitingOnMaintainerPRs || [],
23129
- ...digest.ciBlockedPRs || [],
23130
- ...digest.ciNotRunningPRs || []
23131
- ];
23132
- function truncateTitle(title, max = 50) {
23133
- const truncated = title.length <= max ? title : title.slice(0, max) + "...";
23134
- return escapeHtml(truncated);
23135
- }
23136
- function renderHealthItems(prs, cssClass, svgPaths, labelFn, metaFn) {
23137
- return prs.map((pr) => {
23138
- const rawLabel = typeof labelFn === "string" ? labelFn : labelFn(pr);
23139
- const label = escapeHtml(rawLabel);
23140
- return `
23141
- <div class="health-item ${cssClass}" data-status="${cssClass}" data-repo="${escapeHtml(pr.repo)}" data-title="${escapeHtml(pr.title.toLowerCase())}">
23142
- <div class="health-icon">
23143
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
23144
- ${svgPaths}
23145
- </svg>
23146
- </div>
23147
- <div class="health-content">
23148
- <div class="health-title"><a href="${escapeHtml(pr.url)}" target="_blank">${escapeHtml(pr.repo)}#${pr.number}</a> - ${label}</div>
23149
- <div class="health-meta">${metaFn(pr)}</div>
23150
- </div>
23151
- </div>`;
23152
- }).join("");
23153
- }
23154
- const SVG = {
23155
- 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"/>',
23156
- 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"/>',
23157
- 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"/>',
23158
- 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"/>',
23159
- 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"/>',
23160
- 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"/>',
23161
- checkCircle: '<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>',
23162
- clock: '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>',
23163
- lock: '<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>',
23164
- 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"/>',
23165
- refresh: '<polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>',
23166
- 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"/>',
23167
- 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"/>',
23168
- 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"/>'
23169
- };
23170
- const titleMeta = (pr) => truncateTitle(pr.title);
23171
- return `<!DOCTYPE html>
23172
- <html lang="en">
23173
- <head>
23174
- <meta charset="UTF-8">
23175
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
23176
- <title>OSS Autopilot - Mission Control</title>
23177
- <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
23178
- <link rel="preconnect" href="https://fonts.googleapis.com">
23179
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
23180
- <link href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500&display=swap" rel="stylesheet">
23181
- <style>
23087
+ var init_dashboard_formatters = __esm({
23088
+ "../core/dist/commands/dashboard-formatters.js"() {
23089
+ "use strict";
23090
+ }
23091
+ });
23092
+
23093
+ // ../core/dist/commands/dashboard-styles.js
23094
+ var DASHBOARD_CSS;
23095
+ var init_dashboard_styles = __esm({
23096
+ "../core/dist/commands/dashboard-styles.js"() {
23097
+ "use strict";
23098
+ DASHBOARD_CSS = `
23182
23099
  :root, [data-theme="dark"] {
23183
23100
  --bg-base: #080b10;
23184
23101
  --bg-surface: rgba(22, 27, 34, 0.65);
@@ -23938,6 +23855,369 @@ function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpene
23938
23855
  .health-item[data-hidden="true"] {
23939
23856
  display: none;
23940
23857
  }
23858
+ `;
23859
+ }
23860
+ });
23861
+
23862
+ // ../core/dist/commands/dashboard-components.js
23863
+ function truncateTitle(title, max = 50) {
23864
+ const truncated = title.length <= max ? title : title.slice(0, max) + "...";
23865
+ return escapeHtml(truncated);
23866
+ }
23867
+ function renderHealthItems(prs, cssClass, svgPaths, labelFn, metaFn) {
23868
+ return prs.map((pr) => {
23869
+ const rawLabel = typeof labelFn === "string" ? labelFn : labelFn(pr);
23870
+ const label = escapeHtml(rawLabel);
23871
+ return `
23872
+ <div class="health-item ${cssClass}" data-status="${cssClass}" data-repo="${escapeHtml(pr.repo)}" data-title="${escapeHtml(pr.title.toLowerCase())}">
23873
+ <div class="health-icon">
23874
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
23875
+ ${svgPaths}
23876
+ </svg>
23877
+ </div>
23878
+ <div class="health-content">
23879
+ <div class="health-title"><a href="${escapeHtml(pr.url)}" target="_blank">${escapeHtml(pr.repo)}#${pr.number}</a> - ${label}</div>
23880
+ <div class="health-meta">${metaFn(pr)}</div>
23881
+ </div>
23882
+ </div>`;
23883
+ }).join("");
23884
+ }
23885
+ function titleMeta(pr) {
23886
+ return truncateTitle(pr.title);
23887
+ }
23888
+ var SVG_ICONS;
23889
+ var init_dashboard_components = __esm({
23890
+ "../core/dist/commands/dashboard-components.js"() {
23891
+ "use strict";
23892
+ init_dashboard_formatters();
23893
+ SVG_ICONS = {
23894
+ 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"/>',
23895
+ 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"/>',
23896
+ 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"/>',
23897
+ 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"/>',
23898
+ 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"/>',
23899
+ 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"/>',
23900
+ checkCircle: '<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>',
23901
+ clock: '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>',
23902
+ lock: '<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>',
23903
+ 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"/>',
23904
+ refresh: '<polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>',
23905
+ 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"/>',
23906
+ 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"/>',
23907
+ 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"/>'
23908
+ };
23909
+ }
23910
+ });
23911
+
23912
+ // ../core/dist/commands/dashboard-scripts.js
23913
+ function generateDashboardScripts(stats, monthlyMerged, monthlyClosed, monthlyOpened, digest, state) {
23914
+ const statusChart = `
23915
+ Chart.defaults.color = '#6e7681';
23916
+ Chart.defaults.borderColor = 'rgba(48, 54, 61, 0.4)';
23917
+ Chart.defaults.font.family = "'Geist', sans-serif";
23918
+ Chart.defaults.font.size = 11;
23919
+
23920
+ // === Status Doughnut ===
23921
+ new Chart(document.getElementById('statusChart'), {
23922
+ type: 'doughnut',
23923
+ data: {
23924
+ labels: ['Active', 'Shelved', 'Merged', 'Closed'],
23925
+ datasets: [{
23926
+ data: [${stats.activePRs}, ${stats.shelvedPRs}, ${stats.mergedPRs}, ${stats.closedPRs}],
23927
+ backgroundColor: ['#3fb950', '#6e7681', '#a855f7', '#484f58'],
23928
+ borderColor: 'rgba(8, 11, 16, 0.8)',
23929
+ borderWidth: 2,
23930
+ hoverOffset: 8
23931
+ }]
23932
+ },
23933
+ options: {
23934
+ responsive: true,
23935
+ maintainAspectRatio: false,
23936
+ cutout: '65%',
23937
+ plugins: {
23938
+ legend: {
23939
+ position: 'bottom',
23940
+ labels: { padding: 16, usePointStyle: true, pointStyle: 'circle', font: { size: 11 } }
23941
+ }
23942
+ }
23943
+ }
23944
+ });`;
23945
+ const repoChart = (() => {
23946
+ const { excludeRepos: exRepos = [], excludeOrgs: exOrgs, minStars } = state.config;
23947
+ const starThreshold = minStars ?? 50;
23948
+ const shouldExcludeRepo = (repo) => {
23949
+ const repoLower = repo.toLowerCase();
23950
+ if (exRepos.some((r) => r.toLowerCase() === repoLower))
23951
+ return true;
23952
+ if (exOrgs?.some((o) => o.toLowerCase() === repoLower.split("/")[0]))
23953
+ return true;
23954
+ const score = (state.repoScores || {})[repo];
23955
+ if (score?.stargazersCount !== void 0 && score.stargazersCount < starThreshold)
23956
+ return true;
23957
+ return false;
23958
+ };
23959
+ const allRepoEntries = Object.entries(
23960
+ // Rebuild from full prsByRepo to get all repos, not just top 10
23961
+ (() => {
23962
+ const all = {};
23963
+ for (const pr of digest.openPRs || []) {
23964
+ if (shouldExcludeRepo(pr.repo))
23965
+ continue;
23966
+ if (!all[pr.repo])
23967
+ all[pr.repo] = { active: 0, merged: 0, closed: 0 };
23968
+ all[pr.repo].active++;
23969
+ }
23970
+ for (const [repo, score] of Object.entries(state.repoScores || {})) {
23971
+ if (shouldExcludeRepo(repo))
23972
+ continue;
23973
+ if (!all[repo])
23974
+ all[repo] = { active: 0, merged: 0, closed: 0 };
23975
+ all[repo].merged = score.mergedPRCount;
23976
+ all[repo].closed = score.closedWithoutMergeCount;
23977
+ }
23978
+ return all;
23979
+ })()
23980
+ ).sort((a, b) => {
23981
+ const totalA = a[1].merged + a[1].active + a[1].closed;
23982
+ const totalB = b[1].merged + b[1].active + b[1].closed;
23983
+ return totalB - totalA;
23984
+ });
23985
+ const displayRepos = allRepoEntries.slice(0, 10);
23986
+ const otherRepos = allRepoEntries.slice(10);
23987
+ const grandTotal = allRepoEntries.reduce((sum, [, d]) => sum + d.merged + d.active + d.closed, 0);
23988
+ if (otherRepos.length > 0) {
23989
+ const otherData = otherRepos.reduce((acc, [, d]) => ({
23990
+ active: acc.active + d.active,
23991
+ merged: acc.merged + d.merged,
23992
+ closed: acc.closed + d.closed
23993
+ }), { active: 0, merged: 0, closed: 0 });
23994
+ displayRepos.push(["Other", otherData]);
23995
+ }
23996
+ const repoLabels = displayRepos.map(([repo]) => repo === "Other" ? "Other" : repo.split("/")[1] || repo);
23997
+ const mergedData = displayRepos.map(([, d]) => d.merged);
23998
+ const activeData = displayRepos.map(([, d]) => d.active);
23999
+ const closedData = displayRepos.map(([, d]) => d.closed);
24000
+ return `
24001
+ new Chart(document.getElementById('reposChart'), {
24002
+ type: 'bar',
24003
+ data: {
24004
+ labels: ${JSON.stringify(repoLabels)},
24005
+ datasets: [
24006
+ { label: 'Merged', data: ${JSON.stringify(mergedData)}, backgroundColor: '#a855f7', borderRadius: 3 },
24007
+ { label: 'Active', data: ${JSON.stringify(activeData)}, backgroundColor: '#3fb950', borderRadius: 3 },
24008
+ { label: 'Closed', data: ${JSON.stringify(closedData)}, backgroundColor: '#484f58', borderRadius: 3 }
24009
+ ]
24010
+ },
24011
+ options: {
24012
+ responsive: true,
24013
+ maintainAspectRatio: false,
24014
+ scales: {
24015
+ x: { stacked: true, grid: { display: false }, ticks: { font: { size: 10 } } },
24016
+ y: { stacked: true, grid: { color: 'rgba(48, 54, 61, 0.3)' }, ticks: { stepSize: 1 } }
24017
+ },
24018
+ plugins: {
24019
+ legend: { position: 'bottom', labels: { padding: 16, usePointStyle: true, pointStyle: 'circle', font: { size: 11 } } },
24020
+ tooltip: {
24021
+ callbacks: {
24022
+ afterBody: function(context) {
24023
+ const idx = context[0].dataIndex;
24024
+ const total = ${JSON.stringify(mergedData)}[idx] + ${JSON.stringify(activeData)}[idx] + ${JSON.stringify(closedData)}[idx];
24025
+ const pct = ${grandTotal} > 0 ? ((total / ${grandTotal}) * 100).toFixed(1) : '0.0';
24026
+ return pct + '% of all PRs';
24027
+ }
24028
+ }
24029
+ }
24030
+ }
24031
+ }
24032
+ });`;
24033
+ })();
24034
+ const timelineChart = (() => {
24035
+ const now = /* @__PURE__ */ new Date();
24036
+ const allMonths = [];
24037
+ for (let offset = 5; offset >= 0; offset--) {
24038
+ const d = new Date(now.getFullYear(), now.getMonth() - offset, 1);
24039
+ allMonths.push(`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`);
24040
+ }
24041
+ return `
24042
+ const timelineMonths = ${JSON.stringify(allMonths)};
24043
+ const openedData = ${JSON.stringify(monthlyOpened)};
24044
+ const mergedData = ${JSON.stringify(monthlyMerged)};
24045
+ const closedData = ${JSON.stringify(monthlyClosed)};
24046
+ new Chart(document.getElementById('monthlyChart'), {
24047
+ type: 'bar',
24048
+ data: {
24049
+ labels: timelineMonths,
24050
+ datasets: [
24051
+ {
24052
+ label: 'Opened',
24053
+ data: timelineMonths.map(m => openedData[m] || 0),
24054
+ backgroundColor: '#58a6ff',
24055
+ borderRadius: 3
24056
+ },
24057
+ {
24058
+ label: 'Merged',
24059
+ data: timelineMonths.map(m => mergedData[m] || 0),
24060
+ backgroundColor: '#a855f7',
24061
+ borderRadius: 3
24062
+ },
24063
+ {
24064
+ label: 'Closed',
24065
+ data: timelineMonths.map(m => closedData[m] || 0),
24066
+ backgroundColor: '#484f58',
24067
+ borderRadius: 3
24068
+ }
24069
+ ]
24070
+ },
24071
+ options: {
24072
+ responsive: true,
24073
+ maintainAspectRatio: false,
24074
+ scales: {
24075
+ x: { grid: { display: false } },
24076
+ y: { grid: { color: 'rgba(48, 54, 61, 0.3)' }, beginAtZero: true, ticks: { stepSize: 1 } }
24077
+ },
24078
+ plugins: {
24079
+ legend: { position: 'bottom', labels: { padding: 16, usePointStyle: true, pointStyle: 'circle', font: { size: 11 } } }
24080
+ },
24081
+ interaction: { intersect: false, mode: 'index' }
24082
+ }
24083
+ });`;
24084
+ })();
24085
+ return THEME_AND_FILTER_SCRIPT + statusChart + "\n" + repoChart + "\n" + timelineChart;
24086
+ }
24087
+ var THEME_AND_FILTER_SCRIPT;
24088
+ var init_dashboard_scripts = __esm({
24089
+ "../core/dist/commands/dashboard-scripts.js"() {
24090
+ "use strict";
24091
+ THEME_AND_FILTER_SCRIPT = `
24092
+ // === Theme Toggle ===
24093
+ (function() {
24094
+ var html = document.documentElement;
24095
+ var toggle = document.getElementById('themeToggle');
24096
+ var sunIcon = document.getElementById('themeIconSun');
24097
+ var moonIcon = document.getElementById('themeIconMoon');
24098
+ var label = document.getElementById('themeLabel');
24099
+
24100
+ function getEffectiveTheme() {
24101
+ try {
24102
+ var stored = localStorage.getItem('oss-dashboard-theme');
24103
+ if (stored === 'light' || stored === 'dark') return stored;
24104
+ } catch (e) { /* localStorage unavailable (private browsing) */ }
24105
+ return window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
24106
+ }
24107
+
24108
+ function applyTheme(theme) {
24109
+ html.setAttribute('data-theme', theme);
24110
+ if (theme === 'light') {
24111
+ sunIcon.style.display = 'none';
24112
+ moonIcon.style.display = 'block';
24113
+ label.textContent = 'Dark';
24114
+ } else {
24115
+ sunIcon.style.display = 'block';
24116
+ moonIcon.style.display = 'none';
24117
+ label.textContent = 'Light';
24118
+ }
24119
+ }
24120
+
24121
+ applyTheme(getEffectiveTheme());
24122
+
24123
+ toggle.addEventListener('click', function() {
24124
+ var current = html.getAttribute('data-theme');
24125
+ var next = current === 'dark' ? 'light' : 'dark';
24126
+ try { localStorage.setItem('oss-dashboard-theme', next); } catch (e) { /* private browsing */ }
24127
+ applyTheme(next);
24128
+ });
24129
+ })();
24130
+
24131
+ // === Filtering & Search ===
24132
+ (function() {
24133
+ var searchInput = document.getElementById('searchInput');
24134
+ var statusFilter = document.getElementById('statusFilter');
24135
+ var repoFilter = document.getElementById('repoFilter');
24136
+ var filterCount = document.getElementById('filterCount');
24137
+
24138
+ function applyFilters() {
24139
+ var query = searchInput.value.toLowerCase().trim();
24140
+ var status = statusFilter.value;
24141
+ var repo = repoFilter.value;
24142
+ var allItems = document.querySelectorAll('.health-item[data-status], .pr-item[data-status]');
24143
+ var visible = 0;
24144
+ var total = allItems.length;
24145
+
24146
+ allItems.forEach(function(item) {
24147
+ var itemStatus = item.getAttribute('data-status') || '';
24148
+ var itemRepo = item.getAttribute('data-repo') || '';
24149
+ var itemTitle = item.getAttribute('data-title') || '';
24150
+
24151
+ var matchesStatus = (status === 'all') || (itemStatus === status);
24152
+ var matchesRepo = (repo === 'all') || (itemRepo === repo);
24153
+ var matchesSearch = !query || itemTitle.indexOf(query) !== -1;
24154
+
24155
+ if (matchesStatus && matchesRepo && matchesSearch) {
24156
+ item.setAttribute('data-hidden', 'false');
24157
+ visible++;
24158
+ } else {
24159
+ item.setAttribute('data-hidden', 'true');
24160
+ }
24161
+ });
24162
+
24163
+ // Show/hide parent sections if all children are hidden
24164
+ var sections = document.querySelectorAll('.health-section, .pr-list-section');
24165
+ sections.forEach(function(section) {
24166
+ var items = section.querySelectorAll('.health-item[data-status], .pr-item[data-status]');
24167
+ if (items.length === 0) return; // sections without filterable items (e.g. empty state)
24168
+ var anyVisible = false;
24169
+ items.forEach(function(item) {
24170
+ if (item.getAttribute('data-hidden') !== 'true') anyVisible = true;
24171
+ });
24172
+ section.style.display = anyVisible ? '' : 'none';
24173
+ });
24174
+
24175
+ var isFiltering = (status !== 'all' || repo !== 'all' || query.length > 0);
24176
+ filterCount.textContent = isFiltering ? (visible + ' of ' + total + ' items') : '';
24177
+ }
24178
+
24179
+ searchInput.addEventListener('input', applyFilters);
24180
+ statusFilter.addEventListener('change', applyFilters);
24181
+ repoFilter.addEventListener('change', applyFilters);
24182
+ })();
24183
+ `;
24184
+ }
24185
+ });
24186
+
24187
+ // ../core/dist/commands/dashboard-templates.js
24188
+ function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpened, digest, state, issueResponses = []) {
24189
+ const approachingDormantDays = state.config?.approachingDormantDays ?? 25;
24190
+ const shelvedPRs = digest.shelvedPRs || [];
24191
+ const autoUnshelvedPRs = digest.autoUnshelvedPRs || [];
24192
+ const recentlyMerged = digest.recentlyMergedPRs || [];
24193
+ const shelvedUrls = new Set(shelvedPRs.map((pr) => pr.url));
24194
+ const activePRList = (digest.openPRs || []).filter((pr) => !shelvedUrls.has(pr.url));
24195
+ const actionRequired = [
24196
+ ...digest.prsNeedingResponse || [],
24197
+ ...digest.needsChangesPRs || [],
24198
+ ...digest.ciFailingPRs || [],
24199
+ ...digest.mergeConflictPRs || [],
24200
+ ...digest.incompleteChecklistPRs || [],
24201
+ ...digest.missingRequiredFilesPRs || [],
24202
+ ...digest.needsRebasePRs || []
24203
+ ];
24204
+ const waitingOnOthers = [
24205
+ ...digest.changesAddressedPRs || [],
24206
+ ...digest.waitingOnMaintainerPRs || [],
24207
+ ...digest.ciBlockedPRs || [],
24208
+ ...digest.ciNotRunningPRs || []
24209
+ ];
24210
+ return `<!DOCTYPE html>
24211
+ <html lang="en">
24212
+ <head>
24213
+ <meta charset="UTF-8">
24214
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
24215
+ <title>OSS Autopilot - Mission Control</title>
24216
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
24217
+ <link rel="preconnect" href="https://fonts.googleapis.com">
24218
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
24219
+ <link href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500&display=swap" rel="stylesheet">
24220
+ <style>${DASHBOARD_CSS}
23941
24221
  </style>
23942
24222
  </head>
23943
24223
  <body>
@@ -24069,13 +24349,13 @@ function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpene
24069
24349
  <span class="health-badge">${actionRequired.length} issue${actionRequired.length !== 1 ? "s" : ""}</span>
24070
24350
  </div>
24071
24351
  <div class="health-items">
24072
- ${renderHealthItems(digest.prsNeedingResponse || [], "needs-response", SVG.comment, "Needs Response", (pr) => pr.lastMaintainerComment ? `@${escapeHtml(pr.lastMaintainerComment.author)}: ${truncateTitle(pr.lastMaintainerComment.body, 40)}` : truncateTitle(pr.title))}
24073
- ${renderHealthItems(digest.needsChangesPRs || [], "needs-changes", SVG.edit, "Needs Changes", titleMeta)}
24074
- ${renderHealthItems(digest.ciFailingPRs || [], "ci-failing", SVG.xCircle, "CI Failing", titleMeta)}
24075
- ${renderHealthItems(digest.mergeConflictPRs || [], "conflict", SVG.conflict, "Merge Conflict", titleMeta)}
24076
- ${renderHealthItems(digest.incompleteChecklistPRs || [], "incomplete-checklist", SVG.checklist, (pr) => `Incomplete Checklist${pr.checklistStats ? ` (${pr.checklistStats.checked}/${pr.checklistStats.total})` : ""}`, titleMeta)}
24077
- ${renderHealthItems(digest.missingRequiredFilesPRs || [], "missing-files", SVG.file, "Missing Required Files", (pr) => pr.missingRequiredFiles ? escapeHtml(pr.missingRequiredFiles.join(", ")) : truncateTitle(pr.title))}
24078
- ${renderHealthItems(digest.needsRebasePRs || [], "needs-rebase", SVG.refresh, (pr) => `Needs Rebase${pr.commitsBehindUpstream ? ` (${pr.commitsBehindUpstream} behind)` : ""}`, titleMeta)}
24352
+ ${renderHealthItems(digest.prsNeedingResponse || [], "needs-response", SVG_ICONS.comment, "Needs Response", (pr) => pr.lastMaintainerComment ? `@${escapeHtml(pr.lastMaintainerComment.author)}: ${truncateTitle(pr.lastMaintainerComment.body, 40)}` : truncateTitle(pr.title))}
24353
+ ${renderHealthItems(digest.needsChangesPRs || [], "needs-changes", SVG_ICONS.edit, "Needs Changes", titleMeta)}
24354
+ ${renderHealthItems(digest.ciFailingPRs || [], "ci-failing", SVG_ICONS.xCircle, "CI Failing", titleMeta)}
24355
+ ${renderHealthItems(digest.mergeConflictPRs || [], "conflict", SVG_ICONS.conflict, "Merge Conflict", titleMeta)}
24356
+ ${renderHealthItems(digest.incompleteChecklistPRs || [], "incomplete-checklist", SVG_ICONS.checklist, (pr) => `Incomplete Checklist${pr.checklistStats ? ` (${pr.checklistStats.checked}/${pr.checklistStats.total})` : ""}`, titleMeta)}
24357
+ ${renderHealthItems(digest.missingRequiredFilesPRs || [], "missing-files", SVG_ICONS.file, "Missing Required Files", (pr) => pr.missingRequiredFiles ? escapeHtml(pr.missingRequiredFiles.join(", ")) : truncateTitle(pr.title))}
24358
+ ${renderHealthItems(digest.needsRebasePRs || [], "needs-rebase", SVG_ICONS.refresh, (pr) => `Needs Rebase${pr.commitsBehindUpstream ? ` (${pr.commitsBehindUpstream} behind)` : ""}`, titleMeta)}
24079
24359
  </div>
24080
24360
  </section>
24081
24361
  ` : ""}
@@ -24091,10 +24371,10 @@ function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpene
24091
24371
  <span class="health-badge" style="background: var(--accent-info-dim); color: var(--accent-info);">${waitingOnOthers.length} PR${waitingOnOthers.length !== 1 ? "s" : ""}</span>
24092
24372
  </div>
24093
24373
  <div class="health-items">
24094
- ${renderHealthItems(digest.changesAddressedPRs || [], "changes-addressed", SVG.checkCircle, "Changes Addressed", (pr) => `Awaiting re-review${pr.lastMaintainerComment ? ` from @${escapeHtml(pr.lastMaintainerComment.author)}` : ""}`)}
24095
- ${renderHealthItems(digest.waitingOnMaintainerPRs || [], "waiting-maintainer", SVG.clock, "Waiting on Maintainer", titleMeta)}
24096
- ${renderHealthItems(digest.ciBlockedPRs || [], "ci-blocked", SVG.lock, "CI Blocked", titleMeta)}
24097
- ${renderHealthItems(digest.ciNotRunningPRs || [], "ci-not-running", SVG.infoCircle, "CI Not Running", titleMeta)}
24374
+ ${renderHealthItems(digest.changesAddressedPRs || [], "changes-addressed", SVG_ICONS.checkCircle, "Changes Addressed", (pr) => `Awaiting re-review${pr.lastMaintainerComment ? ` from @${escapeHtml(pr.lastMaintainerComment.author)}` : ""}`)}
24375
+ ${renderHealthItems(digest.waitingOnMaintainerPRs || [], "waiting-maintainer", SVG_ICONS.clock, "Waiting on Maintainer", titleMeta)}
24376
+ ${renderHealthItems(digest.ciBlockedPRs || [], "ci-blocked", SVG_ICONS.lock, "CI Blocked", titleMeta)}
24377
+ ${renderHealthItems(digest.ciNotRunningPRs || [], "ci-not-running", SVG_ICONS.infoCircle, "CI Not Running", titleMeta)}
24098
24378
  </div>
24099
24379
  </section>
24100
24380
  ` : ""}
@@ -24118,7 +24398,7 @@ function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpene
24118
24398
  <section class="health-section" style="animation-delay: 0.15s;">
24119
24399
  <div class="health-header">
24120
24400
  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--accent-merged)" stroke-width="2">
24121
- ${SVG.gitMerge}
24401
+ ${SVG_ICONS.gitMerge}
24122
24402
  </svg>
24123
24403
  <h2>Recently Merged</h2>
24124
24404
  <span class="health-badge" style="background: var(--accent-merged-dim); color: var(--accent-merged);">${recentlyMerged.length} merged</span>
@@ -24128,7 +24408,7 @@ function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpene
24128
24408
  <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())}">
24129
24409
  <div class="health-icon" style="background: var(--accent-merged-dim); color: var(--accent-merged);">
24130
24410
  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
24131
- ${SVG.gitMerge}
24411
+ ${SVG_ICONS.gitMerge}
24132
24412
  </svg>
24133
24413
  </div>
24134
24414
  <div class="health-content">
@@ -24176,13 +24456,13 @@ function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpene
24176
24456
  <section class="health-section" style="animation-delay: 0.25s;">
24177
24457
  <div class="health-header">
24178
24458
  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--accent-info)" stroke-width="2">
24179
- ${SVG.bell}
24459
+ ${SVG_ICONS.bell}
24180
24460
  </svg>
24181
24461
  <h2>Auto-Unshelved</h2>
24182
24462
  <span class="health-badge" style="background: var(--accent-info-dim); color: var(--accent-info);">${autoUnshelvedPRs.length} unshelved</span>
24183
24463
  </div>
24184
24464
  <div class="health-items">
24185
- ${renderHealthItems(autoUnshelvedPRs, "auto-unshelved", SVG.bell, (pr) => "Auto-Unshelved (" + pr.status.replace(/_/g, " ") + ")", titleMeta)}
24465
+ ${renderHealthItems(autoUnshelvedPRs, "auto-unshelved", SVG_ICONS.bell, (pr) => "Auto-Unshelved (" + pr.status.replace(/_/g, " ") + ")", titleMeta)}
24186
24466
  </div>
24187
24467
  </section>
24188
24468
  ` : ""}
@@ -24191,7 +24471,7 @@ function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpene
24191
24471
  <section class="health-section" style="animation-delay: 0.3s;">
24192
24472
  <div class="health-header">
24193
24473
  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--accent-info)" stroke-width="2">
24194
- ${SVG.comment}
24474
+ ${SVG_ICONS.comment}
24195
24475
  </svg>
24196
24476
  <h2>Issue Conversations</h2>
24197
24477
  <span class="health-badge" style="background: var(--accent-info-dim); color: var(--accent-info);">${issueResponses.length} repl${issueResponses.length !== 1 ? "ies" : "y"}</span>
@@ -24201,7 +24481,7 @@ function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpene
24201
24481
  <div class="health-item changes-addressed">
24202
24482
  <div class="health-icon" style="background: var(--accent-info-dim); color: var(--accent-info);">
24203
24483
  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
24204
- ${SVG.comment}
24484
+ ${SVG_ICONS.comment}
24205
24485
  </svg>
24206
24486
  </div>
24207
24487
  <div class="health-content">
@@ -24331,7 +24611,7 @@ function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpene
24331
24611
  <div class="pr-item" data-status="shelved" data-repo="${escapeHtml(pr.repo)}" data-title="${escapeHtml(pr.title.toLowerCase())}">
24332
24612
  <div class="pr-status-indicator" style="background: rgba(110, 118, 129, 0.1); color: var(--text-muted);">
24333
24613
  <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
24334
- ${SVG.box}
24614
+ ${SVG_ICONS.box}
24335
24615
  </svg>
24336
24616
  </div>
24337
24617
  <div class="pr-content">
@@ -24358,274 +24638,7 @@ function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpene
24358
24638
  </div>
24359
24639
 
24360
24640
  <script>
24361
- // === Theme Toggle ===
24362
- (function() {
24363
- var html = document.documentElement;
24364
- var toggle = document.getElementById('themeToggle');
24365
- var sunIcon = document.getElementById('themeIconSun');
24366
- var moonIcon = document.getElementById('themeIconMoon');
24367
- var label = document.getElementById('themeLabel');
24368
-
24369
- function getEffectiveTheme() {
24370
- try {
24371
- var stored = localStorage.getItem('oss-dashboard-theme');
24372
- if (stored === 'light' || stored === 'dark') return stored;
24373
- } catch (e) { /* localStorage unavailable (private browsing) */ }
24374
- return window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
24375
- }
24376
-
24377
- function applyTheme(theme) {
24378
- html.setAttribute('data-theme', theme);
24379
- if (theme === 'light') {
24380
- sunIcon.style.display = 'none';
24381
- moonIcon.style.display = 'block';
24382
- label.textContent = 'Dark';
24383
- } else {
24384
- sunIcon.style.display = 'block';
24385
- moonIcon.style.display = 'none';
24386
- label.textContent = 'Light';
24387
- }
24388
- }
24389
-
24390
- applyTheme(getEffectiveTheme());
24391
-
24392
- toggle.addEventListener('click', function() {
24393
- var current = html.getAttribute('data-theme');
24394
- var next = current === 'dark' ? 'light' : 'dark';
24395
- try { localStorage.setItem('oss-dashboard-theme', next); } catch (e) { /* private browsing */ }
24396
- applyTheme(next);
24397
- });
24398
- })();
24399
-
24400
- // === Filtering & Search ===
24401
- (function() {
24402
- var searchInput = document.getElementById('searchInput');
24403
- var statusFilter = document.getElementById('statusFilter');
24404
- var repoFilter = document.getElementById('repoFilter');
24405
- var filterCount = document.getElementById('filterCount');
24406
-
24407
- function applyFilters() {
24408
- var query = searchInput.value.toLowerCase().trim();
24409
- var status = statusFilter.value;
24410
- var repo = repoFilter.value;
24411
- var allItems = document.querySelectorAll('.health-item[data-status], .pr-item[data-status]');
24412
- var visible = 0;
24413
- var total = allItems.length;
24414
-
24415
- allItems.forEach(function(item) {
24416
- var itemStatus = item.getAttribute('data-status') || '';
24417
- var itemRepo = item.getAttribute('data-repo') || '';
24418
- var itemTitle = item.getAttribute('data-title') || '';
24419
-
24420
- var matchesStatus = (status === 'all') || (itemStatus === status);
24421
- var matchesRepo = (repo === 'all') || (itemRepo === repo);
24422
- var matchesSearch = !query || itemTitle.indexOf(query) !== -1;
24423
-
24424
- if (matchesStatus && matchesRepo && matchesSearch) {
24425
- item.setAttribute('data-hidden', 'false');
24426
- visible++;
24427
- } else {
24428
- item.setAttribute('data-hidden', 'true');
24429
- }
24430
- });
24431
-
24432
- // Show/hide parent sections if all children are hidden
24433
- var sections = document.querySelectorAll('.health-section, .pr-list-section');
24434
- sections.forEach(function(section) {
24435
- var items = section.querySelectorAll('.health-item[data-status], .pr-item[data-status]');
24436
- if (items.length === 0) return; // sections without filterable items (e.g. empty state)
24437
- var anyVisible = false;
24438
- items.forEach(function(item) {
24439
- if (item.getAttribute('data-hidden') !== 'true') anyVisible = true;
24440
- });
24441
- section.style.display = anyVisible ? '' : 'none';
24442
- });
24443
-
24444
- var isFiltering = (status !== 'all' || repo !== 'all' || query.length > 0);
24445
- filterCount.textContent = isFiltering ? (visible + ' of ' + total + ' items') : '';
24446
- }
24447
-
24448
- searchInput.addEventListener('input', applyFilters);
24449
- statusFilter.addEventListener('change', applyFilters);
24450
- repoFilter.addEventListener('change', applyFilters);
24451
- })();
24452
-
24453
- // === Chart.js Configuration ===
24454
- Chart.defaults.color = '#6e7681';
24455
- Chart.defaults.borderColor = 'rgba(48, 54, 61, 0.4)';
24456
- Chart.defaults.font.family = "'Geist', sans-serif";
24457
- Chart.defaults.font.size = 11;
24458
-
24459
- // === Status Doughnut ===
24460
- new Chart(document.getElementById('statusChart'), {
24461
- type: 'doughnut',
24462
- data: {
24463
- labels: ['Active', 'Shelved', 'Merged', 'Closed'],
24464
- datasets: [{
24465
- data: [${stats.activePRs}, ${stats.shelvedPRs}, ${stats.mergedPRs}, ${stats.closedPRs}],
24466
- backgroundColor: ['#3fb950', '#6e7681', '#a855f7', '#484f58'],
24467
- borderColor: 'rgba(8, 11, 16, 0.8)',
24468
- borderWidth: 2,
24469
- hoverOffset: 8
24470
- }]
24471
- },
24472
- options: {
24473
- responsive: true,
24474
- maintainAspectRatio: false,
24475
- cutout: '65%',
24476
- plugins: {
24477
- legend: {
24478
- position: 'bottom',
24479
- labels: { padding: 16, usePointStyle: true, pointStyle: 'circle', font: { size: 11 } }
24480
- }
24481
- }
24482
- }
24483
- });
24484
-
24485
- // === Repository Breakdown (with "Other" bucket + percentage tooltips) ===
24486
- ${(() => {
24487
- const { excludeRepos: exRepos = [], excludeOrgs: exOrgs, minStars } = state.config;
24488
- const starThreshold = minStars ?? 50;
24489
- const shouldExcludeRepo = (repo) => {
24490
- const repoLower = repo.toLowerCase();
24491
- if (exRepos.some((r) => r.toLowerCase() === repoLower))
24492
- return true;
24493
- if (exOrgs?.some((o) => o.toLowerCase() === repoLower.split("/")[0]))
24494
- return true;
24495
- const score = (state.repoScores || {})[repo];
24496
- if (score?.stargazersCount !== void 0 && score.stargazersCount < starThreshold)
24497
- return true;
24498
- return false;
24499
- };
24500
- const allRepoEntries = Object.entries(
24501
- // Rebuild from full prsByRepo to get all repos, not just top 10
24502
- (() => {
24503
- const all = {};
24504
- for (const pr of digest.openPRs || []) {
24505
- if (shouldExcludeRepo(pr.repo))
24506
- continue;
24507
- if (!all[pr.repo])
24508
- all[pr.repo] = { active: 0, merged: 0, closed: 0 };
24509
- all[pr.repo].active++;
24510
- }
24511
- for (const [repo, score] of Object.entries(state.repoScores || {})) {
24512
- if (shouldExcludeRepo(repo))
24513
- continue;
24514
- if (!all[repo])
24515
- all[repo] = { active: 0, merged: 0, closed: 0 };
24516
- all[repo].merged = score.mergedPRCount;
24517
- all[repo].closed = score.closedWithoutMergeCount;
24518
- }
24519
- return all;
24520
- })()
24521
- ).sort((a, b) => {
24522
- const totalA = a[1].merged + a[1].active + a[1].closed;
24523
- const totalB = b[1].merged + b[1].active + b[1].closed;
24524
- return totalB - totalA;
24525
- });
24526
- const displayRepos = allRepoEntries.slice(0, 10);
24527
- const otherRepos = allRepoEntries.slice(10);
24528
- const grandTotal = allRepoEntries.reduce((sum, [, d]) => sum + d.merged + d.active + d.closed, 0);
24529
- if (otherRepos.length > 0) {
24530
- const otherData = otherRepos.reduce((acc, [, d]) => ({
24531
- active: acc.active + d.active,
24532
- merged: acc.merged + d.merged,
24533
- closed: acc.closed + d.closed
24534
- }), { active: 0, merged: 0, closed: 0 });
24535
- displayRepos.push(["Other", otherData]);
24536
- }
24537
- const repoLabels = displayRepos.map(([repo]) => repo === "Other" ? "Other" : repo.split("/")[1] || repo);
24538
- const mergedData = displayRepos.map(([, d]) => d.merged);
24539
- const activeData = displayRepos.map(([, d]) => d.active);
24540
- const closedData = displayRepos.map(([, d]) => d.closed);
24541
- return `
24542
- new Chart(document.getElementById('reposChart'), {
24543
- type: 'bar',
24544
- data: {
24545
- labels: ${JSON.stringify(repoLabels)},
24546
- datasets: [
24547
- { label: 'Merged', data: ${JSON.stringify(mergedData)}, backgroundColor: '#a855f7', borderRadius: 3 },
24548
- { label: 'Active', data: ${JSON.stringify(activeData)}, backgroundColor: '#3fb950', borderRadius: 3 },
24549
- { label: 'Closed', data: ${JSON.stringify(closedData)}, backgroundColor: '#484f58', borderRadius: 3 }
24550
- ]
24551
- },
24552
- options: {
24553
- responsive: true,
24554
- maintainAspectRatio: false,
24555
- scales: {
24556
- x: { stacked: true, grid: { display: false }, ticks: { font: { size: 10 } } },
24557
- y: { stacked: true, grid: { color: 'rgba(48, 54, 61, 0.3)' }, ticks: { stepSize: 1 } }
24558
- },
24559
- plugins: {
24560
- legend: { position: 'bottom', labels: { padding: 16, usePointStyle: true, pointStyle: 'circle', font: { size: 11 } } },
24561
- tooltip: {
24562
- callbacks: {
24563
- afterBody: function(context) {
24564
- const idx = context[0].dataIndex;
24565
- const total = ${JSON.stringify(mergedData)}[idx] + ${JSON.stringify(activeData)}[idx] + ${JSON.stringify(closedData)}[idx];
24566
- const pct = ${grandTotal} > 0 ? ((total / ${grandTotal}) * 100).toFixed(1) : '0.0';
24567
- return pct + '% of all PRs';
24568
- }
24569
- }
24570
- }
24571
- }
24572
- }
24573
- });`;
24574
- })()}
24575
-
24576
- // === Contribution Timeline (grouped bar: Opened/Merged/Closed) ===
24577
- ${(() => {
24578
- const now = /* @__PURE__ */ new Date();
24579
- const allMonths = [];
24580
- for (let offset = 5; offset >= 0; offset--) {
24581
- const d = new Date(now.getFullYear(), now.getMonth() - offset, 1);
24582
- allMonths.push(`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`);
24583
- }
24584
- return `
24585
- const timelineMonths = ${JSON.stringify(allMonths)};
24586
- const openedData = ${JSON.stringify(monthlyOpened)};
24587
- const mergedData = ${JSON.stringify(monthlyMerged)};
24588
- const closedData = ${JSON.stringify(monthlyClosed)};
24589
- new Chart(document.getElementById('monthlyChart'), {
24590
- type: 'bar',
24591
- data: {
24592
- labels: timelineMonths,
24593
- datasets: [
24594
- {
24595
- label: 'Opened',
24596
- data: timelineMonths.map(m => openedData[m] || 0),
24597
- backgroundColor: '#58a6ff',
24598
- borderRadius: 3
24599
- },
24600
- {
24601
- label: 'Merged',
24602
- data: timelineMonths.map(m => mergedData[m] || 0),
24603
- backgroundColor: '#a855f7',
24604
- borderRadius: 3
24605
- },
24606
- {
24607
- label: 'Closed',
24608
- data: timelineMonths.map(m => closedData[m] || 0),
24609
- backgroundColor: '#484f58',
24610
- borderRadius: 3
24611
- }
24612
- ]
24613
- },
24614
- options: {
24615
- responsive: true,
24616
- maintainAspectRatio: false,
24617
- scales: {
24618
- x: { grid: { display: false } },
24619
- y: { grid: { color: 'rgba(48, 54, 61, 0.3)' }, beginAtZero: true, ticks: { stepSize: 1 } }
24620
- },
24621
- plugins: {
24622
- legend: { position: 'bottom', labels: { padding: 16, usePointStyle: true, pointStyle: 'circle', font: { size: 11 } } }
24623
- },
24624
- interaction: { intersect: false, mode: 'index' }
24625
- }
24626
- });`;
24627
- })()}
24628
-
24641
+ ${generateDashboardScripts(stats, monthlyMerged, monthlyClosed, monthlyOpened, digest, state)}
24629
24642
  </script>
24630
24643
  </body>
24631
24644
  </html>`;
@@ -24633,6 +24646,11 @@ function generateDashboardHtml(stats, monthlyMerged, monthlyClosed, monthlyOpene
24633
24646
  var init_dashboard_templates = __esm({
24634
24647
  "../core/dist/commands/dashboard-templates.js"() {
24635
24648
  "use strict";
24649
+ init_dashboard_formatters();
24650
+ init_dashboard_styles();
24651
+ init_dashboard_components();
24652
+ init_dashboard_scripts();
24653
+ init_dashboard_formatters();
24636
24654
  }
24637
24655
  });
24638
24656
 
@@ -26493,8 +26511,8 @@ function getErrorMap() {
26493
26511
 
26494
26512
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
26495
26513
  var makeIssue = (params) => {
26496
- const { data, path: path7, errorMaps, issueData } = params;
26497
- const fullPath = [...path7, ...issueData.path || []];
26514
+ const { data, path: path6, errorMaps, issueData } = params;
26515
+ const fullPath = [...path6, ...issueData.path || []];
26498
26516
  const fullIssue = {
26499
26517
  ...issueData,
26500
26518
  path: fullPath
@@ -26610,11 +26628,11 @@ var errorUtil;
26610
26628
 
26611
26629
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js
26612
26630
  var ParseInputLazyPath = class {
26613
- constructor(parent, value, path7, key) {
26631
+ constructor(parent, value, path6, key) {
26614
26632
  this._cachedPath = [];
26615
26633
  this.parent = parent;
26616
26634
  this.data = value;
26617
- this._path = path7;
26635
+ this._path = path6;
26618
26636
  this._key = key;
26619
26637
  }
26620
26638
  get path() {
@@ -31741,25 +31759,25 @@ var Protocol = class {
31741
31759
  });
31742
31760
  }
31743
31761
  _resetTimeout(messageId) {
31744
- const info = this._timeoutInfo.get(messageId);
31745
- if (!info)
31762
+ const info2 = this._timeoutInfo.get(messageId);
31763
+ if (!info2)
31746
31764
  return false;
31747
- const totalElapsed = Date.now() - info.startTime;
31748
- if (info.maxTotalTimeout && totalElapsed >= info.maxTotalTimeout) {
31765
+ const totalElapsed = Date.now() - info2.startTime;
31766
+ if (info2.maxTotalTimeout && totalElapsed >= info2.maxTotalTimeout) {
31749
31767
  this._timeoutInfo.delete(messageId);
31750
31768
  throw McpError.fromError(ErrorCode.RequestTimeout, "Maximum total timeout exceeded", {
31751
- maxTotalTimeout: info.maxTotalTimeout,
31769
+ maxTotalTimeout: info2.maxTotalTimeout,
31752
31770
  totalElapsed
31753
31771
  });
31754
31772
  }
31755
- clearTimeout(info.timeoutId);
31756
- info.timeoutId = setTimeout(info.onTimeout, info.timeout);
31773
+ clearTimeout(info2.timeoutId);
31774
+ info2.timeoutId = setTimeout(info2.onTimeout, info2.timeout);
31757
31775
  return true;
31758
31776
  }
31759
31777
  _cleanupTimeout(messageId) {
31760
- const info = this._timeoutInfo.get(messageId);
31761
- if (info) {
31762
- clearTimeout(info.timeoutId);
31778
+ const info2 = this._timeoutInfo.get(messageId);
31779
+ if (info2) {
31780
+ clearTimeout(info2.timeoutId);
31763
31781
  this._timeoutInfo.delete(messageId);
31764
31782
  }
31765
31783
  }
@@ -34434,24 +34452,28 @@ init_core3();
34434
34452
  // ../core/dist/commands/validation.js
34435
34453
  init_errors3();
34436
34454
  var PR_URL_PATTERN = /^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+$/;
34437
- var ISSUE_URL_PATTERN = /^https:\/\/github\.com\/[^/]+\/[^/]+\/issues\/\d+$/;
34455
+ var ISSUE_OR_PR_URL_PATTERN = /^https:\/\/github\.com\/[^/]+\/[^/]+\/(issues|pull)\/\d+$/;
34438
34456
  var MAX_URL_LENGTH = 2048;
34439
34457
  var MAX_MESSAGE_LENGTH = 1e3;
34440
34458
  function validateGitHubUrl(url, pattern, entityType) {
34441
34459
  if (pattern.test(url))
34442
34460
  return;
34443
- const example = entityType === "PR" ? "https://github.com/owner/repo/pull/123" : "https://github.com/owner/repo/issues/123";
34444
- throw new Error(`Invalid ${entityType} URL: ${url}. Expected format: ${example}`);
34461
+ const examples = {
34462
+ PR: "https://github.com/owner/repo/pull/123",
34463
+ issue: "https://github.com/owner/repo/issues/123",
34464
+ "issue or PR": "https://github.com/owner/repo/issues/123 or https://github.com/owner/repo/pull/123"
34465
+ };
34466
+ throw new ValidationError(`Invalid ${entityType} URL: ${url}. Expected format: ${examples[entityType]}`);
34445
34467
  }
34446
34468
  function validateUrl(url) {
34447
34469
  if (url.length > MAX_URL_LENGTH) {
34448
- throw new Error(`URL exceeds maximum length of ${MAX_URL_LENGTH} characters`);
34470
+ throw new ValidationError(`URL exceeds maximum length of ${MAX_URL_LENGTH} characters`);
34449
34471
  }
34450
34472
  return url;
34451
34473
  }
34452
34474
  function validateMessage(message) {
34453
34475
  if (message.length > MAX_MESSAGE_LENGTH) {
34454
- throw new Error(`Message exceeds maximum length of ${MAX_MESSAGE_LENGTH} characters`);
34476
+ throw new ValidationError(`Message exceeds maximum length of ${MAX_MESSAGE_LENGTH} characters`);
34455
34477
  }
34456
34478
  return message;
34457
34479
  }
@@ -34564,27 +34586,29 @@ async function runComments(options) {
34564
34586
  }
34565
34587
  const { owner, repo, number: pull_number } = parsed;
34566
34588
  const { data: pr } = await octokit.pulls.get({ owner, repo, pull_number });
34567
- const reviewComments = await paginateAll((page) => octokit.pulls.listReviewComments({
34568
- owner,
34569
- repo,
34570
- pull_number,
34571
- per_page: 100,
34572
- page
34573
- }));
34574
- const issueComments = await paginateAll((page) => octokit.issues.listComments({
34575
- owner,
34576
- repo,
34577
- issue_number: pull_number,
34578
- per_page: 100,
34579
- page
34580
- }));
34581
- const reviews = await paginateAll((page) => octokit.pulls.listReviews({
34582
- owner,
34583
- repo,
34584
- pull_number,
34585
- per_page: 100,
34586
- page
34587
- }));
34589
+ const [reviewComments, issueComments, reviews] = await Promise.all([
34590
+ paginateAll((page) => octokit.pulls.listReviewComments({
34591
+ owner,
34592
+ repo,
34593
+ pull_number,
34594
+ per_page: 100,
34595
+ page
34596
+ })),
34597
+ paginateAll((page) => octokit.issues.listComments({
34598
+ owner,
34599
+ repo,
34600
+ issue_number: pull_number,
34601
+ per_page: 100,
34602
+ page
34603
+ })),
34604
+ paginateAll((page) => octokit.pulls.listReviews({
34605
+ owner,
34606
+ repo,
34607
+ pull_number,
34608
+ per_page: 100,
34609
+ page
34610
+ }))
34611
+ ]);
34588
34612
  const username = stateManager2.getState().config.githubUsername;
34589
34613
  const filterComment = (c) => {
34590
34614
  if (!c.user)
@@ -34961,24 +34985,24 @@ async function runUnshelve(options) {
34961
34985
  // ../core/dist/commands/dismiss.js
34962
34986
  init_core3();
34963
34987
  async function runDismiss(options) {
34964
- validateUrl(options.issueUrl);
34965
- validateGitHubUrl(options.issueUrl, ISSUE_URL_PATTERN, "issue");
34988
+ validateUrl(options.url);
34989
+ validateGitHubUrl(options.url, ISSUE_OR_PR_URL_PATTERN, "issue or PR");
34966
34990
  const stateManager2 = getStateManager();
34967
- const added = stateManager2.dismissIssue(options.issueUrl, (/* @__PURE__ */ new Date()).toISOString());
34991
+ const added = stateManager2.dismissIssue(options.url, (/* @__PURE__ */ new Date()).toISOString());
34968
34992
  if (added) {
34969
34993
  stateManager2.save();
34970
34994
  }
34971
- return { dismissed: added, url: options.issueUrl };
34995
+ return { dismissed: added, url: options.url };
34972
34996
  }
34973
34997
  async function runUndismiss(options) {
34974
- validateUrl(options.issueUrl);
34975
- validateGitHubUrl(options.issueUrl, ISSUE_URL_PATTERN, "issue");
34998
+ validateUrl(options.url);
34999
+ validateGitHubUrl(options.url, ISSUE_OR_PR_URL_PATTERN, "issue or PR");
34976
35000
  const stateManager2 = getStateManager();
34977
- const removed = stateManager2.undismissIssue(options.issueUrl);
35001
+ const removed = stateManager2.undismissIssue(options.url);
34978
35002
  if (removed) {
34979
35003
  stateManager2.save();
34980
35004
  }
34981
- return { undismissed: removed, url: options.issueUrl };
35005
+ return { undismissed: removed, url: options.url };
34982
35006
  }
34983
35007
 
34984
35008
  // ../core/dist/commands/snooze.js
@@ -35019,14 +35043,15 @@ async function runUnsnooze(options) {
35019
35043
 
35020
35044
  // ../core/dist/commands/startup.js
35021
35045
  var fs6 = __toESM(require("fs"), 1);
35022
- var path5 = __toESM(require("path"), 1);
35023
35046
  var import_child_process2 = require("child_process");
35024
35047
  init_core3();
35048
+ init_errors3();
35025
35049
  init_daily();
35026
35050
 
35027
35051
  // ../core/dist/commands/dashboard.js
35028
35052
  var fs5 = __toESM(require("fs"), 1);
35029
35053
  init_core3();
35054
+ init_errors3();
35030
35055
  init_json();
35031
35056
  init_dashboard_data();
35032
35057
  init_dashboard_templates();
@@ -35047,15 +35072,6 @@ function writeDashboardFromState() {
35047
35072
  }
35048
35073
 
35049
35074
  // ../core/dist/commands/startup.js
35050
- function getVersion() {
35051
- try {
35052
- const pkgPath = path5.join(path5.dirname(process.argv[1]), "..", "package.json");
35053
- return JSON.parse(fs6.readFileSync(pkgPath, "utf-8")).version;
35054
- } catch (error2) {
35055
- console.error("[STARTUP] Failed to detect CLI version:", error2 instanceof Error ? error2.message : error2);
35056
- return "0.0.0";
35057
- }
35058
- }
35059
35075
  function parseIssueListPathFromConfig(configContent) {
35060
35076
  const match = configContent.match(/^---\n([\s\S]*?)\n---/);
35061
35077
  if (!match)
@@ -35092,7 +35108,7 @@ function detectIssueList() {
35092
35108
  source = "configured";
35093
35109
  }
35094
35110
  } catch (error2) {
35095
- console.error("[STARTUP] Failed to read config:", error2 instanceof Error ? error2.message : error2);
35111
+ console.error("[STARTUP] Failed to read config:", errorMessage(error2));
35096
35112
  }
35097
35113
  }
35098
35114
  if (!issueListPath) {
@@ -35112,7 +35128,7 @@ function detectIssueList() {
35112
35128
  const { availableCount, completedCount } = countIssueListItems(content);
35113
35129
  return { path: issueListPath, source, availableCount, completedCount };
35114
35130
  } catch (error2) {
35115
- console.error(`[STARTUP] Failed to read issue list at ${issueListPath}:`, error2 instanceof Error ? error2.message : error2);
35131
+ console.error(`[STARTUP] Failed to read issue list at ${issueListPath}:`, errorMessage(error2));
35116
35132
  return { path: issueListPath, source, availableCount: 0, completedCount: 0 };
35117
35133
  }
35118
35134
  }
@@ -35127,7 +35143,7 @@ function openInBrowser(filePath) {
35127
35143
  });
35128
35144
  }
35129
35145
  async function runStartup() {
35130
- const version3 = getVersion();
35146
+ const version3 = getCLIVersion();
35131
35147
  const stateManager2 = getStateManager();
35132
35148
  if (!stateManager2.isSetupComplete()) {
35133
35149
  return { version: version3, setupComplete: false };
@@ -35150,7 +35166,7 @@ async function runStartup() {
35150
35166
  dashboardOpened = true;
35151
35167
  }
35152
35168
  } catch (error2) {
35153
- console.error("[STARTUP] Dashboard generation failed:", error2 instanceof Error ? error2.message : error2);
35169
+ console.error("[STARTUP] Dashboard generation failed:", errorMessage(error2));
35154
35170
  }
35155
35171
  if (dashboardOpened) {
35156
35172
  daily.briefSummary += " | Dashboard opened in browser";
@@ -35165,26 +35181,29 @@ async function runStartup() {
35165
35181
  };
35166
35182
  }
35167
35183
 
35184
+ // ../core/dist/commands/parse-list.js
35185
+ init_errors3();
35186
+
35168
35187
  // ../core/dist/commands/check-integration.js
35169
35188
  init_core3();
35189
+ init_errors3();
35170
35190
 
35171
35191
  // ../core/dist/commands/local-repos.js
35172
- var path6 = __toESM(require("path"), 1);
35192
+ var path5 = __toESM(require("path"), 1);
35173
35193
  var os2 = __toESM(require("os"), 1);
35174
35194
  init_core3();
35195
+ init_errors3();
35175
35196
  var DEFAULT_SCAN_PATHS = [
35176
- path6.join(os2.homedir(), "Documents", "oss"),
35177
- path6.join(os2.homedir(), "dev"),
35178
- path6.join(os2.homedir(), "projects"),
35179
- path6.join(os2.homedir(), "src"),
35180
- path6.join(os2.homedir(), "code"),
35181
- path6.join(os2.homedir(), "repos")
35197
+ path5.join(os2.homedir(), "Documents", "oss"),
35198
+ path5.join(os2.homedir(), "dev"),
35199
+ path5.join(os2.homedir(), "projects"),
35200
+ path5.join(os2.homedir(), "src"),
35201
+ path5.join(os2.homedir(), "code"),
35202
+ path5.join(os2.homedir(), "repos")
35182
35203
  ];
35183
35204
 
35184
35205
  // src/tools.ts
35185
- function errorMessage(e) {
35186
- return e instanceof Error ? e.message : String(e);
35187
- }
35206
+ init_core3();
35188
35207
  function ok(data) {
35189
35208
  return {
35190
35209
  content: [{ type: "text", text: JSON.stringify(data, null, 2) ?? "null" }]
@@ -35397,9 +35416,9 @@ function registerTools(server) {
35397
35416
  server.registerTool(
35398
35417
  "dismiss",
35399
35418
  {
35400
- description: "Dismiss a GitHub issue so it no longer appears in search results.",
35419
+ description: "Dismiss a GitHub issue or PR so it no longer appears in notifications.",
35401
35420
  inputSchema: {
35402
- issueUrl: external_exports.string().describe("Full GitHub issue URL to dismiss")
35421
+ url: external_exports.string().describe("Full GitHub issue or PR URL to dismiss")
35403
35422
  },
35404
35423
  annotations: { readOnlyHint: false, destructiveHint: false }
35405
35424
  },
@@ -35408,9 +35427,9 @@ function registerTools(server) {
35408
35427
  server.registerTool(
35409
35428
  "undismiss",
35410
35429
  {
35411
- description: "Undismiss a previously dismissed issue, allowing it to appear in search results again.",
35430
+ description: "Undismiss a previously dismissed issue or PR, re-enabling notifications.",
35412
35431
  inputSchema: {
35413
- issueUrl: external_exports.string().describe("Full GitHub issue URL to undismiss")
35432
+ url: external_exports.string().describe("Full GitHub issue or PR URL to undismiss")
35414
35433
  },
35415
35434
  annotations: { readOnlyHint: false, destructiveHint: false }
35416
35435
  },
@@ -35444,6 +35463,7 @@ function registerTools(server) {
35444
35463
 
35445
35464
  // src/resources.ts
35446
35465
  init_core3();
35466
+ init_core3();
35447
35467
  function resourceContent(uri, data) {
35448
35468
  return {
35449
35469
  contents: [{ uri: uri.href, mimeType: "application/json", text: JSON.stringify(data, null, 2) }]
@@ -35508,7 +35528,7 @@ function registerResources(server) {
35508
35528
  const openPRs = getStateManager().getState().lastDigest?.openPRs ?? [];
35509
35529
  return {
35510
35530
  resources: openPRs.map((pr) => {
35511
- const [owner, repo] = pr.repo.split("/");
35531
+ const { owner, repo } = splitRepo(pr.repo);
35512
35532
  return {
35513
35533
  uri: `oss://pr/${owner}/${repo}/${pr.number}`,
35514
35534
  name: `${pr.repo}#${pr.number}`,
@@ -35549,6 +35569,7 @@ function registerResources(server) {
35549
35569
  }
35550
35570
 
35551
35571
  // src/prompts.ts
35572
+ init_core3();
35552
35573
  function userMessage(text) {
35553
35574
  return {
35554
35575
  messages: [