@humanbased/crosscheck 0.14.0 → 0.15.0-beta.145
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/ISSUE.md +1 -1
- package/LICENSE +1 -1
- package/README.md +191 -8
- package/README.zh.md +1 -1
- package/crosscheck.config.example.yml +4 -2
- package/dist/__tests__/backtrace.test.js +1 -1
- package/dist/__tests__/backtrace.test.js.map +1 -1
- package/dist/__tests__/diagnose.test.js +36 -0
- package/dist/__tests__/diagnose.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__/error-classification.test.d.ts +2 -0
- package/dist/__tests__/error-classification.test.d.ts.map +1 -0
- package/dist/__tests__/error-classification.test.js +36 -0
- package/dist/__tests__/error-classification.test.js.map +1 -0
- package/dist/__tests__/fix.test.js +48 -1
- package/dist/__tests__/fix.test.js.map +1 -1
- package/dist/__tests__/issue.test.js +2 -2
- package/dist/__tests__/issue.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 +18 -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__/scopes.test.js +11 -11
- package/dist/__tests__/scopes.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/diagnose.d.ts +1 -1
- package/dist/commands/diagnose.d.ts.map +1 -1
- package/dist/commands/diagnose.js +30 -1
- package/dist/commands/diagnose.js.map +1 -1
- package/dist/commands/kickass.d.ts +28 -10
- package/dist/commands/kickass.d.ts.map +1 -1
- package/dist/commands/kickass.js +295 -68
- 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/status.d.ts.map +1 -1
- package/dist/commands/status.js +10 -2
- package/dist/commands/status.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/board.d.ts.map +1 -1
- package/dist/lib/board.js +7 -5
- package/dist/lib/board.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 +4 -1
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/logger.js +41 -5
- 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/review-models.d.ts +2 -2
- package/dist/lib/review-models.d.ts.map +1 -1
- package/dist/lib/review-models.js +6 -1
- package/dist/lib/review-models.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 +338 -55
- package/dist/lib/runner.js.map +1 -1
- package/dist/lib/scopes.js +1 -1
- package/dist/lib/scopes.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 +15 -10
- 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/docs/fixture-pr.md +112 -0
- package/docs/proof-demo.md +102 -0
- package/get-started.md +128 -31
- package/get-started.zh.md +7 -1
- package/package.json +4 -2
|
@@ -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,15 @@ export interface KickassExecutionResult {
|
|
|
22
30
|
}
|
|
23
31
|
export interface ExecuteKickassDeps {
|
|
24
32
|
getCurrentHeadSha: (item: PreflightItem) => Promise<string>;
|
|
25
|
-
dispatchRun: (item: PreflightItem) => Promise<void>;
|
|
26
|
-
|
|
33
|
+
dispatchRun: (item: PreflightItem) => Promise<string | void>;
|
|
34
|
+
/** Route status messages through a custom sink (e.g. PRBoard scrollback). Defaults to console.log. */
|
|
35
|
+
log?: (msg: string) => void;
|
|
36
|
+
/** Called just before dispatchRun — use to add a board slot. */
|
|
37
|
+
onDispatchStart?: (item: PreflightItem, key: string, startedAt: number) => void;
|
|
38
|
+
/** Called after a successful dispatchRun — use to complete a board slot. */
|
|
39
|
+
onDispatchEnd?: (item: PreflightItem, key: string, startedAt: number) => void;
|
|
40
|
+
/** Called when dispatchRun throws — use to mark a board slot as failed. */
|
|
41
|
+
onDispatchFail?: (item: PreflightItem, key: string, error: unknown) => void;
|
|
27
42
|
}
|
|
28
43
|
export interface KickassDeps {
|
|
29
44
|
loadScanResult: (options: {
|
|
@@ -32,18 +47,21 @@ export interface KickassDeps {
|
|
|
32
47
|
}) => Promise<ScanResult>;
|
|
33
48
|
pickPRs: (prs: PRStatus[]) => Promise<PRStatus[]>;
|
|
34
49
|
confirm: (message: string) => Promise<boolean>;
|
|
50
|
+
getFixDeliveryMode?: () => FixDeliveryMode | Promise<FixDeliveryMode>;
|
|
35
51
|
getCurrentHeadSha: (item: PreflightItem) => Promise<string>;
|
|
36
|
-
|
|
37
|
-
|
|
52
|
+
onBeforeExecute?: () => void;
|
|
53
|
+
onAfterExecute?: () => void;
|
|
54
|
+
dispatchRun: (item: PreflightItem) => Promise<string | void>;
|
|
38
55
|
}
|
|
39
56
|
export declare function runKickass(opts?: KickassOpts): Promise<void>;
|
|
40
57
|
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;
|
|
58
|
+
export declare function buildPreflightPlan(prs: PRStatus[], roundMode?: 'crazy' | 'halfcrazy', fixDeliveryMode?: FixDeliveryMode): PreflightItem[];
|
|
59
|
+
export declare function executeKickassPlan(plan: PreflightItem[], deps: ExecuteKickassDeps, concurrency?: number, staggerMs?: number): Promise<KickassExecutionResult[]>;
|
|
60
|
+
export declare function printPreflight(plan: PreflightItem[], mergeReady?: PRStatus[]): void;
|
|
61
|
+
export declare function printMergeReady(prs: PRStatus[]): void;
|
|
44
62
|
export declare function summarizeExecutionResults(results: KickassExecutionResult[]): string;
|
|
45
63
|
export declare function printExecutionSummary(results: KickassExecutionResult[]): void;
|
|
46
|
-
export declare function buildKickassRunArgs(itemOrPR: PreflightItem | PRStatus): string[];
|
|
64
|
+
export declare function buildKickassRunArgs(itemOrPR: PreflightItem | PRStatus, roundMode?: 'crazy' | 'halfcrazy', timeout?: string): string[];
|
|
47
65
|
export interface CliInvocation {
|
|
48
66
|
command: string;
|
|
49
67
|
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":"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"}
|
package/dist/commands/kickass.js
CHANGED
|
@@ -5,14 +5,19 @@ 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';
|
|
13
|
+
import { PRBoard } from '../lib/board.js';
|
|
14
|
+
import { loadWorkflow } from '../lib/workflow.js';
|
|
14
15
|
export async function runKickass(opts = {}) {
|
|
15
|
-
|
|
16
|
+
const config = loadConfig();
|
|
17
|
+
const workflow = loadWorkflow(process.cwd());
|
|
18
|
+
const board = new PRBoard();
|
|
19
|
+
board.setConfig(config, workflow);
|
|
20
|
+
await runKickassWithDeps(opts, defaultKickassDeps(opts, board));
|
|
16
21
|
}
|
|
17
22
|
export async function runKickassWithDeps(opts = {}, deps) {
|
|
18
23
|
let staleAfterMs;
|
|
@@ -23,11 +28,38 @@ export async function runKickassWithDeps(opts = {}, deps) {
|
|
|
23
28
|
console.error(chalk.red(`✗ ${err instanceof Error ? err.message : String(err)}`));
|
|
24
29
|
process.exit(1);
|
|
25
30
|
}
|
|
31
|
+
if (opts.timeout) {
|
|
32
|
+
try {
|
|
33
|
+
parseDuration(opts.timeout);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
console.error(chalk.red(`✗ Invalid --timeout value "${opts.timeout}". Use a duration like 300s or 10m.`));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (opts.concurrent !== undefined && (opts.concurrent < 0 || !Number.isInteger(opts.concurrent))) {
|
|
41
|
+
console.error(chalk.red('✗ --concurrent must be a non-negative integer (0 = one agent per selected PR)'));
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
26
44
|
try {
|
|
27
45
|
const scan = await deps.loadScanResult({ force: opts.force, staleAfterMs });
|
|
28
|
-
|
|
46
|
+
// Actionable = nextAction is set and is not merge (merge not dispatched in v1).
|
|
47
|
+
// Stale PRs shown first; not-stale actionable PRs follow.
|
|
48
|
+
const queue = scan.prs
|
|
49
|
+
.filter(pr => pr.nextAction !== null && pr.nextAction !== 'merge')
|
|
50
|
+
.sort((a, b) => {
|
|
51
|
+
if (a.freshness !== b.freshness)
|
|
52
|
+
return a.freshness === 'stale' ? -1 : 1;
|
|
53
|
+
return 0;
|
|
54
|
+
});
|
|
55
|
+
const mergeReady = scan.prs.filter(pr => pr.nextAction === 'merge');
|
|
56
|
+
if (queue.length === 0 && mergeReady.length === 0) {
|
|
57
|
+
printNoActionablePRsWarning(scan.cached);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
29
60
|
if (queue.length === 0) {
|
|
30
|
-
|
|
61
|
+
printMergeReady(mergeReady);
|
|
62
|
+
console.log(chalk.dim('\nNo PRs need review, fix, or recheck — all actionable work is merge-ready (manual).'));
|
|
31
63
|
return;
|
|
32
64
|
}
|
|
33
65
|
const selected = await deps.pickPRs(queue);
|
|
@@ -35,8 +67,9 @@ export async function runKickassWithDeps(opts = {}, deps) {
|
|
|
35
67
|
console.log(chalk.dim('No PRs selected.'));
|
|
36
68
|
return;
|
|
37
69
|
}
|
|
38
|
-
const
|
|
39
|
-
|
|
70
|
+
const fixDeliveryMode = deps.getFixDeliveryMode ? await deps.getFixDeliveryMode() : 'pull_request';
|
|
71
|
+
const plan = buildPreflightPlan(selected, opts.roundMode, fixDeliveryMode);
|
|
72
|
+
printPreflight(plan, mergeReady);
|
|
40
73
|
if (opts.dryRun) {
|
|
41
74
|
console.log(chalk.dim('\ndry-run: no mutations executed'));
|
|
42
75
|
return;
|
|
@@ -46,7 +79,17 @@ export async function runKickassWithDeps(opts = {}, deps) {
|
|
|
46
79
|
console.log(chalk.dim('Canceled.'));
|
|
47
80
|
return;
|
|
48
81
|
}
|
|
49
|
-
|
|
82
|
+
// 0 = one agent per selected PR; undefined/1 = sequential
|
|
83
|
+
const resolvedConcurrency = opts.concurrent === 0
|
|
84
|
+
? selected.length
|
|
85
|
+
: Math.max(1, opts.concurrent ?? 1);
|
|
86
|
+
const resolvedStagger = resolvedConcurrency > 1 ? (opts.staggerMs ?? 2_000) : 0;
|
|
87
|
+
if (resolvedConcurrency > 1) {
|
|
88
|
+
console.log(chalk.dim(`\n running ${resolvedConcurrency} agents in parallel (${resolvedStagger}ms stagger)`));
|
|
89
|
+
}
|
|
90
|
+
deps.onBeforeExecute?.();
|
|
91
|
+
const results = await executeKickassPlan(plan, deps, resolvedConcurrency, resolvedStagger);
|
|
92
|
+
deps.onAfterExecute?.();
|
|
50
93
|
printExecutionSummary(results);
|
|
51
94
|
if (results.some(result => result.status === 'failed')) {
|
|
52
95
|
process.exitCode = 2;
|
|
@@ -56,7 +99,9 @@ export async function runKickassWithDeps(opts = {}, deps) {
|
|
|
56
99
|
handleScanError('kickass', err);
|
|
57
100
|
}
|
|
58
101
|
}
|
|
59
|
-
export function buildPreflightPlan(prs) {
|
|
102
|
+
export function buildPreflightPlan(prs, roundMode, fixDeliveryMode = 'pull_request') {
|
|
103
|
+
const modeTag = roundMode ? ` [${roundMode}]` : '';
|
|
104
|
+
const chainRecheck = fixDeliveryMode === 'commit';
|
|
60
105
|
return prs.map((pr) => {
|
|
61
106
|
const fork = isForkPR(pr);
|
|
62
107
|
if (pr.nextAction === 'fix' && fork) {
|
|
@@ -89,59 +134,177 @@ export function buildPreflightPlan(prs) {
|
|
|
89
134
|
return {
|
|
90
135
|
pr,
|
|
91
136
|
action: 'fix',
|
|
92
|
-
transition:
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
details: ['links latest review'],
|
|
137
|
+
transition: chainRecheck
|
|
138
|
+
? `${pr.reviewState} -> fix→recheck${modeTag}`
|
|
139
|
+
: `${pr.reviewState} -> fix`,
|
|
140
|
+
details: [
|
|
141
|
+
`fixer ${fixerLabel(pr)}`,
|
|
142
|
+
`delivery ${fixDeliveryMode}`,
|
|
143
|
+
...(chainRecheck ? [] : ['recheck deferred']),
|
|
144
|
+
],
|
|
145
|
+
chainRecheck,
|
|
102
146
|
};
|
|
103
147
|
}
|
|
148
|
+
// nextAction === 'recheck' — fix was applied externally; close the loop with one recheck
|
|
104
149
|
return {
|
|
105
150
|
pr,
|
|
106
|
-
action: '
|
|
107
|
-
transition:
|
|
108
|
-
details: ['
|
|
151
|
+
action: 'recheck',
|
|
152
|
+
transition: `${pr.reviewState} -> Recheck`,
|
|
153
|
+
details: ['links latest review'],
|
|
109
154
|
};
|
|
110
155
|
});
|
|
111
156
|
}
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
157
|
+
function printCapturedOutput(label, output, log = console.log) {
|
|
158
|
+
const lines = output.trimEnd().split('\n');
|
|
159
|
+
log(chalk.dim(`\n── ${label} ${'─'.repeat(Math.max(0, 48 - label.length))}`));
|
|
160
|
+
for (const line of lines)
|
|
161
|
+
log(` ${line}`);
|
|
162
|
+
}
|
|
163
|
+
function printNoActionablePRsWarning(fromCache) {
|
|
164
|
+
console.log(chalk.dim('No actionable PRs found.'));
|
|
165
|
+
if (fromCache) {
|
|
166
|
+
console.log(chalk.yellow(`⚠ This result came from the scan cache. Rerun with --force to refresh the queue.`));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
export async function executeKickassPlan(plan, deps, concurrency = 1, staggerMs = 0) {
|
|
170
|
+
const results = new Array(plan.length);
|
|
171
|
+
const log = deps.log ?? console.log;
|
|
172
|
+
const executeItem = async (item, index, attempt = 1) => {
|
|
115
173
|
if (item.action === 'skip') {
|
|
116
|
-
|
|
117
|
-
results
|
|
118
|
-
|
|
174
|
+
log(chalk.yellow(`↷ skip ${formatPRSignature(item.pr)} ${item.skipReason ?? 'skipped'}`));
|
|
175
|
+
results[index] = { pr: item.pr, status: 'skipped', reason: item.skipReason };
|
|
176
|
+
return;
|
|
119
177
|
}
|
|
120
178
|
try {
|
|
121
179
|
const currentHeadSha = await deps.getCurrentHeadSha(item);
|
|
122
180
|
if (currentHeadSha !== item.pr.headSha) {
|
|
123
|
-
|
|
124
|
-
results
|
|
125
|
-
|
|
181
|
+
log(chalk.yellow(`↷ skip ${formatPRSignature(item.pr)} stale_signature`));
|
|
182
|
+
results[index] = { pr: item.pr, status: 'skipped', reason: 'stale_signature' };
|
|
183
|
+
return;
|
|
126
184
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
185
|
+
const key = `${item.pr.owner}/${item.pr.repo}#${item.pr.number}@${item.pr.headSha}`;
|
|
186
|
+
const startedAt = Date.now();
|
|
187
|
+
const attemptLabel = attempt > 1 ? ` (retry ${attempt - 1})` : '';
|
|
188
|
+
log(chalk.cyan(`\n→ ${item.transition} ${formatPRSignature(item.pr)}${attemptLabel}`));
|
|
189
|
+
deps.onDispatchStart?.(item, key, startedAt);
|
|
190
|
+
const output = await deps.dispatchRun(item);
|
|
191
|
+
deps.onDispatchEnd?.(item, key, startedAt);
|
|
192
|
+
if (typeof output === 'string' && output)
|
|
193
|
+
printCapturedOutput(formatPRSignature(item.pr), output, log);
|
|
194
|
+
if (item.action === 'fix' && item.chainRecheck === true) {
|
|
195
|
+
const fixedHeadSha = await deps.getCurrentHeadSha(item);
|
|
196
|
+
if (fixedHeadSha !== item.pr.headSha) {
|
|
197
|
+
const recheckItem = buildPostFixRecheckItem(item, fixedHeadSha);
|
|
198
|
+
const recheckKey = `${recheckItem.pr.owner}/${recheckItem.pr.repo}#${recheckItem.pr.number}@${recheckItem.pr.headSha}`;
|
|
199
|
+
const recheckStart = Date.now();
|
|
200
|
+
log(chalk.cyan(`\n→ ${recheckItem.transition} ${formatPRSignature(recheckItem.pr)}`));
|
|
201
|
+
deps.onDispatchStart?.(recheckItem, recheckKey, recheckStart);
|
|
202
|
+
const recheckOutput = await deps.dispatchRun(recheckItem);
|
|
203
|
+
deps.onDispatchEnd?.(recheckItem, recheckKey, recheckStart);
|
|
204
|
+
if (typeof recheckOutput === 'string' && recheckOutput)
|
|
205
|
+
printCapturedOutput(formatPRSignature(recheckItem.pr), recheckOutput, log);
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
log(chalk.dim(` head SHA unchanged after fix — recheck deferred`));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
results[index] = { pr: item.pr, status: 'executed' };
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
logError({ event: 'kickass_pr_failed', owner: item.pr.owner, repo: item.pr.repo, pr: item.pr.number, ...(attempt > 1 && { attempt }) }, err);
|
|
215
|
+
log(chalk.red(`✗ failed ${formatPRSignature(item.pr)}`));
|
|
216
|
+
const failKey = `${item.pr.owner}/${item.pr.repo}#${item.pr.number}@${item.pr.headSha}`;
|
|
217
|
+
deps.onDispatchFail?.(item, failKey, err);
|
|
218
|
+
// Classify execa errors using structured fields, not the raw message.
|
|
219
|
+
// The raw message includes the full CLI invocation (e.g. "Command failed with exit
|
|
220
|
+
// code 1: node crosscheck run --timeout 300s --no-timeout"), so a text match against
|
|
221
|
+
// `message` would misclassify ordinary subprocess failures as 'timeout' whenever the
|
|
222
|
+
// command contains a --timeout flag.
|
|
223
|
+
const maybeExeca = err;
|
|
224
|
+
let msgForClassify;
|
|
225
|
+
if (maybeExeca.timedOut === true) {
|
|
226
|
+
// execa's structured timeout flag — reliable; bypass message matching entirely.
|
|
227
|
+
msgForClassify = 'timed out';
|
|
228
|
+
}
|
|
229
|
+
else if (typeof maybeExeca.exitCode === 'number') {
|
|
230
|
+
// Subprocess failure: prefer stderr (actual error output) over the message which
|
|
231
|
+
// includes the full command string. Strip the command suffix when stderr is absent.
|
|
232
|
+
const stderr = typeof maybeExeca.stderr === 'string' ? maybeExeca.stderr.trim() : '';
|
|
233
|
+
msgForClassify = stderr || (err instanceof Error ? err.message.replace(/:\s*\S.*$/, '') : String(err));
|
|
130
234
|
}
|
|
131
235
|
else {
|
|
132
|
-
|
|
236
|
+
msgForClassify = err instanceof Error ? err.message : String(err);
|
|
133
237
|
}
|
|
134
|
-
|
|
238
|
+
const category = classifyError(msgForClassify);
|
|
239
|
+
results[index] = { pr: item.pr, status: 'failed', reason: category };
|
|
135
240
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
241
|
+
};
|
|
242
|
+
if (concurrency <= 1) {
|
|
243
|
+
for (let i = 0; i < plan.length; i++)
|
|
244
|
+
await executeItem(plan[i], i);
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
// Worker-pool: up to `concurrency` PRs run in parallel.
|
|
248
|
+
// staggerMs > 0 delays each worker's start by (workerIdx * staggerMs) to spread
|
|
249
|
+
// concurrent subprocess startup API calls over time rather than hitting GitHub simultaneously.
|
|
250
|
+
let ptr = 0;
|
|
251
|
+
const makeWorker = (workerIdx) => async () => {
|
|
252
|
+
if (staggerMs > 0 && workerIdx > 0) {
|
|
253
|
+
await new Promise(resolve => setTimeout(resolve, workerIdx * staggerMs));
|
|
254
|
+
}
|
|
255
|
+
while (ptr < plan.length) {
|
|
256
|
+
const i = ptr++;
|
|
257
|
+
await executeItem(plan[i], i);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, plan.length) }, (_, idx) => makeWorker(idx)()));
|
|
261
|
+
}
|
|
262
|
+
// Retry transient failures up to 4 times with escalating delays.
|
|
263
|
+
// Auth and permission failures are operator issues that won't self-heal.
|
|
264
|
+
const RETRYABLE = new Set(['network', 'timeout']);
|
|
265
|
+
const RETRY_DELAYS_MS = [60_000, 120_000, 300_000, 600_000];
|
|
266
|
+
for (let attempt = 2; attempt <= RETRY_DELAYS_MS.length + 1; attempt++) {
|
|
267
|
+
const delayMs = RETRY_DELAYS_MS[attempt - 2];
|
|
268
|
+
const retryItems = results
|
|
269
|
+
.map((r, i) => ({ r, i }))
|
|
270
|
+
.filter(({ r }) => r.status === 'failed' && RETRYABLE.has(r.reason));
|
|
271
|
+
if (retryItems.length === 0)
|
|
272
|
+
break;
|
|
273
|
+
const delaySec = delayMs / 1000;
|
|
274
|
+
const delayLabel = delaySec >= 60 ? `${delaySec / 60}m` : `${delaySec}s`;
|
|
275
|
+
log(chalk.dim(`\n ${retryItems.length} transient failure(s) — retry ${attempt - 1}/${RETRY_DELAYS_MS.length} in ${delayLabel}...`));
|
|
276
|
+
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
277
|
+
for (const { i } of retryItems) {
|
|
278
|
+
const priorResult = results[i];
|
|
279
|
+
await executeItem(plan[i], i, attempt);
|
|
280
|
+
// Stale-signature means the fix already committed in a prior attempt but the
|
|
281
|
+
// chained recheck failed transiently. Instead of reporting that failure as
|
|
282
|
+
// final, fetch the current head and run a bare recheck to actually retry it.
|
|
283
|
+
if (results[i].status === 'skipped' && results[i].reason === 'stale_signature'
|
|
284
|
+
&& plan[i].action === 'fix' && plan[i].chainRecheck === true) {
|
|
285
|
+
try {
|
|
286
|
+
const currentHead = await deps.getCurrentHeadSha(plan[i]);
|
|
287
|
+
const recheckItem = buildPostFixRecheckItem(plan[i], currentHead);
|
|
288
|
+
await executeItem(recheckItem, i, attempt);
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
// If we cannot fetch the head (network failure), preserve the original failure.
|
|
292
|
+
results[i] = priorResult;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
140
295
|
}
|
|
141
296
|
}
|
|
142
297
|
return results;
|
|
143
298
|
}
|
|
144
|
-
|
|
299
|
+
function buildPostFixRecheckItem(item, headSha) {
|
|
300
|
+
return {
|
|
301
|
+
pr: { ...item.pr, headSha, nextAction: 'recheck', reviewState: 'NEEDS_RECHECK' },
|
|
302
|
+
action: 'recheck',
|
|
303
|
+
transition: 'fix -> Recheck',
|
|
304
|
+
details: ['links latest review', `head ${headSha.slice(0, 7)}`],
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
export function printPreflight(plan, mergeReady = []) {
|
|
145
308
|
console.log('\nPreflight');
|
|
146
309
|
const grouped = groupPreflight(plan);
|
|
147
310
|
for (const [transition, items] of grouped) {
|
|
@@ -151,6 +314,14 @@ export function printPreflight(plan) {
|
|
|
151
314
|
console.log(` ${formatPRSignature(item.pr)} ${item.details.join(' ')}${explanation}`);
|
|
152
315
|
}
|
|
153
316
|
}
|
|
317
|
+
if (mergeReady.length > 0)
|
|
318
|
+
printMergeReady(mergeReady);
|
|
319
|
+
}
|
|
320
|
+
export function printMergeReady(prs) {
|
|
321
|
+
console.log(chalk.dim('\nneeds merge (manual — not selected)'));
|
|
322
|
+
for (const pr of prs) {
|
|
323
|
+
console.log(chalk.dim(` ${formatPRSignature(pr)} APPROVE`));
|
|
324
|
+
}
|
|
154
325
|
}
|
|
155
326
|
export function summarizeExecutionResults(results) {
|
|
156
327
|
const executed = results.filter(result => result.status === 'executed').length;
|
|
@@ -161,18 +332,36 @@ export function summarizeExecutionResults(results) {
|
|
|
161
332
|
export function printExecutionSummary(results) {
|
|
162
333
|
console.log(chalk.dim(`\n${summarizeExecutionResults(results)}`));
|
|
163
334
|
}
|
|
164
|
-
export function buildKickassRunArgs(itemOrPR) {
|
|
335
|
+
export function buildKickassRunArgs(itemOrPR, roundMode, timeout) {
|
|
165
336
|
const item = 'action' in itemOrPR ? itemOrPR : buildPreflightPlan([itemOrPR])[0];
|
|
166
|
-
if (item.action === '
|
|
337
|
+
if (item.action === 'skip')
|
|
167
338
|
return [];
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
339
|
+
const args = ['run', item.pr.url];
|
|
340
|
+
// No --steps for normal review/recheck/fix actions: run.ts calls
|
|
341
|
+
// identifyNextWorkflowStep against live PR history to determine the correct
|
|
342
|
+
// next step. Exception: when kickass demoted a fix action to review because the
|
|
343
|
+
// latest annotation covers an older SHA (no_usable_review_comment), we must
|
|
344
|
+
// force --steps review so run.ts doesn't re-detect from live history and choose
|
|
345
|
+
// the stale review's fix step, applying fixes to the unreviewed new diff.
|
|
346
|
+
if (item.action === 'review' && item.explanation === 'no_usable_review_comment') {
|
|
347
|
+
args.push('--steps', 'review');
|
|
348
|
+
}
|
|
349
|
+
args.push('--expected-head-sha', item.pr.headSha);
|
|
350
|
+
if (item.action !== 'fix') {
|
|
351
|
+
if (roundMode === 'crazy')
|
|
352
|
+
args.push('--crazy');
|
|
353
|
+
else if (roundMode === 'halfcrazy')
|
|
354
|
+
args.push('--half-crazy');
|
|
355
|
+
}
|
|
356
|
+
else if (roundMode) {
|
|
357
|
+
// fix legs don't loop, but still need the no-timeout constraint lifted
|
|
358
|
+
args.push('--no-timeout');
|
|
359
|
+
}
|
|
360
|
+
// forward user-specified --timeout for runs that aren't already in a round mode
|
|
361
|
+
if (timeout && !roundMode)
|
|
362
|
+
args.push('--timeout', timeout);
|
|
363
|
+
args.push('--trigger', 'kickass');
|
|
364
|
+
return args;
|
|
176
365
|
}
|
|
177
366
|
export function resolveCliInvocation(options = {}) {
|
|
178
367
|
const exists = options.exists ?? existsSync;
|
|
@@ -191,16 +380,48 @@ export function resolveCliInvocation(options = {}) {
|
|
|
191
380
|
return invocationForEntry(sourceCli, execPath, localTsx, exists);
|
|
192
381
|
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
382
|
}
|
|
194
|
-
function defaultKickassDeps() {
|
|
383
|
+
function defaultKickassDeps(opts = {}, board) {
|
|
195
384
|
let cli;
|
|
196
385
|
const getCli = () => {
|
|
197
386
|
cli ??= resolveCliInvocation();
|
|
198
387
|
return cli;
|
|
199
388
|
};
|
|
389
|
+
const dispatchRun = async (item) => {
|
|
390
|
+
const invocation = getCli();
|
|
391
|
+
const args = [...invocation.args, ...buildKickassRunArgs(item, opts.roundMode, opts.timeout)];
|
|
392
|
+
// When board is active always pipe so output routes through board.log scrollback.
|
|
393
|
+
// Without board, pipe only for explicit concurrent mode; sequential streams inline.
|
|
394
|
+
if (board || opts.concurrent !== undefined) {
|
|
395
|
+
try {
|
|
396
|
+
const result = await execa(invocation.command, args, { stdio: 'pipe', all: true });
|
|
397
|
+
return result.all ?? '';
|
|
398
|
+
}
|
|
399
|
+
catch (err) {
|
|
400
|
+
// Surface captured output before re-throwing so the board log includes
|
|
401
|
+
// the child's stdout/stderr (auth errors, model failures, etc.) that were
|
|
402
|
+
// previously visible via inherited stdio.
|
|
403
|
+
const e = err;
|
|
404
|
+
const captured = typeof e.all === 'string' ? e.all.trim()
|
|
405
|
+
: typeof e.stderr === 'string' ? e.stderr.trim() : '';
|
|
406
|
+
if (captured) {
|
|
407
|
+
if (board)
|
|
408
|
+
board.log(captured);
|
|
409
|
+
else
|
|
410
|
+
console.error(captured);
|
|
411
|
+
}
|
|
412
|
+
throw err;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
await execa(invocation.command, args, { stdio: 'inherit' });
|
|
416
|
+
};
|
|
417
|
+
const actionPhase = (action) => action === 'fix' ? 'fixing' : action === 'recheck' ? 'rechecking' : 'reviewing';
|
|
418
|
+
const actionLabel = (action) => action === 'fix' ? 'applying fix...' : action === 'recheck' ? 'rechecking...' : 'reviewing...';
|
|
419
|
+
const donePhase = (action) => action === 'fix' ? 'fixed' : action === 'recheck' ? 'rechecked' : 'reviewed';
|
|
200
420
|
return {
|
|
201
421
|
loadScanResult,
|
|
202
422
|
pickPRs,
|
|
203
423
|
confirm: confirmMutation,
|
|
424
|
+
getFixDeliveryMode: () => loadConfig().post_review.auto_fix.delivery.mode,
|
|
204
425
|
getCurrentHeadSha: async (item) => {
|
|
205
426
|
const token = getGithubToken();
|
|
206
427
|
const octokit = createGithubClient(token);
|
|
@@ -211,18 +432,24 @@ function defaultKickassDeps() {
|
|
|
211
432
|
});
|
|
212
433
|
return data.head.sha;
|
|
213
434
|
},
|
|
214
|
-
dispatchRun
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
225
|
-
|
|
435
|
+
dispatchRun,
|
|
436
|
+
...(board && {
|
|
437
|
+
log: (msg) => board.log(msg),
|
|
438
|
+
onDispatchStart: (item, key, _startedAt) => {
|
|
439
|
+
board.addPR(key, item.pr.number, `${item.pr.owner}/${item.pr.repo}`, item.pr.headRef);
|
|
440
|
+
board.updatePR(key, { phase: actionPhase(item.action), label: actionLabel(item.action) });
|
|
441
|
+
},
|
|
442
|
+
onDispatchEnd: (item, key, startedAt) => {
|
|
443
|
+
board.updatePR(key, { phase: donePhase(item.action) });
|
|
444
|
+
board.completePR(key, { elapsedMs: Date.now() - startedAt, url: item.pr.url });
|
|
445
|
+
},
|
|
446
|
+
onDispatchFail: (_item, key, err) => {
|
|
447
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
448
|
+
board.failPR(key, msg);
|
|
449
|
+
},
|
|
450
|
+
onBeforeExecute: () => board.start(),
|
|
451
|
+
onAfterExecute: () => board.stop(),
|
|
452
|
+
}),
|
|
226
453
|
};
|
|
227
454
|
}
|
|
228
455
|
async function confirmMutation(message) {
|
|
@@ -268,10 +495,10 @@ function checksLabel(pr) {
|
|
|
268
495
|
return 'green';
|
|
269
496
|
return pr.merge.mergeStateStatus ?? 'unknown';
|
|
270
497
|
}
|
|
271
|
-
function
|
|
272
|
-
if (action === 'review')
|
|
498
|
+
function stepsForItem(item) {
|
|
499
|
+
if (item.action === 'review')
|
|
273
500
|
return 'review';
|
|
274
|
-
if (action === 'fix')
|
|
501
|
+
if (item.action === 'fix')
|
|
275
502
|
return 'fix';
|
|
276
503
|
return 'recheck';
|
|
277
504
|
}
|