@humanbased/crosscheck 0.14.0 → 0.15.0-beta.103
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +147 -4
- package/crosscheck.config.example.yml +3 -1
- package/dist/__tests__/backtrace.test.js +1 -1
- package/dist/__tests__/backtrace.test.js.map +1 -1
- package/dist/__tests__/durations.test.js +5 -1
- package/dist/__tests__/durations.test.js.map +1 -1
- package/dist/__tests__/fix.test.js +48 -1
- package/dist/__tests__/fix.test.js.map +1 -1
- package/dist/__tests__/kickass.test.js +362 -69
- package/dist/__tests__/kickass.test.js.map +1 -1
- package/dist/__tests__/optimize.test.js +3 -3
- package/dist/__tests__/optimize.test.js.map +1 -1
- package/dist/__tests__/pr-picker.test.js +8 -7
- package/dist/__tests__/pr-picker.test.js.map +1 -1
- package/dist/__tests__/pr-status.test.js +41 -20
- package/dist/__tests__/pr-status.test.js.map +1 -1
- package/dist/__tests__/pr-workflow-state.test.d.ts +2 -0
- package/dist/__tests__/pr-workflow-state.test.d.ts.map +1 -0
- package/dist/__tests__/pr-workflow-state.test.js +184 -0
- package/dist/__tests__/pr-workflow-state.test.js.map +1 -0
- package/dist/__tests__/review-models.test.js +1 -0
- package/dist/__tests__/review-models.test.js.map +1 -1
- package/dist/__tests__/run.test.d.ts +2 -0
- package/dist/__tests__/run.test.d.ts.map +1 -0
- package/dist/__tests__/run.test.js +81 -0
- package/dist/__tests__/run.test.js.map +1 -0
- package/dist/__tests__/runner.test.js +117 -1
- package/dist/__tests__/runner.test.js.map +1 -1
- package/dist/__tests__/smart-switch.test.js +1 -1
- package/dist/__tests__/smart-switch.test.js.map +1 -1
- package/dist/__tests__/tier-timeouts.test.d.ts +2 -0
- package/dist/__tests__/tier-timeouts.test.d.ts.map +1 -0
- package/dist/__tests__/tier-timeouts.test.js +23 -0
- package/dist/__tests__/tier-timeouts.test.js.map +1 -0
- package/dist/__tests__/webhook.test.d.ts +2 -0
- package/dist/__tests__/webhook.test.d.ts.map +1 -0
- package/dist/__tests__/webhook.test.js +197 -0
- package/dist/__tests__/webhook.test.js.map +1 -0
- package/dist/cli.js +38 -5
- package/dist/cli.js.map +1 -1
- package/dist/commands/detect-step.d.ts +5 -0
- package/dist/commands/detect-step.d.ts.map +1 -0
- package/dist/commands/detect-step.js +124 -0
- package/dist/commands/detect-step.js.map +1 -0
- package/dist/commands/kickass.d.ts +18 -10
- package/dist/commands/kickass.d.ts.map +1 -1
- package/dist/commands/kickass.js +234 -63
- package/dist/commands/kickass.js.map +1 -1
- package/dist/commands/review.d.ts.map +1 -1
- package/dist/commands/review.js +14 -5
- package/dist/commands/review.js.map +1 -1
- package/dist/commands/run.d.ts +16 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +347 -44
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +41 -3
- package/dist/commands/serve.js.map +1 -1
- package/dist/commands/watch.d.ts.map +1 -1
- package/dist/commands/watch.js +200 -6
- package/dist/commands/watch.js.map +1 -1
- package/dist/config/schema.d.ts +52 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +24 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/github/client.d.ts +40 -1
- package/dist/github/client.d.ts.map +1 -1
- package/dist/github/client.js +69 -9
- package/dist/github/client.js.map +1 -1
- package/dist/github/review-status.d.ts.map +1 -1
- package/dist/github/review-status.js +7 -4
- package/dist/github/review-status.js.map +1 -1
- package/dist/github/webhook.d.ts +25 -1
- package/dist/github/webhook.d.ts.map +1 -1
- package/dist/github/webhook.js +37 -1
- package/dist/github/webhook.js.map +1 -1
- package/dist/lib/annotation.d.ts +4 -0
- package/dist/lib/annotation.d.ts.map +1 -1
- package/dist/lib/annotation.js +5 -1
- package/dist/lib/annotation.js.map +1 -1
- package/dist/lib/comment-bodies.d.ts.map +1 -1
- package/dist/lib/comment-bodies.js +3 -2
- package/dist/lib/comment-bodies.js.map +1 -1
- package/dist/lib/durations.d.ts.map +1 -1
- package/dist/lib/durations.js +5 -3
- package/dist/lib/durations.js.map +1 -1
- package/dist/lib/logger.d.ts +3 -0
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/logger.js +29 -3
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/pr-picker.d.ts.map +1 -1
- package/dist/lib/pr-picker.js +5 -1
- package/dist/lib/pr-picker.js.map +1 -1
- package/dist/lib/pr-status.d.ts +4 -3
- package/dist/lib/pr-status.d.ts.map +1 -1
- package/dist/lib/pr-status.js +19 -13
- package/dist/lib/pr-status.js.map +1 -1
- package/dist/lib/pr-workflow-state.d.ts +68 -0
- package/dist/lib/pr-workflow-state.d.ts.map +1 -0
- package/dist/lib/pr-workflow-state.js +328 -0
- package/dist/lib/pr-workflow-state.js.map +1 -0
- package/dist/lib/product.d.ts +3 -0
- package/dist/lib/product.d.ts.map +1 -0
- package/dist/lib/product.js +5 -0
- package/dist/lib/product.js.map +1 -0
- package/dist/lib/repo-picker.d.ts +1 -0
- package/dist/lib/repo-picker.d.ts.map +1 -1
- package/dist/lib/repo-picker.js +50 -33
- package/dist/lib/repo-picker.js.map +1 -1
- package/dist/lib/runner.d.ts +19 -1
- package/dist/lib/runner.d.ts.map +1 -1
- package/dist/lib/runner.js +337 -54
- package/dist/lib/runner.js.map +1 -1
- package/dist/lib/smart-switch.js +1 -1
- package/dist/lib/smart-switch.js.map +1 -1
- package/dist/lib/vendor.d.ts +4 -0
- package/dist/lib/vendor.d.ts.map +1 -0
- package/dist/lib/vendor.js +14 -0
- package/dist/lib/vendor.js.map +1 -0
- package/dist/lib/workflow.d.ts +5 -0
- package/dist/lib/workflow.d.ts.map +1 -1
- package/dist/lib/workflow.js.map +1 -1
- package/dist/reviewers/claude.d.ts +3 -1
- package/dist/reviewers/claude.d.ts.map +1 -1
- package/dist/reviewers/claude.js +14 -9
- package/dist/reviewers/claude.js.map +1 -1
- package/dist/reviewers/codex.d.ts +1 -1
- package/dist/reviewers/codex.d.ts.map +1 -1
- package/dist/reviewers/codex.js +7 -10
- package/dist/reviewers/codex.js.map +1 -1
- package/dist/reviewers/conflict-resolve.d.ts +1 -1
- package/dist/reviewers/conflict-resolve.d.ts.map +1 -1
- package/dist/reviewers/conflict-resolve.js +3 -2
- package/dist/reviewers/conflict-resolve.js.map +1 -1
- package/dist/reviewers/fix.d.ts +5 -1
- package/dist/reviewers/fix.d.ts.map +1 -1
- package/dist/reviewers/fix.js +68 -2
- package/dist/reviewers/fix.js.map +1 -1
- package/dist/reviewers/tier-timeouts.d.ts +5 -0
- package/dist/reviewers/tier-timeouts.d.ts.map +1 -0
- package/dist/reviewers/tier-timeouts.js +14 -0
- package/dist/reviewers/tier-timeouts.js.map +1 -0
- package/get-started.md +56 -5
- package/get-started.zh.md +7 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect-step.d.ts","sourceRoot":"","sources":["../../src/commands/detect-step.ts"],"names":[],"mappings":"AAgDA,wBAAsB,aAAa,CACjC,KAAK,EAAE,MAAM,EACb,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GAC7C,OAAO,CAAC,IAAI,CAAC,CAmFf"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { createGithubClient } from '../github/client.js';
|
|
4
|
+
import { getGithubToken } from '../config/loader.js';
|
|
5
|
+
import { loadWorkflow } from '../lib/workflow.js';
|
|
6
|
+
import { fetchStepHistory, identifyNextWorkflowStep } from '../lib/pr-workflow-state.js';
|
|
7
|
+
function parsePRUrl(url) {
|
|
8
|
+
const m = url.match(/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/);
|
|
9
|
+
if (!m)
|
|
10
|
+
return null;
|
|
11
|
+
return { owner: m[1], repo: m[2], number: parseInt(m[3], 10) };
|
|
12
|
+
}
|
|
13
|
+
function fmtDate(iso) {
|
|
14
|
+
const d = new Date(iso);
|
|
15
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
16
|
+
return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`;
|
|
17
|
+
}
|
|
18
|
+
function verdictColor(verdict) {
|
|
19
|
+
if (!verdict)
|
|
20
|
+
return chalk.dim('?');
|
|
21
|
+
if (verdict === 'APPROVE')
|
|
22
|
+
return chalk.green(verdict);
|
|
23
|
+
if (verdict === 'BLOCK')
|
|
24
|
+
return chalk.red(verdict);
|
|
25
|
+
return chalk.yellow(verdict.replace('_', ' '));
|
|
26
|
+
}
|
|
27
|
+
function printHistory(records) {
|
|
28
|
+
if (records.length === 0) {
|
|
29
|
+
console.log(chalk.dim(' (no crosscheck steps found)'));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const typeWidth = Math.max(...records.map(r => r.type.length));
|
|
33
|
+
for (let i = 0; i < records.length; i++) {
|
|
34
|
+
const r = records[i];
|
|
35
|
+
const idx = chalk.dim(String(i + 1).padStart(2));
|
|
36
|
+
const date = chalk.dim(fmtDate(r.createdAt));
|
|
37
|
+
const type = r.type.padEnd(typeWidth);
|
|
38
|
+
const reviewer = r.reviewer ? chalk.cyan(r.reviewer) + (r.model ? chalk.dim(`·${r.model}`) : '') : chalk.dim('—');
|
|
39
|
+
const recordSha = r.sha ?? r.pushedSha;
|
|
40
|
+
const sha = recordSha ? chalk.dim(`sha=${recordSha.slice(0, 7)}`) : '';
|
|
41
|
+
const round = r.round > 1 ? chalk.dim(`round=${r.round}`) : '';
|
|
42
|
+
const source = r.source === 'commit' ? chalk.dim('source=commit') : '';
|
|
43
|
+
const verdict = r.type === 'review' || r.type === 'recheck' ? verdictColor(r.verdict) : '';
|
|
44
|
+
const parts = [reviewer, sha, source, round, verdict].filter(Boolean).join(' ');
|
|
45
|
+
console.log(` ${idx} ${date} ${type} ${parts}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export async function runDetectStep(prUrl, opts = {}) {
|
|
49
|
+
const parsed = parsePRUrl(prUrl);
|
|
50
|
+
if (!parsed) {
|
|
51
|
+
console.error(chalk.red('Invalid PR URL. Expected: https://github.com/owner/repo/pull/123'));
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
const { owner, repo, number } = parsed;
|
|
55
|
+
let token;
|
|
56
|
+
try {
|
|
57
|
+
token = getGithubToken();
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
console.error(chalk.red(`✗ ${err instanceof Error ? err.message : String(err)}`));
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
const spinner = ora(`Fetching PR #${number}...`).start();
|
|
64
|
+
const octokit = createGithubClient(token);
|
|
65
|
+
const { data: prData } = await octokit.rest.pulls.get({ owner, repo, pull_number: number });
|
|
66
|
+
spinner.succeed(`PR #${number} · ${prData.title}`);
|
|
67
|
+
const steps = loadWorkflow(process.cwd());
|
|
68
|
+
const historySpinner = ora('Reading workflow history...').start();
|
|
69
|
+
const history = await fetchStepHistory(owner, repo, number, token);
|
|
70
|
+
historySpinner.stop();
|
|
71
|
+
const currentSha = prData.head.sha;
|
|
72
|
+
const nextResult = identifyNextWorkflowStep(history, steps, currentSha);
|
|
73
|
+
if (opts.json) {
|
|
74
|
+
console.log(JSON.stringify({
|
|
75
|
+
pr: { number, title: prData.title, headSha: currentSha, base: prData.base.ref },
|
|
76
|
+
history: history.map(r => ({
|
|
77
|
+
type: r.type,
|
|
78
|
+
verdict: r.verdict,
|
|
79
|
+
sha: r.sha,
|
|
80
|
+
pushedSha: r.pushedSha,
|
|
81
|
+
source: r.source,
|
|
82
|
+
round: r.round,
|
|
83
|
+
commentId: r.commentId,
|
|
84
|
+
createdAt: r.createdAt,
|
|
85
|
+
reviewer: r.reviewer,
|
|
86
|
+
model: r.model,
|
|
87
|
+
next_step: r.next_step,
|
|
88
|
+
})),
|
|
89
|
+
next: nextResult.step
|
|
90
|
+
? {
|
|
91
|
+
step: nextResult.step.type,
|
|
92
|
+
stepName: nextResult.step.name,
|
|
93
|
+
round: nextResult.round,
|
|
94
|
+
reviewCommentId: nextResult.reviewComment?.id,
|
|
95
|
+
}
|
|
96
|
+
: null,
|
|
97
|
+
}, null, 2));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
console.log();
|
|
101
|
+
console.log(chalk.dim(` HEAD ${currentSha.slice(0, 7)} · base: ${prData.base.ref}`));
|
|
102
|
+
console.log(chalk.dim(` workflow steps: ${steps.map(s => s.name).join(' → ')}`));
|
|
103
|
+
console.log();
|
|
104
|
+
const divider = chalk.dim('─'.repeat(70));
|
|
105
|
+
console.log(` ${chalk.bold('Step history')} ${chalk.dim(`(${history.length} entr${history.length === 1 ? 'y' : 'ies'})`)}\n ${divider}`);
|
|
106
|
+
printHistory(history);
|
|
107
|
+
console.log(` ${divider}`);
|
|
108
|
+
console.log();
|
|
109
|
+
if (nextResult.step === null) {
|
|
110
|
+
const lastVerdict = [...history].reverse().find(r => r.verdict)?.verdict;
|
|
111
|
+
console.log(` ${chalk.green('✓')} Workflow complete${lastVerdict ? ` — last verdict: ${verdictColor(lastVerdict)}` : ''}`);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
const nextLabel = chalk.bold(nextResult.step.type);
|
|
115
|
+
const roundLabel = nextResult.round > 1 ? chalk.dim(` (round ${nextResult.round})`) : '';
|
|
116
|
+
console.log(` Next step: ${nextLabel}${roundLabel}`);
|
|
117
|
+
if (nextResult.reviewComment) {
|
|
118
|
+
const commentUrl = `https://github.com/${owner}/${repo}/pull/${number}#issuecomment-${nextResult.reviewComment.id}`;
|
|
119
|
+
console.log(` Context: review comment #${nextResult.reviewComment.id} · ${chalk.dim(commentUrl)}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
console.log();
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=detect-step.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect-step.js","sourceRoot":"","sources":["../../src/commands/detect-step.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,12 +1,19 @@
|
|
|
1
|
+
import type { Config } from '../config/schema.js';
|
|
2
|
+
import type { ErrorCategory } from '../lib/logger.js';
|
|
1
3
|
import type { ScanPRStatus as PRStatus, ScanResult } from '../lib/pr-status.js';
|
|
2
4
|
export interface KickassOpts {
|
|
3
5
|
force?: boolean;
|
|
4
6
|
staleAfter?: string;
|
|
5
7
|
dryRun?: boolean;
|
|
8
|
+
roundMode?: 'crazy' | 'halfcrazy';
|
|
9
|
+
timeout?: string;
|
|
10
|
+
concurrent?: number;
|
|
11
|
+
staggerMs?: number;
|
|
6
12
|
}
|
|
7
|
-
export type KickassAction = 'review' | 'fix' | 'recheck' | '
|
|
13
|
+
export type KickassAction = 'review' | 'fix' | 'recheck' | 'skip';
|
|
8
14
|
export type KickassSkipReason = 'fork_pr' | 'stale_signature';
|
|
9
|
-
export type KickassFailureReason =
|
|
15
|
+
export type KickassFailureReason = ErrorCategory;
|
|
16
|
+
export type FixDeliveryMode = Config['post_review']['auto_fix']['delivery']['mode'];
|
|
10
17
|
export interface PreflightItem {
|
|
11
18
|
pr: PRStatus;
|
|
12
19
|
action: KickassAction;
|
|
@@ -14,6 +21,7 @@ export interface PreflightItem {
|
|
|
14
21
|
details: string[];
|
|
15
22
|
explanation?: string;
|
|
16
23
|
skipReason?: KickassSkipReason;
|
|
24
|
+
chainRecheck?: boolean;
|
|
17
25
|
}
|
|
18
26
|
export interface KickassExecutionResult {
|
|
19
27
|
pr: PRStatus;
|
|
@@ -22,8 +30,7 @@ export interface KickassExecutionResult {
|
|
|
22
30
|
}
|
|
23
31
|
export interface ExecuteKickassDeps {
|
|
24
32
|
getCurrentHeadSha: (item: PreflightItem) => Promise<string>;
|
|
25
|
-
dispatchRun: (item: PreflightItem) => Promise<void>;
|
|
26
|
-
dispatchMerge: (item: PreflightItem) => Promise<void>;
|
|
33
|
+
dispatchRun: (item: PreflightItem) => Promise<string | void>;
|
|
27
34
|
}
|
|
28
35
|
export interface KickassDeps {
|
|
29
36
|
loadScanResult: (options: {
|
|
@@ -32,18 +39,19 @@ export interface KickassDeps {
|
|
|
32
39
|
}) => Promise<ScanResult>;
|
|
33
40
|
pickPRs: (prs: PRStatus[]) => Promise<PRStatus[]>;
|
|
34
41
|
confirm: (message: string) => Promise<boolean>;
|
|
42
|
+
getFixDeliveryMode?: () => FixDeliveryMode | Promise<FixDeliveryMode>;
|
|
35
43
|
getCurrentHeadSha: (item: PreflightItem) => Promise<string>;
|
|
36
|
-
dispatchRun: (item: PreflightItem) => Promise<void>;
|
|
37
|
-
dispatchMerge: (item: PreflightItem) => Promise<void>;
|
|
44
|
+
dispatchRun: (item: PreflightItem) => Promise<string | void>;
|
|
38
45
|
}
|
|
39
46
|
export declare function runKickass(opts?: KickassOpts): Promise<void>;
|
|
40
47
|
export declare function runKickassWithDeps(opts: KickassOpts | undefined, deps: KickassDeps): Promise<void>;
|
|
41
|
-
export declare function buildPreflightPlan(prs: PRStatus[]): PreflightItem[];
|
|
42
|
-
export declare function executeKickassPlan(plan: PreflightItem[], deps: ExecuteKickassDeps): Promise<KickassExecutionResult[]>;
|
|
43
|
-
export declare function printPreflight(plan: PreflightItem[]): void;
|
|
48
|
+
export declare function buildPreflightPlan(prs: PRStatus[], roundMode?: 'crazy' | 'halfcrazy', fixDeliveryMode?: FixDeliveryMode): PreflightItem[];
|
|
49
|
+
export declare function executeKickassPlan(plan: PreflightItem[], deps: ExecuteKickassDeps, concurrency?: number, staggerMs?: number): Promise<KickassExecutionResult[]>;
|
|
50
|
+
export declare function printPreflight(plan: PreflightItem[], mergeReady?: PRStatus[]): void;
|
|
51
|
+
export declare function printMergeReady(prs: PRStatus[]): void;
|
|
44
52
|
export declare function summarizeExecutionResults(results: KickassExecutionResult[]): string;
|
|
45
53
|
export declare function printExecutionSummary(results: KickassExecutionResult[]): void;
|
|
46
|
-
export declare function buildKickassRunArgs(itemOrPR: PreflightItem | PRStatus): string[];
|
|
54
|
+
export declare function buildKickassRunArgs(itemOrPR: PreflightItem | PRStatus, roundMode?: 'crazy' | 'halfcrazy', timeout?: string): string[];
|
|
47
55
|
export interface CliInvocation {
|
|
48
56
|
command: string;
|
|
49
57
|
args: string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kickass.d.ts","sourceRoot":"","sources":["../../src/commands/kickass.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"kickass.d.ts","sourceRoot":"","sources":["../../src/commands/kickass.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAGjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAErD,OAAO,KAAK,EAAE,YAAY,IAAI,QAAQ,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAG/E,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,SAAS,CAAC,EAAE,OAAO,GAAG,WAAW,CAAA;IACjC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,KAAK,GAAG,SAAS,GAAG,MAAM,CAAA;AACjE,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,iBAAiB,CAAA;AAC7D,MAAM,MAAM,oBAAoB,GAAG,aAAa,CAAA;AAChD,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,aAAa,CAAC,CAAC,UAAU,CAAC,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAA;AAEnF,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,QAAQ,CAAA;IACZ,MAAM,EAAE,aAAa,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,iBAAiB,CAAA;IAC9B,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,QAAQ,CAAA;IACZ,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAA;IACzC,MAAM,CAAC,EAAE,iBAAiB,GAAG,oBAAoB,CAAA;CAClD;AAED,MAAM,WAAW,kBAAkB;IACjC,iBAAiB,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAG3D,WAAW,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;CAC7D;AAED,MAAM,WAAW,WAAW;IAC1B,cAAc,EAAE,CAAC,OAAO,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,UAAU,CAAC,CAAA;IAC3F,OAAO,EAAE,CAAC,GAAG,EAAE,QAAQ,EAAE,KAAK,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAA;IACjD,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAC9C,kBAAkB,CAAC,EAAE,MAAM,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAAA;IACrE,iBAAiB,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAC3D,WAAW,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;CAC7D;AAED,wBAAsB,UAAU,CAAC,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAEtE;AAED,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,WAAW,YAAK,EACtB,IAAI,EAAE,WAAW,GAChB,OAAO,CAAC,IAAI,CAAC,CAoFf;AAED,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,QAAQ,EAAE,EACf,SAAS,CAAC,EAAE,OAAO,GAAG,WAAW,EACjC,eAAe,GAAE,eAAgC,GAChD,aAAa,EAAE,CA0DjB;AAeD,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,aAAa,EAAE,EACrB,IAAI,EAAE,kBAAkB,EACxB,WAAW,SAAI,EACf,SAAS,SAAI,GACZ,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAwHnC;AAWD,wBAAgB,cAAc,CAAC,IAAI,EAAE,aAAa,EAAE,EAAE,UAAU,GAAE,QAAQ,EAAO,GAAG,IAAI,CAWvF;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,QAAQ,EAAE,GAAG,IAAI,CAKrD;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,sBAAsB,EAAE,GAAG,MAAM,CAKnF;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,sBAAsB,EAAE,GAAG,IAAI,CAE7E;AAED,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,aAAa,GAAG,QAAQ,EAClC,SAAS,CAAC,EAAE,OAAO,GAAG,WAAW,EACjC,OAAO,CAAC,EAAE,MAAM,GACf,MAAM,EAAE,CAyBV;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,EAAE,CAAA;CACf;AAED,UAAU,2BAA2B;IACnC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;IAClC,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,MAAM,CAAA;CACjC;AAED,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,2BAAgC,GAAG,aAAa,CAkB7F"}
|
package/dist/commands/kickass.js
CHANGED
|
@@ -5,14 +5,13 @@ import { createInterface } from 'readline/promises';
|
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { execa } from 'execa';
|
|
7
7
|
import { createGithubClient } from '../github/client.js';
|
|
8
|
-
import {
|
|
9
|
-
import { getGithubToken } from '../config/loader.js';
|
|
8
|
+
import { getGithubToken, loadConfig } from '../config/loader.js';
|
|
10
9
|
import { parseDuration } from '../lib/durations.js';
|
|
11
|
-
import { logError } from '../lib/logger.js';
|
|
10
|
+
import { classifyError, logError } from '../lib/logger.js';
|
|
12
11
|
import { pickPRs } from '../lib/pr-picker.js';
|
|
13
12
|
import { handleScanError, loadScanResult } from './scan.js';
|
|
14
13
|
export async function runKickass(opts = {}) {
|
|
15
|
-
await runKickassWithDeps(opts, defaultKickassDeps());
|
|
14
|
+
await runKickassWithDeps(opts, defaultKickassDeps(opts));
|
|
16
15
|
}
|
|
17
16
|
export async function runKickassWithDeps(opts = {}, deps) {
|
|
18
17
|
let staleAfterMs;
|
|
@@ -23,11 +22,38 @@ export async function runKickassWithDeps(opts = {}, deps) {
|
|
|
23
22
|
console.error(chalk.red(`✗ ${err instanceof Error ? err.message : String(err)}`));
|
|
24
23
|
process.exit(1);
|
|
25
24
|
}
|
|
25
|
+
if (opts.timeout) {
|
|
26
|
+
try {
|
|
27
|
+
parseDuration(opts.timeout);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
console.error(chalk.red(`✗ Invalid --timeout value "${opts.timeout}". Use a duration like 300s or 10m.`));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (opts.concurrent !== undefined && (opts.concurrent < 0 || !Number.isInteger(opts.concurrent))) {
|
|
35
|
+
console.error(chalk.red('✗ --concurrent must be a non-negative integer (0 = one agent per selected PR)'));
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
26
38
|
try {
|
|
27
39
|
const scan = await deps.loadScanResult({ force: opts.force, staleAfterMs });
|
|
28
|
-
|
|
40
|
+
// Actionable = nextAction is set and is not merge (merge not dispatched in v1).
|
|
41
|
+
// Stale PRs shown first; not-stale actionable PRs follow.
|
|
42
|
+
const queue = scan.prs
|
|
43
|
+
.filter(pr => pr.nextAction !== null && pr.nextAction !== 'merge')
|
|
44
|
+
.sort((a, b) => {
|
|
45
|
+
if (a.freshness !== b.freshness)
|
|
46
|
+
return a.freshness === 'stale' ? -1 : 1;
|
|
47
|
+
return 0;
|
|
48
|
+
});
|
|
49
|
+
const mergeReady = scan.prs.filter(pr => pr.nextAction === 'merge');
|
|
50
|
+
if (queue.length === 0 && mergeReady.length === 0) {
|
|
51
|
+
printNoActionablePRsWarning(scan.cached);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
29
54
|
if (queue.length === 0) {
|
|
30
|
-
|
|
55
|
+
printMergeReady(mergeReady);
|
|
56
|
+
console.log(chalk.dim('\nNo PRs need review, fix, or recheck — all actionable work is merge-ready (manual).'));
|
|
31
57
|
return;
|
|
32
58
|
}
|
|
33
59
|
const selected = await deps.pickPRs(queue);
|
|
@@ -35,8 +61,9 @@ export async function runKickassWithDeps(opts = {}, deps) {
|
|
|
35
61
|
console.log(chalk.dim('No PRs selected.'));
|
|
36
62
|
return;
|
|
37
63
|
}
|
|
38
|
-
const
|
|
39
|
-
|
|
64
|
+
const fixDeliveryMode = deps.getFixDeliveryMode ? await deps.getFixDeliveryMode() : 'pull_request';
|
|
65
|
+
const plan = buildPreflightPlan(selected, opts.roundMode, fixDeliveryMode);
|
|
66
|
+
printPreflight(plan, mergeReady);
|
|
40
67
|
if (opts.dryRun) {
|
|
41
68
|
console.log(chalk.dim('\ndry-run: no mutations executed'));
|
|
42
69
|
return;
|
|
@@ -46,7 +73,15 @@ export async function runKickassWithDeps(opts = {}, deps) {
|
|
|
46
73
|
console.log(chalk.dim('Canceled.'));
|
|
47
74
|
return;
|
|
48
75
|
}
|
|
49
|
-
|
|
76
|
+
// 0 = one agent per selected PR; undefined/1 = sequential
|
|
77
|
+
const resolvedConcurrency = opts.concurrent === 0
|
|
78
|
+
? selected.length
|
|
79
|
+
: Math.max(1, opts.concurrent ?? 1);
|
|
80
|
+
const resolvedStagger = resolvedConcurrency > 1 ? (opts.staggerMs ?? 2_000) : 0;
|
|
81
|
+
if (resolvedConcurrency > 1) {
|
|
82
|
+
console.log(chalk.dim(`\n running ${resolvedConcurrency} agents in parallel (${resolvedStagger}ms stagger)`));
|
|
83
|
+
}
|
|
84
|
+
const results = await executeKickassPlan(plan, deps, resolvedConcurrency, resolvedStagger);
|
|
50
85
|
printExecutionSummary(results);
|
|
51
86
|
if (results.some(result => result.status === 'failed')) {
|
|
52
87
|
process.exitCode = 2;
|
|
@@ -56,7 +91,9 @@ export async function runKickassWithDeps(opts = {}, deps) {
|
|
|
56
91
|
handleScanError('kickass', err);
|
|
57
92
|
}
|
|
58
93
|
}
|
|
59
|
-
export function buildPreflightPlan(prs) {
|
|
94
|
+
export function buildPreflightPlan(prs, roundMode, fixDeliveryMode = 'pull_request') {
|
|
95
|
+
const modeTag = roundMode ? ` [${roundMode}]` : '';
|
|
96
|
+
const chainRecheck = fixDeliveryMode === 'commit';
|
|
60
97
|
return prs.map((pr) => {
|
|
61
98
|
const fork = isForkPR(pr);
|
|
62
99
|
if (pr.nextAction === 'fix' && fork) {
|
|
@@ -89,59 +126,166 @@ export function buildPreflightPlan(prs) {
|
|
|
89
126
|
return {
|
|
90
127
|
pr,
|
|
91
128
|
action: 'fix',
|
|
92
|
-
transition:
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
details: ['links latest review'],
|
|
129
|
+
transition: chainRecheck
|
|
130
|
+
? `${pr.reviewState} -> fix→recheck${modeTag}`
|
|
131
|
+
: `${pr.reviewState} -> fix`,
|
|
132
|
+
details: [
|
|
133
|
+
`fixer ${fixerLabel(pr)}`,
|
|
134
|
+
`delivery ${fixDeliveryMode}`,
|
|
135
|
+
...(chainRecheck ? [] : ['recheck deferred']),
|
|
136
|
+
],
|
|
137
|
+
chainRecheck,
|
|
102
138
|
};
|
|
103
139
|
}
|
|
140
|
+
// nextAction === 'recheck' — fix was applied externally; close the loop with one recheck
|
|
104
141
|
return {
|
|
105
142
|
pr,
|
|
106
|
-
action: '
|
|
107
|
-
transition:
|
|
108
|
-
details: ['
|
|
143
|
+
action: 'recheck',
|
|
144
|
+
transition: `${pr.reviewState} -> Recheck`,
|
|
145
|
+
details: ['links latest review'],
|
|
109
146
|
};
|
|
110
147
|
});
|
|
111
148
|
}
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
149
|
+
function printCapturedOutput(label, output) {
|
|
150
|
+
const lines = output.trimEnd().split('\n');
|
|
151
|
+
console.log(chalk.dim(`\n── ${label} ${'─'.repeat(Math.max(0, 48 - label.length))}`));
|
|
152
|
+
for (const line of lines)
|
|
153
|
+
console.log(` ${line}`);
|
|
154
|
+
}
|
|
155
|
+
function printNoActionablePRsWarning(fromCache) {
|
|
156
|
+
console.log(chalk.dim('No actionable PRs found.'));
|
|
157
|
+
if (fromCache) {
|
|
158
|
+
console.log(chalk.yellow(`⚠ This result came from the scan cache. Rerun with --force to refresh the queue.`));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
export async function executeKickassPlan(plan, deps, concurrency = 1, staggerMs = 0) {
|
|
162
|
+
const results = new Array(plan.length);
|
|
163
|
+
const executeItem = async (item, index, attempt = 1) => {
|
|
115
164
|
if (item.action === 'skip') {
|
|
116
165
|
console.log(chalk.yellow(`↷ skip ${formatPRSignature(item.pr)} ${item.skipReason ?? 'skipped'}`));
|
|
117
|
-
results
|
|
118
|
-
|
|
166
|
+
results[index] = { pr: item.pr, status: 'skipped', reason: item.skipReason };
|
|
167
|
+
return;
|
|
119
168
|
}
|
|
120
169
|
try {
|
|
121
170
|
const currentHeadSha = await deps.getCurrentHeadSha(item);
|
|
122
171
|
if (currentHeadSha !== item.pr.headSha) {
|
|
123
172
|
console.log(chalk.yellow(`↷ skip ${formatPRSignature(item.pr)} stale_signature`));
|
|
124
|
-
results
|
|
125
|
-
|
|
173
|
+
results[index] = { pr: item.pr, status: 'skipped', reason: 'stale_signature' };
|
|
174
|
+
return;
|
|
126
175
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
176
|
+
const attemptLabel = attempt > 1 ? ` (retry ${attempt - 1})` : '';
|
|
177
|
+
console.log(chalk.cyan(`\n→ ${item.transition} ${formatPRSignature(item.pr)}${attemptLabel}`));
|
|
178
|
+
const output = await deps.dispatchRun(item);
|
|
179
|
+
if (typeof output === 'string' && output)
|
|
180
|
+
printCapturedOutput(formatPRSignature(item.pr), output);
|
|
181
|
+
if (item.action === 'fix' && item.chainRecheck === true) {
|
|
182
|
+
const fixedHeadSha = await deps.getCurrentHeadSha(item);
|
|
183
|
+
if (fixedHeadSha !== item.pr.headSha) {
|
|
184
|
+
const recheckItem = buildPostFixRecheckItem(item, fixedHeadSha);
|
|
185
|
+
console.log(chalk.cyan(`\n→ ${recheckItem.transition} ${formatPRSignature(recheckItem.pr)}`));
|
|
186
|
+
const recheckOutput = await deps.dispatchRun(recheckItem);
|
|
187
|
+
if (typeof recheckOutput === 'string' && recheckOutput)
|
|
188
|
+
printCapturedOutput(formatPRSignature(recheckItem.pr), recheckOutput);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
console.log(chalk.dim(` head SHA unchanged after fix — recheck deferred`));
|
|
192
|
+
}
|
|
130
193
|
}
|
|
131
|
-
|
|
132
|
-
await deps.dispatchRun(item);
|
|
133
|
-
}
|
|
134
|
-
results.push({ pr: item.pr, status: 'executed' });
|
|
194
|
+
results[index] = { pr: item.pr, status: 'executed' };
|
|
135
195
|
}
|
|
136
196
|
catch (err) {
|
|
137
|
-
logError({ event: 'kickass_pr_failed', owner: item.pr.owner, repo: item.pr.repo, pr: item.pr.number }, err);
|
|
197
|
+
logError({ event: 'kickass_pr_failed', owner: item.pr.owner, repo: item.pr.repo, pr: item.pr.number, ...(attempt > 1 && { attempt }) }, err);
|
|
138
198
|
console.error(chalk.red(`✗ failed ${formatPRSignature(item.pr)}`));
|
|
139
|
-
|
|
199
|
+
// Classify execa errors using structured fields, not the raw message.
|
|
200
|
+
// The raw message includes the full CLI invocation (e.g. "Command failed with exit
|
|
201
|
+
// code 1: node crosscheck run --timeout 300s --no-timeout"), so a text match against
|
|
202
|
+
// `message` would misclassify ordinary subprocess failures as 'timeout' whenever the
|
|
203
|
+
// command contains a --timeout flag.
|
|
204
|
+
const maybeExeca = err;
|
|
205
|
+
let msgForClassify;
|
|
206
|
+
if (maybeExeca.timedOut === true) {
|
|
207
|
+
// execa's structured timeout flag — reliable; bypass message matching entirely.
|
|
208
|
+
msgForClassify = 'timed out';
|
|
209
|
+
}
|
|
210
|
+
else if (typeof maybeExeca.exitCode === 'number') {
|
|
211
|
+
// Subprocess failure: prefer stderr (actual error output) over the message which
|
|
212
|
+
// includes the full command string. Strip the command suffix when stderr is absent.
|
|
213
|
+
const stderr = typeof maybeExeca.stderr === 'string' ? maybeExeca.stderr.trim() : '';
|
|
214
|
+
msgForClassify = stderr || (err instanceof Error ? err.message.replace(/:\s*\S.*$/, '') : String(err));
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
msgForClassify = err instanceof Error ? err.message : String(err);
|
|
218
|
+
}
|
|
219
|
+
const category = classifyError(msgForClassify);
|
|
220
|
+
results[index] = { pr: item.pr, status: 'failed', reason: category };
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
if (concurrency <= 1) {
|
|
224
|
+
for (let i = 0; i < plan.length; i++)
|
|
225
|
+
await executeItem(plan[i], i);
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
// Worker-pool: up to `concurrency` PRs run in parallel.
|
|
229
|
+
// staggerMs > 0 delays each worker's start by (workerIdx * staggerMs) to spread
|
|
230
|
+
// concurrent subprocess startup API calls over time rather than hitting GitHub simultaneously.
|
|
231
|
+
let ptr = 0;
|
|
232
|
+
const makeWorker = (workerIdx) => async () => {
|
|
233
|
+
if (staggerMs > 0 && workerIdx > 0) {
|
|
234
|
+
await new Promise(resolve => setTimeout(resolve, workerIdx * staggerMs));
|
|
235
|
+
}
|
|
236
|
+
while (ptr < plan.length) {
|
|
237
|
+
const i = ptr++;
|
|
238
|
+
await executeItem(plan[i], i);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, plan.length) }, (_, idx) => makeWorker(idx)()));
|
|
242
|
+
}
|
|
243
|
+
// Retry transient failures up to 4 times with escalating delays.
|
|
244
|
+
// Auth and permission failures are operator issues that won't self-heal.
|
|
245
|
+
const RETRYABLE = new Set(['network', 'timeout']);
|
|
246
|
+
const RETRY_DELAYS_MS = [60_000, 120_000, 300_000, 600_000];
|
|
247
|
+
for (let attempt = 2; attempt <= RETRY_DELAYS_MS.length + 1; attempt++) {
|
|
248
|
+
const delayMs = RETRY_DELAYS_MS[attempt - 2];
|
|
249
|
+
const retryItems = results
|
|
250
|
+
.map((r, i) => ({ r, i }))
|
|
251
|
+
.filter(({ r }) => r.status === 'failed' && RETRYABLE.has(r.reason));
|
|
252
|
+
if (retryItems.length === 0)
|
|
253
|
+
break;
|
|
254
|
+
const delaySec = delayMs / 1000;
|
|
255
|
+
const delayLabel = delaySec >= 60 ? `${delaySec / 60}m` : `${delaySec}s`;
|
|
256
|
+
console.log(chalk.dim(`\n ${retryItems.length} transient failure(s) — retry ${attempt - 1}/${RETRY_DELAYS_MS.length} in ${delayLabel}...`));
|
|
257
|
+
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
258
|
+
for (const { i } of retryItems) {
|
|
259
|
+
const priorResult = results[i];
|
|
260
|
+
await executeItem(plan[i], i, attempt);
|
|
261
|
+
// Stale-signature means the fix already committed in a prior attempt but the
|
|
262
|
+
// chained recheck failed transiently. Instead of reporting that failure as
|
|
263
|
+
// final, fetch the current head and run a bare recheck to actually retry it.
|
|
264
|
+
if (results[i].status === 'skipped' && results[i].reason === 'stale_signature'
|
|
265
|
+
&& plan[i].action === 'fix' && plan[i].chainRecheck === true) {
|
|
266
|
+
try {
|
|
267
|
+
const currentHead = await deps.getCurrentHeadSha(plan[i]);
|
|
268
|
+
const recheckItem = buildPostFixRecheckItem(plan[i], currentHead);
|
|
269
|
+
await executeItem(recheckItem, i, attempt);
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
// If we cannot fetch the head (network failure), preserve the original failure.
|
|
273
|
+
results[i] = priorResult;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
140
276
|
}
|
|
141
277
|
}
|
|
142
278
|
return results;
|
|
143
279
|
}
|
|
144
|
-
|
|
280
|
+
function buildPostFixRecheckItem(item, headSha) {
|
|
281
|
+
return {
|
|
282
|
+
pr: { ...item.pr, headSha, nextAction: 'recheck', reviewState: 'NEEDS_RECHECK' },
|
|
283
|
+
action: 'recheck',
|
|
284
|
+
transition: 'fix -> Recheck',
|
|
285
|
+
details: ['links latest review', `head ${headSha.slice(0, 7)}`],
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
export function printPreflight(plan, mergeReady = []) {
|
|
145
289
|
console.log('\nPreflight');
|
|
146
290
|
const grouped = groupPreflight(plan);
|
|
147
291
|
for (const [transition, items] of grouped) {
|
|
@@ -151,6 +295,14 @@ export function printPreflight(plan) {
|
|
|
151
295
|
console.log(` ${formatPRSignature(item.pr)} ${item.details.join(' ')}${explanation}`);
|
|
152
296
|
}
|
|
153
297
|
}
|
|
298
|
+
if (mergeReady.length > 0)
|
|
299
|
+
printMergeReady(mergeReady);
|
|
300
|
+
}
|
|
301
|
+
export function printMergeReady(prs) {
|
|
302
|
+
console.log(chalk.dim('\nneeds merge (manual — not selected)'));
|
|
303
|
+
for (const pr of prs) {
|
|
304
|
+
console.log(chalk.dim(` ${formatPRSignature(pr)} APPROVE`));
|
|
305
|
+
}
|
|
154
306
|
}
|
|
155
307
|
export function summarizeExecutionResults(results) {
|
|
156
308
|
const executed = results.filter(result => result.status === 'executed').length;
|
|
@@ -161,18 +313,36 @@ export function summarizeExecutionResults(results) {
|
|
|
161
313
|
export function printExecutionSummary(results) {
|
|
162
314
|
console.log(chalk.dim(`\n${summarizeExecutionResults(results)}`));
|
|
163
315
|
}
|
|
164
|
-
export function buildKickassRunArgs(itemOrPR) {
|
|
316
|
+
export function buildKickassRunArgs(itemOrPR, roundMode, timeout) {
|
|
165
317
|
const item = 'action' in itemOrPR ? itemOrPR : buildPreflightPlan([itemOrPR])[0];
|
|
166
|
-
if (item.action === '
|
|
318
|
+
if (item.action === 'skip')
|
|
167
319
|
return [];
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
320
|
+
const args = ['run', item.pr.url];
|
|
321
|
+
// No --steps for normal review/recheck/fix actions: run.ts calls
|
|
322
|
+
// identifyNextWorkflowStep against live PR history to determine the correct
|
|
323
|
+
// next step. Exception: when kickass demoted a fix action to review because the
|
|
324
|
+
// latest annotation covers an older SHA (no_usable_review_comment), we must
|
|
325
|
+
// force --steps review so run.ts doesn't re-detect from live history and choose
|
|
326
|
+
// the stale review's fix step, applying fixes to the unreviewed new diff.
|
|
327
|
+
if (item.action === 'review' && item.explanation === 'no_usable_review_comment') {
|
|
328
|
+
args.push('--steps', 'review');
|
|
329
|
+
}
|
|
330
|
+
args.push('--expected-head-sha', item.pr.headSha);
|
|
331
|
+
if (item.action !== 'fix') {
|
|
332
|
+
if (roundMode === 'crazy')
|
|
333
|
+
args.push('--crazy');
|
|
334
|
+
else if (roundMode === 'halfcrazy')
|
|
335
|
+
args.push('--half-crazy');
|
|
336
|
+
}
|
|
337
|
+
else if (roundMode) {
|
|
338
|
+
// fix legs don't loop, but still need the no-timeout constraint lifted
|
|
339
|
+
args.push('--no-timeout');
|
|
340
|
+
}
|
|
341
|
+
// forward user-specified --timeout for runs that aren't already in a round mode
|
|
342
|
+
if (timeout && !roundMode)
|
|
343
|
+
args.push('--timeout', timeout);
|
|
344
|
+
args.push('--trigger', 'kickass');
|
|
345
|
+
return args;
|
|
176
346
|
}
|
|
177
347
|
export function resolveCliInvocation(options = {}) {
|
|
178
348
|
const exists = options.exists ?? existsSync;
|
|
@@ -191,16 +361,20 @@ export function resolveCliInvocation(options = {}) {
|
|
|
191
361
|
return invocationForEntry(sourceCli, execPath, localTsx, exists);
|
|
192
362
|
throw new Error('Cannot resolve crosscheck CLI entrypoint. Run npm run build before kickass, or run from a source checkout with dev dependencies installed.');
|
|
193
363
|
}
|
|
194
|
-
function defaultKickassDeps() {
|
|
364
|
+
function defaultKickassDeps(opts = {}) {
|
|
195
365
|
let cli;
|
|
196
366
|
const getCli = () => {
|
|
197
367
|
cli ??= resolveCliInvocation();
|
|
198
368
|
return cli;
|
|
199
369
|
};
|
|
370
|
+
// opts.concurrent = 0 means "one per PR" (fully parallel); undefined means sequential.
|
|
371
|
+
// Any explicit --concurrent value uses buffered stdio; sequential streams inline.
|
|
372
|
+
const isParallel = opts.concurrent !== undefined;
|
|
200
373
|
return {
|
|
201
374
|
loadScanResult,
|
|
202
375
|
pickPRs,
|
|
203
376
|
confirm: confirmMutation,
|
|
377
|
+
getFixDeliveryMode: () => loadConfig().post_review.auto_fix.delivery.mode,
|
|
204
378
|
getCurrentHeadSha: async (item) => {
|
|
205
379
|
const token = getGithubToken();
|
|
206
380
|
const octokit = createGithubClient(token);
|
|
@@ -213,15 +387,12 @@ function defaultKickassDeps() {
|
|
|
213
387
|
},
|
|
214
388
|
dispatchRun: async (item) => {
|
|
215
389
|
const invocation = getCli();
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
await
|
|
222
|
-
method: 'squash',
|
|
223
|
-
expectedHeadSha: item.pr.headSha,
|
|
224
|
-
});
|
|
390
|
+
const args = [...invocation.args, ...buildKickassRunArgs(item, opts.roundMode, opts.timeout)];
|
|
391
|
+
if (isParallel) {
|
|
392
|
+
const result = await execa(invocation.command, args, { stdio: 'pipe', all: true });
|
|
393
|
+
return result.all ?? '';
|
|
394
|
+
}
|
|
395
|
+
await execa(invocation.command, args, { stdio: 'inherit' });
|
|
225
396
|
},
|
|
226
397
|
};
|
|
227
398
|
}
|
|
@@ -268,10 +439,10 @@ function checksLabel(pr) {
|
|
|
268
439
|
return 'green';
|
|
269
440
|
return pr.merge.mergeStateStatus ?? 'unknown';
|
|
270
441
|
}
|
|
271
|
-
function
|
|
272
|
-
if (action === 'review')
|
|
442
|
+
function stepsForItem(item) {
|
|
443
|
+
if (item.action === 'review')
|
|
273
444
|
return 'review';
|
|
274
|
-
if (action === 'fix')
|
|
445
|
+
if (item.action === 'fix')
|
|
275
446
|
return 'fix';
|
|
276
447
|
return 'recheck';
|
|
277
448
|
}
|