@humanbased/crosscheck 1.3.0-beta.92 → 1.3.0-beta.94
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/crosscheck.config.example.yml +9 -0
- package/dist/__tests__/detector.test.js +87 -1
- package/dist/__tests__/detector.test.js.map +1 -1
- package/dist/__tests__/runner.test.js +161 -1
- package/dist/__tests__/runner.test.js.map +1 -1
- package/dist/__tests__/superseded-fix-pr.test.d.ts +2 -0
- package/dist/__tests__/superseded-fix-pr.test.d.ts.map +1 -0
- package/dist/__tests__/superseded-fix-pr.test.js +225 -0
- package/dist/__tests__/superseded-fix-pr.test.js.map +1 -0
- package/dist/__tests__/webhook.test.js +60 -0
- package/dist/__tests__/webhook.test.js.map +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +29 -2
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/watch.d.ts.map +1 -1
- package/dist/commands/watch.js +24 -0
- package/dist/commands/watch.js.map +1 -1
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +7 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/github/detector.d.ts.map +1 -1
- package/dist/github/detector.js +28 -1
- package/dist/github/detector.js.map +1 -1
- package/dist/github/superseded-fix-pr.d.ts +19 -0
- package/dist/github/superseded-fix-pr.d.ts.map +1 -0
- package/dist/github/superseded-fix-pr.js +97 -0
- package/dist/github/superseded-fix-pr.js.map +1 -0
- package/dist/github/webhook.d.ts +3 -1
- package/dist/github/webhook.d.ts.map +1 -1
- package/dist/github/webhook.js +8 -1
- package/dist/github/webhook.js.map +1 -1
- package/dist/lib/runner.d.ts +24 -0
- package/dist/lib/runner.d.ts.map +1 -1
- package/dist/lib/runner.js +111 -18
- package/dist/lib/runner.js.map +1 -1
- package/dist/lib/workflow.d.ts +4 -0
- package/dist/lib/workflow.d.ts.map +1 -1
- package/dist/lib/workflow.js.map +1 -1
- package/get-started.md +7 -0
- package/package.json +1 -1
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// An auto-fix PR is a snapshot of the reviewed PR at the moment the fix ran. Once the
|
|
2
|
+
// source PR merges, that snapshot is frozen against a tree that no longer exists: if
|
|
3
|
+
// the author pushed their own fix for the same finding, the two fixes live side by side
|
|
4
|
+
// and merging the auto-fix reintroduces exactly what the author's version removed —
|
|
5
|
+
// while reading, on the PR page, like a routine cleanup merge. Nothing in the auto-fix
|
|
6
|
+
// PR says it was superseded.
|
|
7
|
+
//
|
|
8
|
+
// So when the source PR merges, close its auto-fix PR and say why. The finding is
|
|
9
|
+
// either fixed on the base branch or still open there; either way the stale snapshot
|
|
10
|
+
// is not the vehicle for it.
|
|
11
|
+
export function autoFixBranchName(sourcePrNumber) {
|
|
12
|
+
return `fix/cr-${sourcePrNumber}-review-issues`;
|
|
13
|
+
}
|
|
14
|
+
// First line of every auto-fix PR body. Doubles as the ownership check below, so it is
|
|
15
|
+
// built here rather than inlined at the creation site.
|
|
16
|
+
export function autoFixPRIntro(sourcePrNumber) {
|
|
17
|
+
return `Auto-fix by crosscheck for CR issues found in #${sourcePrNumber}.`;
|
|
18
|
+
}
|
|
19
|
+
// Branch-name matching alone would let a hand-created branch of the same name be
|
|
20
|
+
// closed by crosscheck. Require the body it writes as well.
|
|
21
|
+
export function isCrosscheckAutoFixPR(body, sourcePrNumber) {
|
|
22
|
+
return (body ?? '').includes(autoFixPRIntro(sourcePrNumber));
|
|
23
|
+
}
|
|
24
|
+
export function buildSupersededComment(input) {
|
|
25
|
+
const mergedAs = input.mergeCommitSha
|
|
26
|
+
? ` as [\`${input.mergeCommitSha.slice(0, 9)}\`](https://github.com/${input.owner}/${input.repo}/commit/${input.mergeCommitSha})`
|
|
27
|
+
: '';
|
|
28
|
+
return [
|
|
29
|
+
'### Superseded — closing',
|
|
30
|
+
'',
|
|
31
|
+
`#${input.sourcePrNumber} merged${mergedAs}, so the changes on this branch are a snapshot of a tree that no longer exists. If the author fixed the same finding themselves, merging this PR would reintroduce what their fix removed.`,
|
|
32
|
+
'',
|
|
33
|
+
`Re-run a review against \`${input.owner}/${input.repo}\`'s base branch if the finding still stands there — this branch is not the right vehicle for it.`,
|
|
34
|
+
'',
|
|
35
|
+
'<!-- crosscheck: superseded_auto_fix -->',
|
|
36
|
+
].join('\n');
|
|
37
|
+
}
|
|
38
|
+
// True once the source PR has merged. A PR closed *unmerged* is not a merge: the
|
|
39
|
+
// source branch is still where that finding gets addressed.
|
|
40
|
+
//
|
|
41
|
+
// The merge handler sweeps for auto-fix PRs exactly once, so a fallback auto-fix
|
|
42
|
+
// PR opened after that sweep is never seen by it and stays open and mergeable —
|
|
43
|
+
// the stale-snapshot regression this module exists to prevent. The fix step asks
|
|
44
|
+
// this immediately before it pushes the branch and opens the PR, which closes
|
|
45
|
+
// the window down to that single call.
|
|
46
|
+
//
|
|
47
|
+
// Fails open: an unreadable state returns false and the fix is delivered as
|
|
48
|
+
// usual. A dropped fix is invisible to everyone, whereas today's behaviour is to
|
|
49
|
+
// create unconditionally — so failing open is no regression, and the merge
|
|
50
|
+
// webhook remains the primary mechanism.
|
|
51
|
+
export async function sourcePRHasMerged(octokit, owner, repo, sourcePrNumber) {
|
|
52
|
+
try {
|
|
53
|
+
const { data } = await octokit.rest.pulls.get({ owner, repo, pull_number: sourcePrNumber });
|
|
54
|
+
return data.merged === true;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Closes every open auto-fix PR cut from `sourcePrNumber`, commenting first so the
|
|
61
|
+
// reason survives on the timeline. Returns one outcome per candidate found, in the
|
|
62
|
+
// order GitHub listed them; an empty array means there was nothing cut from that PR
|
|
63
|
+
// (the common case).
|
|
64
|
+
//
|
|
65
|
+
// Candidates are handled concurrently — a comment+close pair is two round-trips,
|
|
66
|
+
// and serialising them makes every later candidate wait on all the earlier ones.
|
|
67
|
+
// Ordering that matters is preserved: comment still precedes close *within* a
|
|
68
|
+
// candidate, and Promise.all keeps the results positional.
|
|
69
|
+
export async function closeSupersededAutoFixPRs(octokit, owner, repo, sourcePrNumber, mergeCommitSha) {
|
|
70
|
+
const branch = autoFixBranchName(sourcePrNumber);
|
|
71
|
+
const { data: candidates } = await octokit.rest.pulls.list({
|
|
72
|
+
owner, repo, state: 'open', head: `${owner}:${branch}`,
|
|
73
|
+
});
|
|
74
|
+
return Promise.all(candidates.map(async (candidate) => {
|
|
75
|
+
if (!isCrosscheckAutoFixPR(candidate.body, sourcePrNumber)) {
|
|
76
|
+
return { prNumber: candidate.number, status: 'not_crosscheck' };
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
await octokit.rest.issues.createComment({
|
|
80
|
+
owner, repo, issue_number: candidate.number,
|
|
81
|
+
body: buildSupersededComment({ owner, repo, sourcePrNumber, mergeCommitSha }),
|
|
82
|
+
});
|
|
83
|
+
await octokit.rest.pulls.update({ owner, repo, pull_number: candidate.number, state: 'closed' });
|
|
84
|
+
return { prNumber: candidate.number, status: 'closed' };
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
// Per-candidate catch, so one failure cannot reject the whole batch and
|
|
88
|
+
// leave the remaining candidates unreported.
|
|
89
|
+
return {
|
|
90
|
+
prNumber: candidate.number,
|
|
91
|
+
status: 'failed',
|
|
92
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=superseded-fix-pr.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"superseded-fix-pr.js","sourceRoot":"","sources":["../../src/github/superseded-fix-pr.ts"],"names":[],"mappings":"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"}
|
package/dist/github/webhook.d.ts
CHANGED
|
@@ -27,6 +27,8 @@ export interface PREvent {
|
|
|
27
27
|
labels?: Array<{
|
|
28
28
|
name: string;
|
|
29
29
|
}>;
|
|
30
|
+
merged?: boolean;
|
|
31
|
+
merge_commit_sha?: string | null;
|
|
30
32
|
created_at?: string;
|
|
31
33
|
};
|
|
32
34
|
repository: {
|
|
@@ -66,5 +68,5 @@ export interface WebhookFileLogEntry {
|
|
|
66
68
|
event: string;
|
|
67
69
|
[key: string]: unknown;
|
|
68
70
|
}
|
|
69
|
-
export declare function createWebhookServer(config: Config, webhookSecret: string, onPR: (event: PREvent) => void, onLog: (msg: string) => void, onFileLog?: (entry: WebhookFileLogEntry) => void, onComment?: (event: IssueCommentEvent) => void): import("http").Server<typeof IncomingMessage, typeof ServerResponse>;
|
|
71
|
+
export declare function createWebhookServer(config: Config, webhookSecret: string, onPR: (event: PREvent) => void, onLog: (msg: string) => void, onFileLog?: (entry: WebhookFileLogEntry) => void, onComment?: (event: IssueCommentEvent) => void, onMerged?: (event: PREvent) => void): import("http").Server<typeof IncomingMessage, typeof ServerResponse>;
|
|
70
72
|
//# sourceMappingURL=webhook.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/github/webhook.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,MAAM,CAAA;AAC9E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAIjD,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE;QACZ,KAAK,EAAE,MAAM,CAAA;QACb,IAAI,EAAE,MAAM,CAAA;QACZ,IAAI,EAAE;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,GAAG,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE;gBAAE,SAAS,EAAE,MAAM,CAAA;aAAE,GAAG,IAAI,CAAA;SAAE,CAAA;QAItE,IAAI,EAAE;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE;gBAAE,SAAS,EAAE,MAAM,CAAC;gBAAC,cAAc,CAAC,EAAE,MAAM,CAAA;aAAE,CAAA;SAAE,CAAA;QAC3E,QAAQ,EAAE,MAAM,CAAA;QAChB,IAAI,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAA;QAEvB,MAAM,CAAC,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;QAKhC,UAAU,CAAC,EAAE,MAAM,CAAA;KACpB,CAAA;IACD,UAAU,EAAE;QACV,IAAI,EAAE,MAAM,CAAA;QACZ,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAA;QACxB,SAAS,EAAE,MAAM,CAAA;KAClB,CAAA;CACF;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE;QACL,MAAM,EAAE,MAAM,CAAA;QACd,KAAK,EAAE,MAAM,CAAA;QACb,IAAI,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAA;QACvB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KACvC,CAAA;IACD,OAAO,EAAE;QACP,EAAE,EAAE,MAAM,CAAA;QACV,IAAI,EAAE,MAAM,CAAA;QACZ,IAAI,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAA;KACxB,CAAA;IACD,UAAU,EAAE;QACV,IAAI,EAAE,MAAM,CAAA;QACZ,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAA;KACzB,CAAA;CACF;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,EACrB,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,EAC9B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,EAC5B,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,EAChD,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,
|
|
1
|
+
{"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/github/webhook.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,MAAM,CAAA;AAC9E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAIjD,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE;QACZ,KAAK,EAAE,MAAM,CAAA;QACb,IAAI,EAAE,MAAM,CAAA;QACZ,IAAI,EAAE;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,GAAG,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE;gBAAE,SAAS,EAAE,MAAM,CAAA;aAAE,GAAG,IAAI,CAAA;SAAE,CAAA;QAItE,IAAI,EAAE;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE;gBAAE,SAAS,EAAE,MAAM,CAAC;gBAAC,cAAc,CAAC,EAAE,MAAM,CAAA;aAAE,CAAA;SAAE,CAAA;QAC3E,QAAQ,EAAE,MAAM,CAAA;QAChB,IAAI,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAA;QAEvB,MAAM,CAAC,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;QAGhC,MAAM,CAAC,EAAE,OAAO,CAAA;QAChB,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;QAKhC,UAAU,CAAC,EAAE,MAAM,CAAA;KACpB,CAAA;IACD,UAAU,EAAE;QACV,IAAI,EAAE,MAAM,CAAA;QACZ,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAA;QACxB,SAAS,EAAE,MAAM,CAAA;KAClB,CAAA;CACF;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE;QACL,MAAM,EAAE,MAAM,CAAA;QACd,KAAK,EAAE,MAAM,CAAA;QACb,IAAI,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAA;QACvB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KACvC,CAAA;IACD,OAAO,EAAE;QACP,EAAE,EAAE,MAAM,CAAA;QACV,IAAI,EAAE,MAAM,CAAA;QACZ,IAAI,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAA;KACxB,CAAA;IACD,UAAU,EAAE;QACV,IAAI,EAAE,MAAM,CAAA;QACZ,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAA;KACzB,CAAA;CACF;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,EACrB,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,EAC9B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,EAC5B,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,EAChD,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,EAC9C,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,wEA6FpC"}
|
package/dist/github/webhook.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createServer } from 'http';
|
|
2
2
|
import { verifyWebhookSignature } from './client.js';
|
|
3
3
|
import { parseAnnotation } from '../lib/annotation.js';
|
|
4
|
-
export function createWebhookServer(config, webhookSecret, onPR, onLog, onFileLog, onComment) {
|
|
4
|
+
export function createWebhookServer(config, webhookSecret, onPR, onLog, onFileLog, onComment, onMerged) {
|
|
5
5
|
const server = createServer(async (req, res) => {
|
|
6
6
|
const { pathname } = new URL(req.url ?? '/', `http://localhost`);
|
|
7
7
|
if (pathname !== config.server.webhook_path) {
|
|
@@ -77,6 +77,13 @@ export function createWebhookServer(config, webhookSecret, onPR, onLog, onFileLo
|
|
|
77
77
|
// async — don't block the webhook response
|
|
78
78
|
setImmediate(() => onPR(body));
|
|
79
79
|
}
|
|
80
|
+
else if (body.action === 'closed' && body.pull_request.merged === true && onMerged) {
|
|
81
|
+
// A merge freezes any auto-fix PR cut from this one into a snapshot of a tree
|
|
82
|
+
// that no longer exists; the handler closes it. Closed-unmerged is not the same
|
|
83
|
+
// case — the source PR's branch is still the place the finding gets addressed.
|
|
84
|
+
res.writeHead(200).end('ok');
|
|
85
|
+
setImmediate(() => onMerged(body));
|
|
86
|
+
}
|
|
80
87
|
else {
|
|
81
88
|
res.writeHead(200).end('ok');
|
|
82
89
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../src/github/webhook.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAA6C,MAAM,MAAM,CAAA;AAE9E,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../src/github/webhook.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/runner.d.ts
CHANGED
|
@@ -98,13 +98,37 @@ export interface WorkflowResult {
|
|
|
98
98
|
id?: number;
|
|
99
99
|
body: string;
|
|
100
100
|
};
|
|
101
|
+
/** What each dispatched step actually did. Lets a caller tell "ran and found
|
|
102
|
+
* nothing" apart from "never ran", which the verdict alone cannot express —
|
|
103
|
+
* every step of a conflict-resolve run can skip and still leave verdict null,
|
|
104
|
+
* exactly like a review that approved nothing. */
|
|
105
|
+
stepOutcomes?: StepOutcomes;
|
|
101
106
|
}
|
|
107
|
+
export interface StepOutcomes {
|
|
108
|
+
/** Steps that executed. A dispatched step with no recorded result counts as
|
|
109
|
+
* ran: only an explicit skip is evidence that nothing happened. */
|
|
110
|
+
ran: string[];
|
|
111
|
+
/** Steps dispatched but skipped, each with the reason recorded on its
|
|
112
|
+
* step_skipped log entry. */
|
|
113
|
+
skipped: {
|
|
114
|
+
step: string;
|
|
115
|
+
reason: string;
|
|
116
|
+
}[];
|
|
117
|
+
}
|
|
118
|
+
export declare function summariseStepOutcomes(stepsRun: readonly string[], results: Record<string, StepResult>): StepOutcomes;
|
|
119
|
+
export declare function mergeStepOutcomes(base: StepOutcomes | undefined, next: StepOutcomes | undefined): StepOutcomes | undefined;
|
|
102
120
|
export declare function fixCommitSubject(appliedCount: number, vendor: Vendor): string;
|
|
103
121
|
export declare function fixPRCommitSubject(prNumber: number, vendor: Vendor): string;
|
|
104
122
|
export declare function conflictResolveCommitSubject(conflictCount: number, vendor: Vendor): string;
|
|
105
123
|
export declare function resolveFixVendor(stepReviewer: string, origin: PROrigin, config: Config, fallback?: 'claude' | 'codex'): {
|
|
106
124
|
vendor: 'claude' | 'codex' | null;
|
|
107
125
|
usedHumanFallback: boolean;
|
|
126
|
+
substitutedOriginVendor?: 'claude' | 'codex';
|
|
127
|
+
};
|
|
128
|
+
export declare function resolveConflictResolveVendor(stepReviewer: string, origin: PROrigin, config: Config, fallback?: 'claude' | 'codex'): {
|
|
129
|
+
vendor: 'claude' | 'codex' | null;
|
|
130
|
+
usedHumanFallback: boolean;
|
|
131
|
+
substitutedOriginVendor?: 'claude' | 'codex';
|
|
108
132
|
};
|
|
109
133
|
/**
|
|
110
134
|
* Builds the input the review strategy classifies on, from the already-cloned
|
package/dist/lib/runner.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/lib/runner.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAoB,MAAM,qBAAqB,CAAA;AAEnE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AACnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,EAAqB,KAAK,MAAM,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/lib/runner.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/runner.js
CHANGED
|
@@ -11,6 +11,7 @@ import { runFixStep, runCodexFixStep } from '../reviewers/fix.js';
|
|
|
11
11
|
import { runConflictResolveStep, findConflictedFiles } from '../reviewers/conflict-resolve.js';
|
|
12
12
|
import { parseVerdict, prependVerdictToComment, NULL_VERDICT_WARNING, applySeverityGate, SEVERITY_GATE_NOTE } from '../lib/verdict.js';
|
|
13
13
|
import { createGithubClient, postReviewComment, getLastCrossCheckCommentId, getLastCrossCheckReviewComment } from '../github/client.js';
|
|
14
|
+
import { autoFixBranchName, autoFixPRIntro, sourcePRHasMerged } from '../github/superseded-fix-pr.js';
|
|
14
15
|
import { verifyReviewedSha, isVerifiedReviewedSha, reviewedShaRejection } from '../github/reviewed-sha.js';
|
|
15
16
|
import { resolveLinearAuth, withWorker } from '../linear/identity.js';
|
|
16
17
|
import { notifyLinear } from '../linear/notify.js';
|
|
@@ -148,6 +149,45 @@ export function resolveFixLanding(deliveryMode) {
|
|
|
148
149
|
case 'pull_request': return 'branch-then-separate-pr';
|
|
149
150
|
}
|
|
150
151
|
}
|
|
152
|
+
// stepsRun holds every step the runner dispatched, skips included; results holds
|
|
153
|
+
// what each one did. Split them so callers can report a run where nothing
|
|
154
|
+
// happened without re-deriving the reasons from the log file.
|
|
155
|
+
export function summariseStepOutcomes(stepsRun, results) {
|
|
156
|
+
const outcomes = { ran: [], skipped: [] };
|
|
157
|
+
for (const name of new Set(stepsRun)) {
|
|
158
|
+
const result = results[name];
|
|
159
|
+
if (result?.skipped)
|
|
160
|
+
outcomes.skipped.push({ step: name, reason: result.skipReason ?? 'unknown' });
|
|
161
|
+
else
|
|
162
|
+
outcomes.ran.push(name);
|
|
163
|
+
}
|
|
164
|
+
return outcomes;
|
|
165
|
+
}
|
|
166
|
+
// Accumulates outcomes across the fix→recheck rounds of one invocation, so the
|
|
167
|
+
// completion line reports whether the run as a whole did work: a first round
|
|
168
|
+
// that skips everything followed by a round that applies a fix is not a
|
|
169
|
+
// "no step ran" run.
|
|
170
|
+
//
|
|
171
|
+
// Running once wins over skipping any number of times — the step demonstrably
|
|
172
|
+
// happened. A step skipped in several rounds is reported once, carrying its
|
|
173
|
+
// latest reason: that is the state the run ended in, and listing the same step
|
|
174
|
+
// several times reads as several distinct problems.
|
|
175
|
+
export function mergeStepOutcomes(base, next) {
|
|
176
|
+
if (!base)
|
|
177
|
+
return next;
|
|
178
|
+
if (!next)
|
|
179
|
+
return base;
|
|
180
|
+
const ran = [...new Set([...base.ran, ...next.ran])];
|
|
181
|
+
const ranSet = new Set(ran);
|
|
182
|
+
// Map.set on an existing key overwrites the reason but keeps the original
|
|
183
|
+
// position, so order stays first-seen while the reason stays last-seen.
|
|
184
|
+
const skipped = new Map();
|
|
185
|
+
for (const entry of [...base.skipped, ...next.skipped]) {
|
|
186
|
+
if (!ranSet.has(entry.step))
|
|
187
|
+
skipped.set(entry.step, entry.reason);
|
|
188
|
+
}
|
|
189
|
+
return { ran, skipped: [...skipped].map(([step, reason]) => ({ step, reason })) };
|
|
190
|
+
}
|
|
151
191
|
function countComments(reviewText) {
|
|
152
192
|
const bullets = (reviewText.match(/^[-*•]\s/gm) ?? []).length;
|
|
153
193
|
const numbered = (reviewText.match(/^\d+\.\s/gm) ?? []).length;
|
|
@@ -204,16 +244,38 @@ export function fixPRCommitSubject(prNumber, vendor) {
|
|
|
204
244
|
export function conflictResolveCommitSubject(conflictCount, vendor) {
|
|
205
245
|
return `[crosscheck] resolve: resolve ${conflictCount} conflict${conflictCount !== 1 ? 's' : ''} — by ${vendorDisplayName(vendor)}`;
|
|
206
246
|
}
|
|
207
|
-
// Extends resolveReviewer with a human-origin fallback for the
|
|
247
|
+
// Extends resolveReviewer with a human-origin fallback for the steps that write
|
|
248
|
+
// code (fix, conflict-resolve).
|
|
208
249
|
// Scoped to reviewer: 'origin' only — other reviewer types (claude, codex, auto)
|
|
209
250
|
// already encode explicit vendor intent and need no fallback.
|
|
210
251
|
// When origin is 'human' and no vendor resolved, honours routing.fallback_reviewer
|
|
211
|
-
// so the
|
|
212
|
-
// 'auto'
|
|
213
|
-
//
|
|
214
|
-
//
|
|
215
|
-
|
|
252
|
+
// so the step respects the same routing intent as the review step.
|
|
253
|
+
// 'auto' resolves against the vendors that can actually run stepType, so a step
|
|
254
|
+
// only one vendor supports doesn't fall back to the other and skip a line later.
|
|
255
|
+
// An explicit 'claude'/'codex' is an operator decision and is honoured as written
|
|
256
|
+
// even when that vendor cannot run the step — the caller then reports a precise
|
|
257
|
+
// unsupported-step skip instead of silently substituting a different vendor.
|
|
258
|
+
// null disables the fallback entirely.
|
|
259
|
+
function resolveStepVendor(stepType, stepReviewer, origin, config, fallback) {
|
|
216
260
|
const vendor = resolveReviewer(stepReviewer, origin, config, fallback);
|
|
261
|
+
// Origin detection can assign a vendor that cannot run the step: conflict
|
|
262
|
+
// resolution is Claude-only, so a Codex-origin PR (reviewer: 'origin', origin:
|
|
263
|
+
// 'codex') resolves to 'codex' and the dispatch skips it as unsupported — the
|
|
264
|
+
// conflicts never get resolved. (#284's `Crosscheck-Reviewer: codex` detection
|
|
265
|
+
// introduced this: these crosscheck-authored fix PRs used to detect as 'human'
|
|
266
|
+
// and the auto fallback picked Claude.) Substitute a capable, enabled vendor.
|
|
267
|
+
// Scoped to origin-derived assignment only — an explicit reviewer: claude|codex,
|
|
268
|
+
// reviewer: auto, or routing.fallback_reviewer is an operator decision, left as
|
|
269
|
+
// written so the caller can report the precise unsupported-step skip.
|
|
270
|
+
if (stepReviewer === 'origin' &&
|
|
271
|
+
(origin === 'claude' || origin === 'codex') &&
|
|
272
|
+
vendor !== null &&
|
|
273
|
+
!supportsStep(vendor, stepType)) {
|
|
274
|
+
const capable = vendor === 'claude' ? 'codex' : 'claude';
|
|
275
|
+
if (config.vendors[capable].enabled && supportsStep(capable, stepType)) {
|
|
276
|
+
return { vendor: capable, usedHumanFallback: false, substitutedOriginVendor: vendor };
|
|
277
|
+
}
|
|
278
|
+
}
|
|
217
279
|
if (vendor !== null || origin !== 'human' || stepReviewer !== 'origin') {
|
|
218
280
|
return { vendor, usedHumanFallback: false };
|
|
219
281
|
}
|
|
@@ -224,13 +286,25 @@ export function resolveFixVendor(stepReviewer, origin, config, fallback) {
|
|
|
224
286
|
else if (fb === 'codex')
|
|
225
287
|
humanFallback = config.vendors.codex.enabled ? 'codex' : null;
|
|
226
288
|
else if (fb !== null) {
|
|
227
|
-
// 'auto': prefer codex then claude, same as resolveReviewer's auto path
|
|
228
|
-
|
|
289
|
+
// 'auto': prefer codex then claude, same as resolveReviewer's auto path,
|
|
290
|
+
// narrowed to vendors that support this step type.
|
|
291
|
+
const usable = (v) => config.vendors[v].enabled && supportsStep(v, stepType);
|
|
292
|
+
humanFallback = usable('codex') ? 'codex' : usable('claude') ? 'claude' : null;
|
|
229
293
|
}
|
|
230
294
|
if (!humanFallback)
|
|
231
295
|
return { vendor: null, usedHumanFallback: false };
|
|
232
296
|
return { vendor: humanFallback, usedHumanFallback: true };
|
|
233
297
|
}
|
|
298
|
+
// Exported so callers can detect when the fallback was applied (e.g. for logging).
|
|
299
|
+
export function resolveFixVendor(stepReviewer, origin, config, fallback) {
|
|
300
|
+
return resolveStepVendor('fix', stepReviewer, origin, config, fallback);
|
|
301
|
+
}
|
|
302
|
+
// The default workflow gives conflict-resolve `reviewer: origin`, so every PR
|
|
303
|
+
// crosscheck cannot attribute resolved to null here and skipped with 'no_vendor'
|
|
304
|
+
// — the fix step honoured routing.fallback_reviewer, this one did not.
|
|
305
|
+
export function resolveConflictResolveVendor(stepReviewer, origin, config, fallback) {
|
|
306
|
+
return resolveStepVendor('conflict-resolve', stepReviewer, origin, config, fallback);
|
|
307
|
+
}
|
|
234
308
|
// ─── pr_complexity helpers ────────────────────────────────────────────────────
|
|
235
309
|
const EXT_LANG = {
|
|
236
310
|
ts: 'typescript', tsx: 'typescript', js: 'javascript', jsx: 'javascript',
|
|
@@ -718,7 +792,7 @@ export async function runWorkflow(ctx) {
|
|
|
718
792
|
const effectiveType = getEffectiveStepType(step.type, ctx.isRecheckRun === true);
|
|
719
793
|
if (exceedsMaxRounds(effectiveType, step.type, ctx.overrideMaxRounds ?? step.max_rounds, ctx.round)) {
|
|
720
794
|
fileLog({ level: 'info', event: 'step_skipped', repo: `${owner}/${repoName}`, pr: prNumber, step: step.name, reason: 'max_rounds' });
|
|
721
|
-
results[step.name] = { skipped: true };
|
|
795
|
+
results[step.name] = { skipped: true, skipReason: 'max_rounds' };
|
|
722
796
|
if (effectiveType === 'fix')
|
|
723
797
|
onPhaseChange('', { phase: 'fixed', fixCount: 0 });
|
|
724
798
|
else if (effectiveType === 'recheck')
|
|
@@ -730,7 +804,7 @@ export async function runWorkflow(ctx) {
|
|
|
730
804
|
// Evaluate when condition — skip step if false
|
|
731
805
|
if (step.when && !evaluateWhen(step.when, results)) {
|
|
732
806
|
fileLog({ level: 'info', event: 'step_skipped', repo: `${owner}/${repoName}`, pr: prNumber, step: step.name, reason: 'when_condition' });
|
|
733
|
-
results[step.name] = { skipped: true };
|
|
807
|
+
results[step.name] = { skipped: true, skipReason: 'when_condition' };
|
|
734
808
|
if (effectiveType === 'fix')
|
|
735
809
|
onPhaseChange('', { phase: 'fixed', fixCount: 0 });
|
|
736
810
|
else if (effectiveType === 'recheck')
|
|
@@ -744,7 +818,7 @@ export async function runWorkflow(ctx) {
|
|
|
744
818
|
// review coerced to a recheck (isRecheckRun) is never blocked here.
|
|
745
819
|
if (step.type === 'recheck' && reviewRanThisSession && !anyFixApplied(results)) {
|
|
746
820
|
fileLog({ level: 'info', event: 'step_skipped', repo: `${owner}/${repoName}`, pr: prNumber, step: step.name, reason: 'no_change_since_review' });
|
|
747
|
-
results[step.name] = { skipped: true };
|
|
821
|
+
results[step.name] = { skipped: true, skipReason: 'no_change_since_review' };
|
|
748
822
|
onPhaseChange('', { phase: 'rechecked' });
|
|
749
823
|
continue;
|
|
750
824
|
}
|
|
@@ -753,7 +827,7 @@ export async function runWorkflow(ctx) {
|
|
|
753
827
|
let reviewer = resolveReviewer(step.reviewer, origin, config, ctx.smartSwitchFallback);
|
|
754
828
|
if (!reviewer) {
|
|
755
829
|
fileLog({ level: 'info', event: 'step_skipped', repo: `${owner}/${repoName}`, pr: prNumber, step: step.name, reason: 'no_reviewer' });
|
|
756
|
-
results[step.name] = { skipped: true };
|
|
830
|
+
results[step.name] = { skipped: true, skipReason: 'no_reviewer' };
|
|
757
831
|
continue;
|
|
758
832
|
}
|
|
759
833
|
// The recheck step is confirmed to run — clear the pending-recheck guard
|
|
@@ -1001,7 +1075,7 @@ export async function runWorkflow(ctx) {
|
|
|
1001
1075
|
const skipFix = (reason) => {
|
|
1002
1076
|
lastFixSkipReason = reason;
|
|
1003
1077
|
onPhaseChange('', { phase: 'fixed', fixCount: 0 });
|
|
1004
|
-
results[step.name] = { skipped: true };
|
|
1078
|
+
results[step.name] = { skipped: true, skipReason: reason };
|
|
1005
1079
|
fileLog({ level: 'info', event: 'step_skipped', repo: `${owner}/${repoName}`, pr: prNumber, step: step.name, reason });
|
|
1006
1080
|
};
|
|
1007
1081
|
if (ctx.dryRun) {
|
|
@@ -1342,12 +1416,21 @@ export async function runWorkflow(ctx) {
|
|
|
1342
1416
|
else {
|
|
1343
1417
|
// Fallback: the fix could not land on the PR branch. Push the same commit to
|
|
1344
1418
|
// a dedicated branch and open a follow-up PR targeting the original branch.
|
|
1345
|
-
const
|
|
1419
|
+
const octokit = createGithubClient(token);
|
|
1420
|
+
// The merged-PR handler sweeps for auto-fix PRs once. A PR opened after
|
|
1421
|
+
// that sweep is never seen by it and stays open and mergeable against a
|
|
1422
|
+
// tree that no longer exists. Checked here, immediately before the push,
|
|
1423
|
+
// so the window is one API call rather than the whole fix run — and so a
|
|
1424
|
+
// superseded fix leaves no branch behind either.
|
|
1425
|
+
if (await sourcePRHasMerged(octokit, owner, repoName, prNumber)) {
|
|
1426
|
+
skipFix('source_pr_merged');
|
|
1427
|
+
continue;
|
|
1428
|
+
}
|
|
1429
|
+
const fixBranch = autoFixBranchName(prNumber);
|
|
1346
1430
|
execSync(`git push origin HEAD:${fixBranch}`, {
|
|
1347
1431
|
cwd: tmpDir,
|
|
1348
1432
|
env: { ...process.env, GITHUB_TOKEN: token, GH_TOKEN: token },
|
|
1349
1433
|
});
|
|
1350
|
-
const octokit = createGithubClient(token);
|
|
1351
1434
|
const fixPrTitle = config.post_review.auto_fix.delivery.pr_title.replace('#{original_pr_title}', pr.title);
|
|
1352
1435
|
const { data: fixPr } = await octokit.rest.pulls.create({
|
|
1353
1436
|
owner,
|
|
@@ -1356,7 +1439,7 @@ export async function runWorkflow(ctx) {
|
|
|
1356
1439
|
base: pr.head.ref,
|
|
1357
1440
|
title: fixPrTitle,
|
|
1358
1441
|
body: [
|
|
1359
|
-
|
|
1442
|
+
autoFixPRIntro(prNumber),
|
|
1360
1443
|
'',
|
|
1361
1444
|
`Review: https://github.com/${owner}/${repoName}/pull/${prNumber}`,
|
|
1362
1445
|
'',
|
|
@@ -1396,7 +1479,7 @@ export async function runWorkflow(ctx) {
|
|
|
1396
1479
|
else if (effectiveType === 'conflict-resolve') {
|
|
1397
1480
|
const skipConflictResolve = (reason) => {
|
|
1398
1481
|
onPhaseChange('', { phase: 'fixed', fixCount: 0 });
|
|
1399
|
-
results[step.name] = { skipped: true };
|
|
1482
|
+
results[step.name] = { skipped: true, skipReason: reason };
|
|
1400
1483
|
fileLog({ level: 'info', event: 'step_skipped', repo: `${owner}/${repoName}`, pr: prNumber, step: step.name, reason });
|
|
1401
1484
|
};
|
|
1402
1485
|
if (ctx.dryRun) {
|
|
@@ -1443,7 +1526,16 @@ export async function runWorkflow(ctx) {
|
|
|
1443
1526
|
skipConflictResolve('no_conflicts');
|
|
1444
1527
|
continue;
|
|
1445
1528
|
}
|
|
1446
|
-
|
|
1529
|
+
// resolveConflictResolveVendor extends resolveReviewer with the same human-origin
|
|
1530
|
+
// fallback the fix step uses, so a PR crosscheck cannot attribute still gets its
|
|
1531
|
+
// conflicts resolved instead of skipping with 'no_vendor'.
|
|
1532
|
+
const { vendor, usedHumanFallback, substitutedOriginVendor } = resolveConflictResolveVendor(step.reviewer, origin, config, ctx.smartSwitchFallback);
|
|
1533
|
+
if (usedHumanFallback && vendor) {
|
|
1534
|
+
fileLog({ level: 'info', event: 'conflict_resolve_vendor_fallback', repo: `${owner}/${repoName}`, pr: prNumber, from: 'none', to: vendor, reason: 'human_origin' });
|
|
1535
|
+
}
|
|
1536
|
+
else if (substitutedOriginVendor && vendor) {
|
|
1537
|
+
fileLog({ level: 'info', event: 'conflict_resolve_vendor_fallback', repo: `${owner}/${repoName}`, pr: prNumber, from: substitutedOriginVendor, to: vendor, reason: 'unsupported_vendor' });
|
|
1538
|
+
}
|
|
1447
1539
|
if (!vendor) {
|
|
1448
1540
|
try {
|
|
1449
1541
|
execSync('git merge --abort', { cwd: tmpDir });
|
|
@@ -1649,6 +1741,7 @@ export async function runWorkflow(ctx) {
|
|
|
1649
1741
|
return {
|
|
1650
1742
|
verdict: verdict ?? null,
|
|
1651
1743
|
fixAppliedCount,
|
|
1744
|
+
stepOutcomes: summariseStepOutcomes(stepsRun, results),
|
|
1652
1745
|
...(fixAppliedCount === undefined && lastFixSkipReason !== undefined && { fixSkipReason: lastFixSkipReason }),
|
|
1653
1746
|
...(latestReviewResult?.commentBody && {
|
|
1654
1747
|
latestReviewComment: {
|