@oss-autopilot/core 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +85 -0
  3. package/dist/cli.bundle.cjs +17657 -0
  4. package/dist/cli.d.ts +12 -0
  5. package/dist/cli.js +325 -0
  6. package/dist/commands/check-integration.d.ts +10 -0
  7. package/dist/commands/check-integration.js +192 -0
  8. package/dist/commands/comments.d.ts +24 -0
  9. package/dist/commands/comments.js +311 -0
  10. package/dist/commands/config.d.ts +11 -0
  11. package/dist/commands/config.js +82 -0
  12. package/dist/commands/daily.d.ts +29 -0
  13. package/dist/commands/daily.js +433 -0
  14. package/dist/commands/dashboard-data.d.ts +45 -0
  15. package/dist/commands/dashboard-data.js +132 -0
  16. package/dist/commands/dashboard-templates.d.ts +23 -0
  17. package/dist/commands/dashboard-templates.js +1627 -0
  18. package/dist/commands/dashboard.d.ts +18 -0
  19. package/dist/commands/dashboard.js +134 -0
  20. package/dist/commands/dismiss.d.ts +13 -0
  21. package/dist/commands/dismiss.js +49 -0
  22. package/dist/commands/init.d.ts +10 -0
  23. package/dist/commands/init.js +27 -0
  24. package/dist/commands/local-repos.d.ts +14 -0
  25. package/dist/commands/local-repos.js +155 -0
  26. package/dist/commands/parse-list.d.ts +13 -0
  27. package/dist/commands/parse-list.js +139 -0
  28. package/dist/commands/read.d.ts +12 -0
  29. package/dist/commands/read.js +33 -0
  30. package/dist/commands/search.d.ts +10 -0
  31. package/dist/commands/search.js +74 -0
  32. package/dist/commands/setup.d.ts +15 -0
  33. package/dist/commands/setup.js +276 -0
  34. package/dist/commands/shelve.d.ts +13 -0
  35. package/dist/commands/shelve.js +49 -0
  36. package/dist/commands/snooze.d.ts +18 -0
  37. package/dist/commands/snooze.js +83 -0
  38. package/dist/commands/startup.d.ts +33 -0
  39. package/dist/commands/startup.js +197 -0
  40. package/dist/commands/status.d.ts +10 -0
  41. package/dist/commands/status.js +43 -0
  42. package/dist/commands/track.d.ts +16 -0
  43. package/dist/commands/track.js +59 -0
  44. package/dist/commands/validation.d.ts +43 -0
  45. package/dist/commands/validation.js +112 -0
  46. package/dist/commands/vet.d.ts +10 -0
  47. package/dist/commands/vet.js +36 -0
  48. package/dist/core/checklist-analysis.d.ts +17 -0
  49. package/dist/core/checklist-analysis.js +39 -0
  50. package/dist/core/ci-analysis.d.ts +78 -0
  51. package/dist/core/ci-analysis.js +163 -0
  52. package/dist/core/comment-utils.d.ts +15 -0
  53. package/dist/core/comment-utils.js +52 -0
  54. package/dist/core/concurrency.d.ts +5 -0
  55. package/dist/core/concurrency.js +15 -0
  56. package/dist/core/daily-logic.d.ts +77 -0
  57. package/dist/core/daily-logic.js +512 -0
  58. package/dist/core/display-utils.d.ts +10 -0
  59. package/dist/core/display-utils.js +100 -0
  60. package/dist/core/errors.d.ts +24 -0
  61. package/dist/core/errors.js +34 -0
  62. package/dist/core/github-stats.d.ts +73 -0
  63. package/dist/core/github-stats.js +272 -0
  64. package/dist/core/github.d.ts +19 -0
  65. package/dist/core/github.js +60 -0
  66. package/dist/core/http-cache.d.ts +97 -0
  67. package/dist/core/http-cache.js +269 -0
  68. package/dist/core/index.d.ts +15 -0
  69. package/dist/core/index.js +15 -0
  70. package/dist/core/issue-conversation.d.ts +29 -0
  71. package/dist/core/issue-conversation.js +231 -0
  72. package/dist/core/issue-discovery.d.ts +85 -0
  73. package/dist/core/issue-discovery.js +589 -0
  74. package/dist/core/issue-filtering.d.ts +51 -0
  75. package/dist/core/issue-filtering.js +103 -0
  76. package/dist/core/issue-scoring.d.ts +40 -0
  77. package/dist/core/issue-scoring.js +92 -0
  78. package/dist/core/issue-vetting.d.ts +49 -0
  79. package/dist/core/issue-vetting.js +536 -0
  80. package/dist/core/logger.d.ts +21 -0
  81. package/dist/core/logger.js +49 -0
  82. package/dist/core/maintainer-analysis.d.ts +10 -0
  83. package/dist/core/maintainer-analysis.js +59 -0
  84. package/dist/core/pagination.d.ts +11 -0
  85. package/dist/core/pagination.js +20 -0
  86. package/dist/core/pr-monitor.d.ts +109 -0
  87. package/dist/core/pr-monitor.js +594 -0
  88. package/dist/core/review-analysis.d.ts +72 -0
  89. package/dist/core/review-analysis.js +163 -0
  90. package/dist/core/state.d.ts +371 -0
  91. package/dist/core/state.js +1089 -0
  92. package/dist/core/types.d.ts +507 -0
  93. package/dist/core/types.js +34 -0
  94. package/dist/core/utils.d.ts +249 -0
  95. package/dist/core/utils.js +422 -0
  96. package/dist/formatters/json.d.ts +269 -0
  97. package/dist/formatters/json.js +88 -0
  98. package/package.json +67 -0
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Startup command
3
+ * Combines all pre-flight checks into a single CLI invocation:
4
+ * auth check, setup check, daily fetch, dashboard generation, version detection, issue list detection.
5
+ *
6
+ * Replaces the ~100-line inline bash script in commands/oss.md with a single
7
+ * `node cli.bundle.cjs startup --json` call, reducing UI noise in Claude Code.
8
+ */
9
+ import * as fs from 'fs';
10
+ import * as path from 'path';
11
+ import { execFile } from 'child_process';
12
+ import { getStateManager, getGitHubToken } from '../core/index.js';
13
+ import { outputJson, outputJsonError } from '../formatters/json.js';
14
+ import { executeDailyCheck } from './daily.js';
15
+ import { writeDashboardFromState } from './dashboard.js';
16
+ function getVersion() {
17
+ try {
18
+ const pkgPath = path.join(path.dirname(process.argv[1]), '..', 'package.json');
19
+ return JSON.parse(fs.readFileSync(pkgPath, 'utf-8')).version;
20
+ }
21
+ catch (error) {
22
+ console.error('[STARTUP] Failed to detect CLI version:', error instanceof Error ? error.message : error);
23
+ return '0.0.0';
24
+ }
25
+ }
26
+ /**
27
+ * Parse issueListPath from a config file's YAML frontmatter.
28
+ * Returns the path string or undefined if not found.
29
+ */
30
+ export function parseIssueListPathFromConfig(configContent) {
31
+ const match = configContent.match(/^---\n([\s\S]*?)\n---/);
32
+ if (!match)
33
+ return undefined;
34
+ const frontmatter = match[1];
35
+ const pathMatch = frontmatter.match(/issueListPath:\s*["']?([^"'\n]+)["']?/);
36
+ return pathMatch ? pathMatch[1].trim() : undefined;
37
+ }
38
+ /**
39
+ * Count available and completed items in an issue list file.
40
+ * Available: list items (- [) NOT struck through or marked Done.
41
+ * Completed: list items that ARE struck through or marked Done.
42
+ */
43
+ export function countIssueListItems(content) {
44
+ let availableCount = 0;
45
+ let completedCount = 0;
46
+ const lines = content.split('\n');
47
+ for (const line of lines) {
48
+ // Match list items: "- [" or "- ~~[" (strikethrough completed items)
49
+ if (/^\s*- (?:~~)?\[/.test(line)) {
50
+ if (/~~|\*\*Done\*\*/.test(line)) {
51
+ completedCount++;
52
+ }
53
+ else {
54
+ availableCount++;
55
+ }
56
+ }
57
+ }
58
+ return { availableCount, completedCount };
59
+ }
60
+ /**
61
+ * Detect an issue list file from config or known paths.
62
+ * Returns IssueListInfo or undefined if no list found.
63
+ */
64
+ export function detectIssueList() {
65
+ let issueListPath = '';
66
+ let source = 'auto-detected';
67
+ // 1. Check config file for configured path
68
+ const configPath = '.claude/oss-autopilot/config.md';
69
+ if (fs.existsSync(configPath)) {
70
+ try {
71
+ const configContent = fs.readFileSync(configPath, 'utf-8');
72
+ const configuredPath = parseIssueListPathFromConfig(configContent);
73
+ if (configuredPath && fs.existsSync(configuredPath)) {
74
+ issueListPath = configuredPath;
75
+ source = 'configured';
76
+ }
77
+ }
78
+ catch (error) {
79
+ console.error('[STARTUP] Failed to read config:', error instanceof Error ? error.message : error);
80
+ }
81
+ }
82
+ // 2. Probe known paths
83
+ if (!issueListPath) {
84
+ const probes = ['open-source/potential-issue-list.md', 'oss/issue-list.md', 'issues.md'];
85
+ for (const probe of probes) {
86
+ if (fs.existsSync(probe)) {
87
+ issueListPath = probe;
88
+ source = 'auto-detected';
89
+ break;
90
+ }
91
+ }
92
+ }
93
+ if (!issueListPath)
94
+ return undefined;
95
+ // 3. Count available/completed items
96
+ try {
97
+ const content = fs.readFileSync(issueListPath, 'utf-8');
98
+ const { availableCount, completedCount } = countIssueListItems(content);
99
+ return { path: issueListPath, source, availableCount, completedCount };
100
+ }
101
+ catch (error) {
102
+ console.error(`[STARTUP] Failed to read issue list at ${issueListPath}:`, error instanceof Error ? error.message : error);
103
+ return { path: issueListPath, source, availableCount: 0, completedCount: 0 };
104
+ }
105
+ }
106
+ function openInBrowser(filePath) {
107
+ const isWindows = process.platform === 'win32';
108
+ const openCmd = process.platform === 'darwin' ? 'open' : isWindows ? 'cmd' : 'xdg-open';
109
+ const args = isWindows ? ['/c', 'start', '', filePath] : [filePath];
110
+ execFile(openCmd, args, (error) => {
111
+ if (error) {
112
+ console.error(`[STARTUP] Failed to open dashboard in browser: ${error.message}`);
113
+ }
114
+ });
115
+ }
116
+ export async function runStartup(options) {
117
+ const version = getVersion();
118
+ const stateManager = getStateManager();
119
+ // 1. Check setup
120
+ if (!stateManager.isSetupComplete()) {
121
+ if (options.json) {
122
+ outputJson({ version, setupComplete: false });
123
+ }
124
+ else {
125
+ console.log('Setup incomplete. Run /setup-oss first.');
126
+ }
127
+ return;
128
+ }
129
+ // 2. Check auth
130
+ const token = getGitHubToken();
131
+ if (!token) {
132
+ if (options.json) {
133
+ outputJson({
134
+ version,
135
+ setupComplete: true,
136
+ authError: 'GitHub authentication required. Install GitHub CLI (https://cli.github.com/) and run "gh auth login", or set GITHUB_TOKEN.',
137
+ });
138
+ }
139
+ else {
140
+ console.error('Error: GitHub authentication required.');
141
+ }
142
+ return;
143
+ }
144
+ // 3. Run daily check
145
+ try {
146
+ const daily = await executeDailyCheck(token);
147
+ // 4. Generate dashboard from state (just saved by daily)
148
+ // Skip opening on first run (0 PRs) — the welcome flow handles onboarding
149
+ let dashboardPath;
150
+ let dashboardOpened = false;
151
+ try {
152
+ dashboardPath = writeDashboardFromState();
153
+ if (daily.digest.summary.totalActivePRs > 0) {
154
+ openInBrowser(dashboardPath);
155
+ dashboardOpened = true;
156
+ }
157
+ }
158
+ catch (error) {
159
+ console.error('[STARTUP] Dashboard generation failed:', error instanceof Error ? error.message : error);
160
+ }
161
+ // Append dashboard status to brief summary (only startup opens the browser, not daily)
162
+ if (dashboardOpened) {
163
+ daily.briefSummary += ' | Dashboard opened in browser';
164
+ }
165
+ // 5. Detect issue list
166
+ const issueList = detectIssueList();
167
+ // 6. Output
168
+ if (options.json) {
169
+ outputJson({
170
+ version,
171
+ setupComplete: true,
172
+ daily,
173
+ dashboardPath,
174
+ issueList,
175
+ });
176
+ }
177
+ else {
178
+ console.log(`OSS Autopilot v${version}`);
179
+ console.log(daily.briefSummary);
180
+ if (dashboardPath)
181
+ console.log(`Dashboard: ${dashboardPath}`);
182
+ }
183
+ }
184
+ catch (error) {
185
+ const msg = error instanceof Error ? error.message : String(error);
186
+ if (options.json) {
187
+ outputJsonError(`Daily check failed: ${msg}`);
188
+ }
189
+ else {
190
+ console.error(`[FATAL] Daily check failed: ${msg}`);
191
+ if (error instanceof Error && error.stack) {
192
+ console.error(error.stack);
193
+ }
194
+ }
195
+ process.exit(1);
196
+ }
197
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Status command
3
+ * Shows current status and stats
4
+ */
5
+ interface StatusOptions {
6
+ json?: boolean;
7
+ offline?: boolean;
8
+ }
9
+ export declare function runStatus(options: StatusOptions): Promise<void>;
10
+ export {};
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Status command
3
+ * Shows current status and stats
4
+ */
5
+ import { getStateManager } from '../core/index.js';
6
+ import { outputJson } from '../formatters/json.js';
7
+ export async function runStatus(options) {
8
+ const stateManager = getStateManager();
9
+ const stats = stateManager.getStats();
10
+ const state = stateManager.getState();
11
+ // Status always reads from local state (no API calls), so offline mode
12
+ // simply adds metadata about cache freshness.
13
+ const lastUpdated = state.lastDigestAt || state.lastRunAt;
14
+ if (options.json) {
15
+ // Extract only the stats we want to output (exclude totalTracked)
16
+ const { totalTracked: _totalTracked, ...outputStats } = stats;
17
+ const output = {
18
+ stats: outputStats,
19
+ lastRunAt: state.lastRunAt,
20
+ };
21
+ if (options.offline) {
22
+ output.offline = true;
23
+ output.lastUpdated = lastUpdated;
24
+ }
25
+ outputJson(output);
26
+ }
27
+ else {
28
+ // Simple console output
29
+ console.log('\n📊 OSS Status\n');
30
+ console.log(`Merged PRs: ${stats.mergedPRs}`);
31
+ console.log(`Closed PRs: ${stats.closedPRs}`);
32
+ console.log(`Merge Rate: ${stats.mergeRate}`);
33
+ console.log(`Needs Response: ${stats.needsResponse}`);
34
+ if (options.offline) {
35
+ console.log(`\nLast Updated: ${lastUpdated || 'Never'}`);
36
+ console.log('(Offline mode: showing cached data)');
37
+ }
38
+ else {
39
+ console.log(`\nLast Run: ${state.lastRunAt || 'Never'}`);
40
+ }
41
+ console.log('\nRun with --json for structured output');
42
+ }
43
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Track/Untrack commands
3
+ * In v2, PRs are fetched fresh from GitHub on each `daily` run.
4
+ * These commands are preserved for backward compatibility.
5
+ */
6
+ interface TrackOptions {
7
+ prUrl: string;
8
+ json?: boolean;
9
+ }
10
+ interface UntrackOptions {
11
+ prUrl: string;
12
+ json?: boolean;
13
+ }
14
+ export declare function runTrack(options: TrackOptions): Promise<void>;
15
+ export declare function runUntrack(options: UntrackOptions): Promise<void>;
16
+ export {};
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Track/Untrack commands
3
+ * In v2, PRs are fetched fresh from GitHub on each `daily` run.
4
+ * These commands are preserved for backward compatibility.
5
+ */
6
+ import { getOctokit, getGitHubToken } from '../core/index.js';
7
+ import { outputJson, outputJsonError } from '../formatters/json.js';
8
+ import { validateUrl, PR_URL_PATTERN, validateGitHubUrl } from './validation.js';
9
+ import { parseGitHubUrl } from '../core/utils.js';
10
+ export async function runTrack(options) {
11
+ validateUrl(options.prUrl);
12
+ validateGitHubUrl(options.prUrl, PR_URL_PATTERN, 'PR', options.json);
13
+ // Token is guaranteed by the preAction hook in cli.ts for non-LOCAL_ONLY_COMMANDS.
14
+ const token = getGitHubToken();
15
+ const octokit = getOctokit(token);
16
+ const parsed = parseGitHubUrl(options.prUrl);
17
+ if (!parsed || parsed.type !== 'pull') {
18
+ if (options.json) {
19
+ outputJsonError(`Invalid PR URL: ${options.prUrl}`);
20
+ }
21
+ else {
22
+ console.error(`Error: Invalid PR URL: ${options.prUrl}`);
23
+ }
24
+ process.exit(1);
25
+ }
26
+ const { owner, repo, number } = parsed;
27
+ if (!options.json) {
28
+ console.log(`\n📌 Fetching PR: ${options.prUrl}\n`);
29
+ }
30
+ const { data: ghPR } = await octokit.pulls.get({ owner, repo, pull_number: number });
31
+ const pr = {
32
+ repo: `${owner}/${repo}`,
33
+ number,
34
+ title: ghPR.title,
35
+ url: options.prUrl,
36
+ };
37
+ if (options.json) {
38
+ outputJson({ pr });
39
+ }
40
+ else {
41
+ console.log(`PR: ${pr.repo}#${pr.number} - ${pr.title}`);
42
+ console.log('Note: In v2, PRs are tracked automatically via the daily run.');
43
+ }
44
+ }
45
+ export async function runUntrack(options) {
46
+ validateUrl(options.prUrl);
47
+ validateGitHubUrl(options.prUrl, PR_URL_PATTERN, 'PR', options.json);
48
+ if (options.json) {
49
+ outputJson({
50
+ removed: false,
51
+ url: options.prUrl,
52
+ message: 'In v2, PRs are fetched fresh on each daily run — there is no local tracking list to remove from.',
53
+ });
54
+ }
55
+ else {
56
+ console.log('Note: In v2, PRs are fetched fresh on each daily run — there is no local tracking list to remove from.');
57
+ console.log('Use `shelve` to temporarily hide a PR from the daily summary.');
58
+ }
59
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Shared validation patterns and helpers for CLI commands.
3
+ */
4
+ /** Matches GitHub PR URLs: https://github.com/owner/repo/pull/123 */
5
+ export declare const PR_URL_PATTERN: RegExp;
6
+ /** Matches GitHub issue URLs: https://github.com/owner/repo/issues/123 */
7
+ export declare const ISSUE_URL_PATTERN: RegExp;
8
+ /**
9
+ * Validate a GitHub URL against a pattern. Exits with error if invalid.
10
+ */
11
+ export declare function validateGitHubUrl(url: string, pattern: RegExp, entityType: 'PR' | 'issue', json?: boolean): void;
12
+ /**
13
+ * Validate that a URL does not exceed the maximum allowed length.
14
+ * Returns the URL if valid, throws if too long.
15
+ */
16
+ export declare function validateUrl(url: string): string;
17
+ /**
18
+ * Validate that a PR/issue number is a positive integer within bounds.
19
+ * Returns the number if valid, throws if invalid.
20
+ */
21
+ export declare function validatePRNumber(num: number): number;
22
+ /**
23
+ * Validate that a message string does not exceed the maximum allowed length.
24
+ * Returns the message if valid, throws if too long.
25
+ */
26
+ export declare function validateMessage(message: string): string;
27
+ /**
28
+ * Validate that a repository identifier matches the "owner/repo" format.
29
+ * Returns the identifier if valid, throws if invalid.
30
+ */
31
+ export declare function validateRepoIdentifier(repo: string): string;
32
+ /**
33
+ * Validate a GitHub username against GitHub's username rules.
34
+ * Throws a ValidationError if the username is invalid.
35
+ *
36
+ * Rules:
37
+ * - Must not be empty
38
+ * - Maximum 39 characters
39
+ * - Only alphanumeric characters and hyphens
40
+ * - Cannot start or end with a hyphen
41
+ * - Cannot contain consecutive hyphens
42
+ */
43
+ export declare function validateGitHubUsername(username: string): string;
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Shared validation patterns and helpers for CLI commands.
3
+ */
4
+ import { ValidationError } from '../core/errors.js';
5
+ import { outputJsonError } from '../formatters/json.js';
6
+ /** Matches GitHub PR URLs: https://github.com/owner/repo/pull/123 */
7
+ export const PR_URL_PATTERN = /^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+$/;
8
+ /** Matches GitHub issue URLs: https://github.com/owner/repo/issues/123 */
9
+ export const ISSUE_URL_PATTERN = /^https:\/\/github\.com\/[^/]+\/[^/]+\/issues\/\d+$/;
10
+ /** Maximum allowed URL length */
11
+ const MAX_URL_LENGTH = 2048;
12
+ /** Maximum allowed PR/issue number */
13
+ const MAX_PR_NUMBER = 999999;
14
+ /** Maximum allowed message string length */
15
+ const MAX_MESSAGE_LENGTH = 1000;
16
+ /** Pattern for valid GitHub repository identifiers */
17
+ const REPO_PATTERN = /^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/;
18
+ /**
19
+ * Validate a GitHub URL against a pattern. Exits with error if invalid.
20
+ */
21
+ export function validateGitHubUrl(url, pattern, entityType, json) {
22
+ if (pattern.test(url))
23
+ return;
24
+ const example = entityType === 'PR' ? 'https://github.com/owner/repo/pull/123' : 'https://github.com/owner/repo/issues/123';
25
+ const msg = `Invalid ${entityType} URL: ${url}. Expected format: ${example}`;
26
+ if (json) {
27
+ outputJsonError(msg);
28
+ }
29
+ else {
30
+ console.error(`Error: ${msg}`);
31
+ }
32
+ process.exit(1);
33
+ }
34
+ /**
35
+ * Validate that a URL does not exceed the maximum allowed length.
36
+ * Returns the URL if valid, throws if too long.
37
+ */
38
+ export function validateUrl(url) {
39
+ if (url.length > MAX_URL_LENGTH) {
40
+ throw new Error(`URL exceeds maximum length of ${MAX_URL_LENGTH} characters`);
41
+ }
42
+ return url;
43
+ }
44
+ /**
45
+ * Validate that a PR/issue number is a positive integer within bounds.
46
+ * Returns the number if valid, throws if invalid.
47
+ */
48
+ export function validatePRNumber(num) {
49
+ if (!Number.isInteger(num) || num < 1 || num > MAX_PR_NUMBER) {
50
+ throw new Error(`PR number must be a positive integer up to ${MAX_PR_NUMBER}`);
51
+ }
52
+ return num;
53
+ }
54
+ /**
55
+ * Validate that a message string does not exceed the maximum allowed length.
56
+ * Returns the message if valid, throws if too long.
57
+ */
58
+ export function validateMessage(message) {
59
+ if (message.length > MAX_MESSAGE_LENGTH) {
60
+ throw new Error(`Message exceeds maximum length of ${MAX_MESSAGE_LENGTH} characters`);
61
+ }
62
+ return message;
63
+ }
64
+ /**
65
+ * Validate that a repository identifier matches the "owner/repo" format.
66
+ * Returns the identifier if valid, throws if invalid.
67
+ */
68
+ export function validateRepoIdentifier(repo) {
69
+ if (!REPO_PATTERN.test(repo)) {
70
+ throw new Error(`Invalid repository format: "${repo}". Expected "owner/repo".`);
71
+ }
72
+ return repo;
73
+ }
74
+ /** Maximum allowed GitHub username length */
75
+ const MAX_USERNAME_LENGTH = 39;
76
+ /** Pattern for valid GitHub username characters (alphanumeric and hyphens) */
77
+ const USERNAME_CHARS_PATTERN = /^[a-zA-Z0-9-]+$/;
78
+ /** Pattern for consecutive hyphens */
79
+ const CONSECUTIVE_HYPHENS_PATTERN = /--/;
80
+ /**
81
+ * Validate a GitHub username against GitHub's username rules.
82
+ * Throws a ValidationError if the username is invalid.
83
+ *
84
+ * Rules:
85
+ * - Must not be empty
86
+ * - Maximum 39 characters
87
+ * - Only alphanumeric characters and hyphens
88
+ * - Cannot start or end with a hyphen
89
+ * - Cannot contain consecutive hyphens
90
+ */
91
+ export function validateGitHubUsername(username) {
92
+ if (!username || username.trim().length === 0) {
93
+ throw new ValidationError('GitHub username cannot be empty.');
94
+ }
95
+ const trimmed = username.trim();
96
+ if (trimmed.length > MAX_USERNAME_LENGTH) {
97
+ throw new ValidationError(`GitHub username must be at most ${MAX_USERNAME_LENGTH} characters (got ${trimmed.length}).`);
98
+ }
99
+ if (!USERNAME_CHARS_PATTERN.test(trimmed)) {
100
+ throw new ValidationError('GitHub username can only contain alphanumeric characters and hyphens.');
101
+ }
102
+ if (trimmed.startsWith('-')) {
103
+ throw new ValidationError('GitHub username cannot start with a hyphen.');
104
+ }
105
+ if (trimmed.endsWith('-')) {
106
+ throw new ValidationError('GitHub username cannot end with a hyphen.');
107
+ }
108
+ if (CONSECUTIVE_HYPHENS_PATTERN.test(trimmed)) {
109
+ throw new ValidationError('GitHub username cannot contain consecutive hyphens.');
110
+ }
111
+ return trimmed;
112
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Vet command
3
+ * Vets a specific issue before working on it
4
+ */
5
+ interface VetOptions {
6
+ issueUrl: string;
7
+ json?: boolean;
8
+ }
9
+ export declare function runVet(options: VetOptions): Promise<void>;
10
+ export {};
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Vet command
3
+ * Vets a specific issue before working on it
4
+ */
5
+ import { IssueDiscovery, getGitHubToken } from '../core/index.js';
6
+ import { outputJson } from '../formatters/json.js';
7
+ import { validateUrl } from './validation.js';
8
+ export async function runVet(options) {
9
+ validateUrl(options.issueUrl);
10
+ // Token is guaranteed by the preAction hook in cli.ts for non-LOCAL_ONLY_COMMANDS.
11
+ const token = getGitHubToken();
12
+ const discovery = new IssueDiscovery(token);
13
+ if (!options.json) {
14
+ console.log(`\n🔍 Vetting issue: ${options.issueUrl}\n`);
15
+ }
16
+ const candidate = await discovery.vetIssue(options.issueUrl);
17
+ if (options.json) {
18
+ outputJson({
19
+ issue: {
20
+ repo: candidate.issue.repo,
21
+ number: candidate.issue.number,
22
+ title: candidate.issue.title,
23
+ url: candidate.issue.url,
24
+ labels: candidate.issue.labels,
25
+ },
26
+ recommendation: candidate.recommendation,
27
+ reasonsToApprove: candidate.reasonsToApprove,
28
+ reasonsToSkip: candidate.reasonsToSkip,
29
+ projectHealth: candidate.projectHealth,
30
+ vettingResult: candidate.vettingResult,
31
+ });
32
+ }
33
+ else {
34
+ console.log(discovery.formatCandidate(candidate));
35
+ }
36
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Checklist Analysis - PR body checklist detection and conditional item filtering.
3
+ * Extracted from PRMonitor to isolate checklist-related logic (#263).
4
+ */
5
+ import { FetchedPR } from './types.js';
6
+ export declare function isConditionalChecklistItem(line: string): boolean;
7
+ /**
8
+ * Analyze PR body for incomplete checklists (unchecked markdown checkboxes).
9
+ * Looks for patterns like "- [ ]" (unchecked) vs "- [x]" (checked).
10
+ * Only flags if there ARE checkboxes and some are unchecked.
11
+ * Conditional items (containing "if applicable", "(if ...)", etc.) are
12
+ * excluded from the incomplete count (#152).
13
+ */
14
+ export declare function analyzeChecklist(body: string): {
15
+ hasIncompleteChecklist: boolean;
16
+ checklistStats?: FetchedPR['checklistStats'];
17
+ };
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Checklist Analysis - PR body checklist detection and conditional item filtering.
3
+ * Extracted from PRMonitor to isolate checklist-related logic (#263).
4
+ */
5
+ /**
6
+ * Detect conditional checklist items that are intentionally left unchecked (#152).
7
+ * Matches patterns like "(if the PR is ...)", "if applicable", "N/A", "optional", etc.
8
+ * Conservative — only skips items with clear conditional language.
9
+ */
10
+ const CONDITIONAL_CHECKLIST_PATTERN = /\(if\s|\bif applicable\b|\bif needed\b|\bif relevant\b|\bonly if\b|\bwhen applicable\b|\(optional\)|- \[ \]\s*optional\b|\bn\/a\b|\bnot applicable\b|\bif required\b|\bif necessary\b/;
11
+ export function isConditionalChecklistItem(line) {
12
+ return CONDITIONAL_CHECKLIST_PATTERN.test(line.toLowerCase());
13
+ }
14
+ /**
15
+ * Analyze PR body for incomplete checklists (unchecked markdown checkboxes).
16
+ * Looks for patterns like "- [ ]" (unchecked) vs "- [x]" (checked).
17
+ * Only flags if there ARE checkboxes and some are unchecked.
18
+ * Conditional items (containing "if applicable", "(if ...)", etc.) are
19
+ * excluded from the incomplete count (#152).
20
+ */
21
+ export function analyzeChecklist(body) {
22
+ if (!body)
23
+ return { hasIncompleteChecklist: false };
24
+ const checkedPattern = /- \[x\]/gi;
25
+ const uncheckedLinePattern = /^.*- \[ \].*$/gm;
26
+ const checkedMatches = body.match(checkedPattern) || [];
27
+ const uncheckedLines = body.match(uncheckedLinePattern) || [];
28
+ const checked = checkedMatches.length;
29
+ const total = checked + uncheckedLines.length;
30
+ // No checkboxes at all - not a checklist PR
31
+ if (total === 0)
32
+ return { hasIncompleteChecklist: false };
33
+ // Filter out conditional checklist items that are intentionally unchecked
34
+ const nonConditionalUnchecked = uncheckedLines.filter((line) => !isConditionalChecklistItem(line));
35
+ return {
36
+ hasIncompleteChecklist: nonConditionalUnchecked.length > 0,
37
+ checklistStats: { checked, total },
38
+ };
39
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * CI Analysis - Classification and analysis of CI check runs and combined statuses.
3
+ * Extracted from PRMonitor to isolate CI-related logic (#263).
4
+ */
5
+ import { CIFailureCategory, ClassifiedCheck, CIStatusResult } from './types.js';
6
+ /**
7
+ * Known CI check name patterns that indicate fork limitations rather than real failures (#81).
8
+ * These are deployment/preview services that require repo-level secrets unavailable in forks.
9
+ */
10
+ export declare const FORK_LIMITATION_PATTERNS: RegExp[];
11
+ /**
12
+ * Known CI check name patterns that indicate authorization gates (#81).
13
+ * These require maintainer approval and are not real failures.
14
+ */
15
+ export declare const AUTH_GATE_PATTERNS: RegExp[];
16
+ /**
17
+ * Known CI check name patterns that indicate infrastructure/transient failures (#145).
18
+ * These are runner issues, dependency install problems, or service outages — not code failures.
19
+ */
20
+ export declare const INFRASTRUCTURE_PATTERNS: RegExp[];
21
+ /**
22
+ * Classify a failing CI check as actionable, fork_limitation, auth_gate, or infrastructure (#81, #145).
23
+ * Default is 'actionable' — only known patterns get reclassified.
24
+ * When conclusion is provided (cancelled, timed_out), the check is classified as infrastructure.
25
+ */
26
+ export declare function classifyCICheck(name: string, description?: string, conclusion?: string): CIFailureCategory;
27
+ /**
28
+ * Classify all failing checks and return both the flat names array and classified array (#81, #145).
29
+ * Accepts optional conclusion data to detect infrastructure failures.
30
+ */
31
+ export declare function classifyFailingChecks(failingCheckNames: string[], conclusions?: Map<string, string>): ClassifiedCheck[];
32
+ /**
33
+ * Analyze check runs (GitHub Actions, etc.) and categorize them.
34
+ * Returns flags for failing/pending/success and lists of failing check names + conclusions.
35
+ */
36
+ export declare function analyzeCheckRuns(checkRuns: Array<{
37
+ name: string;
38
+ conclusion: string | null;
39
+ status: string;
40
+ }>): {
41
+ hasFailingChecks: boolean;
42
+ hasPendingChecks: boolean;
43
+ hasSuccessfulChecks: boolean;
44
+ failingCheckNames: string[];
45
+ failingCheckConclusions: Map<string, string>;
46
+ };
47
+ /** Result shape from analyzeCheckRuns, used by mergeStatuses. */
48
+ export interface CheckRunAnalysis {
49
+ hasFailingChecks: boolean;
50
+ hasPendingChecks: boolean;
51
+ hasSuccessfulChecks: boolean;
52
+ failingCheckNames: string[];
53
+ failingCheckConclusions: Map<string, string>;
54
+ }
55
+ /** Result shape from analyzeCombinedStatus, used by mergeStatuses. */
56
+ export interface CombinedStatusAnalysis {
57
+ effectiveCombinedState: string;
58
+ hasStatuses: boolean;
59
+ failingStatusNames: string[];
60
+ }
61
+ /**
62
+ * Analyze combined status API results (Travis, CircleCI, etc.).
63
+ * Filters out authorization-gate statuses and determines the effective combined state.
64
+ * Returns failing status context names in the result (does not mutate caller arrays).
65
+ */
66
+ export declare function analyzeCombinedStatus(combinedStatus: {
67
+ state: string;
68
+ statuses: Array<{
69
+ state: string;
70
+ context: string;
71
+ description: string | null;
72
+ }>;
73
+ }): CombinedStatusAnalysis;
74
+ /**
75
+ * Merge check run analysis and combined status analysis into a final CIStatusResult.
76
+ * Priority: failing > pending > passing > unknown.
77
+ */
78
+ export declare function mergeStatuses(checkRunAnalysis: CheckRunAnalysis, combinedAnalysis: CombinedStatusAnalysis, checkRunCount: number): CIStatusResult;