@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.
Files changed (40) hide show
  1. package/crosscheck.config.example.yml +9 -0
  2. package/dist/__tests__/detector.test.js +87 -1
  3. package/dist/__tests__/detector.test.js.map +1 -1
  4. package/dist/__tests__/runner.test.js +161 -1
  5. package/dist/__tests__/runner.test.js.map +1 -1
  6. package/dist/__tests__/superseded-fix-pr.test.d.ts +2 -0
  7. package/dist/__tests__/superseded-fix-pr.test.d.ts.map +1 -0
  8. package/dist/__tests__/superseded-fix-pr.test.js +225 -0
  9. package/dist/__tests__/superseded-fix-pr.test.js.map +1 -0
  10. package/dist/__tests__/webhook.test.js +60 -0
  11. package/dist/__tests__/webhook.test.js.map +1 -1
  12. package/dist/commands/run.d.ts.map +1 -1
  13. package/dist/commands/run.js +29 -2
  14. package/dist/commands/run.js.map +1 -1
  15. package/dist/commands/watch.d.ts.map +1 -1
  16. package/dist/commands/watch.js +24 -0
  17. package/dist/commands/watch.js.map +1 -1
  18. package/dist/config/schema.d.ts.map +1 -1
  19. package/dist/config/schema.js +7 -0
  20. package/dist/config/schema.js.map +1 -1
  21. package/dist/github/detector.d.ts.map +1 -1
  22. package/dist/github/detector.js +28 -1
  23. package/dist/github/detector.js.map +1 -1
  24. package/dist/github/superseded-fix-pr.d.ts +19 -0
  25. package/dist/github/superseded-fix-pr.d.ts.map +1 -0
  26. package/dist/github/superseded-fix-pr.js +97 -0
  27. package/dist/github/superseded-fix-pr.js.map +1 -0
  28. package/dist/github/webhook.d.ts +3 -1
  29. package/dist/github/webhook.d.ts.map +1 -1
  30. package/dist/github/webhook.js +8 -1
  31. package/dist/github/webhook.js.map +1 -1
  32. package/dist/lib/runner.d.ts +24 -0
  33. package/dist/lib/runner.d.ts.map +1 -1
  34. package/dist/lib/runner.js +111 -18
  35. package/dist/lib/runner.js.map +1 -1
  36. package/dist/lib/workflow.d.ts +4 -0
  37. package/dist/lib/workflow.d.ts.map +1 -1
  38. package/dist/lib/workflow.js.map +1 -1
  39. package/get-started.md +7 -0
  40. 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":"AAEA,sFAAsF;AACtF,qFAAqF;AACrF,wFAAwF;AACxF,oFAAoF;AACpF,uFAAuF;AACvF,6BAA6B;AAC7B,EAAE;AACF,kFAAkF;AAClF,qFAAqF;AACrF,6BAA6B;AAE7B,MAAM,UAAU,iBAAiB,CAAC,cAAsB;IACtD,OAAO,UAAU,cAAc,gBAAgB,CAAA;AACjD,CAAC;AAED,uFAAuF;AACvF,uDAAuD;AACvD,MAAM,UAAU,cAAc,CAAC,cAAsB;IACnD,OAAO,kDAAkD,cAAc,GAAG,CAAA;AAC5E,CAAC;AAED,iFAAiF;AACjF,4DAA4D;AAC5D,MAAM,UAAU,qBAAqB,CAAC,IAA+B,EAAE,cAAsB;IAC3F,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC,CAAA;AAC9D,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,KAKtC;IACC,MAAM,QAAQ,GAAG,KAAK,CAAC,cAAc;QACnC,CAAC,CAAC,UAAU,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,0BAA0B,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,WAAW,KAAK,CAAC,cAAc,GAAG;QACjI,CAAC,CAAC,EAAE,CAAA;IACN,OAAO;QACL,0BAA0B;QAC1B,EAAE;QACF,IAAI,KAAK,CAAC,cAAc,UAAU,QAAQ,4LAA4L;QACtO,EAAE;QACF,6BAA6B,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,mGAAmG;QACzJ,EAAE;QACF,0CAA0C;KAC3C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACd,CAAC;AAUD,iFAAiF;AACjF,4DAA4D;AAC5D,EAAE;AACF,iFAAiF;AACjF,gFAAgF;AAChF,iFAAiF;AACjF,8EAA8E;AAC9E,uCAAuC;AACvC,EAAE;AACF,4EAA4E;AAC5E,iFAAiF;AACjF,2EAA2E;AAC3E,yCAAyC;AACzC,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,OAAgB,EAChB,KAAa,EACb,IAAY,EACZ,cAAsB;IAEtB,IAAI,CAAC;QACH,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC,CAAA;QAC3F,OAAO,IAAI,CAAC,MAAM,KAAK,IAAI,CAAA;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC;AAED,mFAAmF;AACnF,mFAAmF;AACnF,oFAAoF;AACpF,qBAAqB;AACrB,EAAE;AACF,iFAAiF;AACjF,iFAAiF;AACjF,8EAA8E;AAC9E,2DAA2D;AAC3D,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,OAAgB,EAChB,KAAa,EACb,IAAY,EACZ,cAAsB,EACtB,cAA6B;IAE7B,MAAM,MAAM,GAAG,iBAAiB,CAAC,cAAc,CAAC,CAAA;IAChD,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;QACzD,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,MAAM,EAAE;KACvD,CAAC,CAAA;IAEF,OAAO,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,EAA6B,EAAE;QAC/E,IAAI,CAAC,qBAAqB,CAAC,SAAS,CAAC,IAAI,EAAE,cAAc,CAAC,EAAE,CAAC;YAC3D,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAA;QACjE,CAAC;QACD,IAAI,CAAC;YACH,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC;gBACtC,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,CAAC,MAAM;gBAC3C,IAAI,EAAE,sBAAsB,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,cAAc,EAAE,CAAC;aAC9E,CAAC,CAAA;YACF,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAA;YAChG,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAA;QACzD,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,wEAAwE;YACxE,6CAA6C;YAC7C,OAAO;gBACL,QAAQ,EAAE,SAAS,CAAC,MAAM;gBAC1B,MAAM,EAAE,QAAQ;gBAChB,MAAM,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;aACzD,CAAA;QACH,CAAC;IACH,CAAC,CAAC,CAAC,CAAA;AACL,CAAC"}
@@ -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,wEAuF/C"}
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"}
@@ -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":"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"}
1
+ {"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../src/github/webhook.ts"],"names":[],"mappings":"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"}
@@ -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
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/lib/runner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/lib/runner.ts"],"names":[],"mappings":"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"}
@@ -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 fix step.
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 fix step respects the same routing intent as the review step.
212
- // 'auto' mirrors resolveReviewer's auto path (config-enabled check, codex-first)
213
- // without async auth calls. null disables the fallback entirely.
214
- // Exported so callers can detect when the fallback was applied (e.g. for logging).
215
- export function resolveFixVendor(stepReviewer, origin, config, fallback) {
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
- humanFallback = config.vendors.codex.enabled ? 'codex' : config.vendors.claude.enabled ? 'claude' : null;
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 fixBranch = `fix/cr-${prNumber}-review-issues`;
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
- `Auto-fix by crosscheck for CR issues found in #${prNumber}.`,
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
- const vendor = resolveReviewer(step.reviewer, origin, config, ctx.smartSwitchFallback);
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: {