@humanbased/crosscheck 1.3.1-beta.7 → 1.6.0-beta.11

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 (165) hide show
  1. package/README.md +16 -1
  2. package/assets/demo-block-to-approve.gif +0 -0
  3. package/assets/demo-block-to-approve.mp4 +0 -0
  4. package/dist/__tests__/base-ref-recovery.test.d.ts +2 -0
  5. package/dist/__tests__/base-ref-recovery.test.d.ts.map +1 -0
  6. package/dist/__tests__/base-ref-recovery.test.js +102 -0
  7. package/dist/__tests__/base-ref-recovery.test.js.map +1 -0
  8. package/dist/__tests__/board.test.js +510 -20
  9. package/dist/__tests__/board.test.js.map +1 -1
  10. package/dist/__tests__/codex-env.test.js +4 -0
  11. package/dist/__tests__/codex-env.test.js.map +1 -1
  12. package/dist/__tests__/credential-free-origin.test.js +134 -2
  13. package/dist/__tests__/credential-free-origin.test.js.map +1 -1
  14. package/dist/__tests__/demo-arc.test.d.ts +2 -0
  15. package/dist/__tests__/demo-arc.test.d.ts.map +1 -0
  16. package/dist/__tests__/demo-arc.test.js +73 -0
  17. package/dist/__tests__/demo-arc.test.js.map +1 -0
  18. package/dist/__tests__/doc-only-verdict.test.d.ts +2 -0
  19. package/dist/__tests__/doc-only-verdict.test.d.ts.map +1 -0
  20. package/dist/__tests__/doc-only-verdict.test.js +79 -0
  21. package/dist/__tests__/doc-only-verdict.test.js.map +1 -0
  22. package/dist/__tests__/error-classification.test.js +14 -0
  23. package/dist/__tests__/error-classification.test.js.map +1 -1
  24. package/dist/__tests__/fix-requires-recheck.test.d.ts +2 -0
  25. package/dist/__tests__/fix-requires-recheck.test.d.ts.map +1 -0
  26. package/dist/__tests__/fix-requires-recheck.test.js +56 -0
  27. package/dist/__tests__/fix-requires-recheck.test.js.map +1 -0
  28. package/dist/__tests__/human-feedback.test.d.ts +2 -0
  29. package/dist/__tests__/human-feedback.test.d.ts.map +1 -0
  30. package/dist/__tests__/human-feedback.test.js +132 -0
  31. package/dist/__tests__/human-feedback.test.js.map +1 -0
  32. package/dist/__tests__/inconclusive-review.test.d.ts +2 -0
  33. package/dist/__tests__/inconclusive-review.test.d.ts.map +1 -0
  34. package/dist/__tests__/inconclusive-review.test.js +110 -0
  35. package/dist/__tests__/inconclusive-review.test.js.map +1 -0
  36. package/dist/__tests__/lock-ownership.test.d.ts +2 -0
  37. package/dist/__tests__/lock-ownership.test.d.ts.map +1 -0
  38. package/dist/__tests__/lock-ownership.test.js +167 -0
  39. package/dist/__tests__/lock-ownership.test.js.map +1 -0
  40. package/dist/__tests__/merge-gate.test.d.ts +2 -0
  41. package/dist/__tests__/merge-gate.test.d.ts.map +1 -0
  42. package/dist/__tests__/merge-gate.test.js +220 -0
  43. package/dist/__tests__/merge-gate.test.js.map +1 -0
  44. package/dist/__tests__/no-verdict.test.d.ts +2 -0
  45. package/dist/__tests__/no-verdict.test.d.ts.map +1 -0
  46. package/dist/__tests__/no-verdict.test.js +415 -0
  47. package/dist/__tests__/no-verdict.test.js.map +1 -0
  48. package/dist/__tests__/pr-spec.test.js +37 -0
  49. package/dist/__tests__/pr-spec.test.js.map +1 -1
  50. package/dist/__tests__/run.test.js +50 -1
  51. package/dist/__tests__/run.test.js.map +1 -1
  52. package/dist/__tests__/runner.test.js +82 -1
  53. package/dist/__tests__/runner.test.js.map +1 -1
  54. package/dist/__tests__/standing-verdict-history.test.d.ts +2 -0
  55. package/dist/__tests__/standing-verdict-history.test.d.ts.map +1 -0
  56. package/dist/__tests__/standing-verdict-history.test.js +109 -0
  57. package/dist/__tests__/standing-verdict-history.test.js.map +1 -0
  58. package/dist/__tests__/step-plan.test.d.ts +2 -0
  59. package/dist/__tests__/step-plan.test.d.ts.map +1 -0
  60. package/dist/__tests__/step-plan.test.js +129 -0
  61. package/dist/__tests__/step-plan.test.js.map +1 -0
  62. package/dist/cli.js +14 -1
  63. package/dist/cli.js.map +1 -1
  64. package/dist/commands/merge.d.ts +11 -0
  65. package/dist/commands/merge.d.ts.map +1 -0
  66. package/dist/commands/merge.js +188 -0
  67. package/dist/commands/merge.js.map +1 -0
  68. package/dist/commands/review.d.ts +3 -1
  69. package/dist/commands/review.d.ts.map +1 -1
  70. package/dist/commands/review.js +117 -9
  71. package/dist/commands/review.js.map +1 -1
  72. package/dist/commands/run.d.ts +53 -0
  73. package/dist/commands/run.d.ts.map +1 -1
  74. package/dist/commands/run.js +218 -36
  75. package/dist/commands/run.js.map +1 -1
  76. package/dist/commands/watch.d.ts.map +1 -1
  77. package/dist/commands/watch.js +41 -26
  78. package/dist/commands/watch.js.map +1 -1
  79. package/dist/config/loader.d.ts +4 -0
  80. package/dist/config/loader.d.ts.map +1 -1
  81. package/dist/config/loader.js +8 -0
  82. package/dist/config/loader.js.map +1 -1
  83. package/dist/config/review-strategy.json +5 -5
  84. package/dist/github/client.d.ts +18 -1
  85. package/dist/github/client.d.ts.map +1 -1
  86. package/dist/github/client.js +19 -3
  87. package/dist/github/client.js.map +1 -1
  88. package/dist/github/merge.d.ts +28 -0
  89. package/dist/github/merge.d.ts.map +1 -1
  90. package/dist/github/merge.js +41 -0
  91. package/dist/github/merge.js.map +1 -1
  92. package/dist/github/review-status.d.ts +33 -0
  93. package/dist/github/review-status.d.ts.map +1 -1
  94. package/dist/github/review-status.js +94 -9
  95. package/dist/github/review-status.js.map +1 -1
  96. package/dist/github/webhook.d.ts +1 -0
  97. package/dist/github/webhook.d.ts.map +1 -1
  98. package/dist/github/webhook.js.map +1 -1
  99. package/dist/lib/board.d.ts +60 -4
  100. package/dist/lib/board.d.ts.map +1 -1
  101. package/dist/lib/board.js +357 -65
  102. package/dist/lib/board.js.map +1 -1
  103. package/dist/lib/clone.d.ts +65 -1
  104. package/dist/lib/clone.d.ts.map +1 -1
  105. package/dist/lib/clone.js +252 -16
  106. package/dist/lib/clone.js.map +1 -1
  107. package/dist/lib/human-feedback.d.ts +9 -0
  108. package/dist/lib/human-feedback.d.ts.map +1 -0
  109. package/dist/lib/human-feedback.js +83 -0
  110. package/dist/lib/human-feedback.js.map +1 -0
  111. package/dist/lib/logger.d.ts +1 -1
  112. package/dist/lib/logger.d.ts.map +1 -1
  113. package/dist/lib/logger.js +6 -0
  114. package/dist/lib/logger.js.map +1 -1
  115. package/dist/lib/merge-gate.d.ts +70 -0
  116. package/dist/lib/merge-gate.d.ts.map +1 -0
  117. package/dist/lib/merge-gate.js +138 -0
  118. package/dist/lib/merge-gate.js.map +1 -0
  119. package/dist/lib/no-verdict.d.ts +104 -0
  120. package/dist/lib/no-verdict.d.ts.map +1 -0
  121. package/dist/lib/no-verdict.js +262 -0
  122. package/dist/lib/no-verdict.js.map +1 -0
  123. package/dist/lib/pr-lock.d.ts +37 -1
  124. package/dist/lib/pr-lock.d.ts.map +1 -1
  125. package/dist/lib/pr-lock.js +176 -29
  126. package/dist/lib/pr-lock.js.map +1 -1
  127. package/dist/lib/pr-spec.d.ts +7 -0
  128. package/dist/lib/pr-spec.d.ts.map +1 -1
  129. package/dist/lib/pr-spec.js +12 -1
  130. package/dist/lib/pr-spec.js.map +1 -1
  131. package/dist/lib/pr-workflow-state.d.ts +39 -0
  132. package/dist/lib/pr-workflow-state.d.ts.map +1 -1
  133. package/dist/lib/pr-workflow-state.js +73 -1
  134. package/dist/lib/pr-workflow-state.js.map +1 -1
  135. package/dist/lib/review-strategy.d.ts +14 -0
  136. package/dist/lib/review-strategy.d.ts.map +1 -1
  137. package/dist/lib/review-strategy.js +32 -1
  138. package/dist/lib/review-strategy.js.map +1 -1
  139. package/dist/lib/runner.d.ts +51 -2
  140. package/dist/lib/runner.d.ts.map +1 -1
  141. package/dist/lib/runner.js +211 -105
  142. package/dist/lib/runner.js.map +1 -1
  143. package/dist/lib/tips.d.ts.map +1 -1
  144. package/dist/lib/tips.js +1 -0
  145. package/dist/lib/tips.js.map +1 -1
  146. package/dist/lib/verdict.d.ts +30 -1
  147. package/dist/lib/verdict.d.ts.map +1 -1
  148. package/dist/lib/verdict.js +94 -9
  149. package/dist/lib/verdict.js.map +1 -1
  150. package/dist/reviewers/codex-env.d.ts.map +1 -1
  151. package/dist/reviewers/codex-env.js +2 -0
  152. package/dist/reviewers/codex-env.js.map +1 -1
  153. package/dist/reviewers/codex.d.ts.map +1 -1
  154. package/dist/reviewers/codex.js +3 -1
  155. package/dist/reviewers/codex.js.map +1 -1
  156. package/dist/reviewers/fix.d.ts +2 -2
  157. package/dist/reviewers/fix.d.ts.map +1 -1
  158. package/dist/reviewers/fix.js +10 -4
  159. package/dist/reviewers/fix.js.map +1 -1
  160. package/docs/growth/launch/README.md +59 -0
  161. package/docs/growth/launch/essay-stop-letting-one-agent-review-its-own-code.md +118 -0
  162. package/docs/growth/launch/posts.md +175 -0
  163. package/docs/trust.md +16 -1
  164. package/get-started.md +17 -0
  165. package/package.json +11 -3
@@ -0,0 +1,262 @@
1
+ import { fmtTokensRaw } from './board.js';
2
+ import { shaCovers } from './pr-workflow-state.js';
3
+ /**
4
+ * The verdict that still governs the PR: the newest review or recheck that
5
+ * actually carries one.
6
+ *
7
+ * A review can run, post its findings, and record no verdict at all — that is
8
+ * the `ran_no_verdict` disposition, and it says nothing about the `BLOCK` that
9
+ * was standing before it. Taking the newest review-or-recheck record
10
+ * unconditionally would let one malformed review erase a verdict that still
11
+ * gates the PR, and the report would go quiet about it in exactly the state
12
+ * that most needs saying out loud.
13
+ */
14
+ export function selectStandingVerdict(history) {
15
+ const judged = history.filter(r => isVerdictStep(r.type) && r.verdict).at(-1);
16
+ if (!judged?.verdict)
17
+ return undefined;
18
+ return { verdict: judged.verdict, ...(judged.sha !== undefined && { sha: judged.sha }) };
19
+ }
20
+ function isVerdictStep(type) {
21
+ return type === 'review' || type === 'recheck';
22
+ }
23
+ /**
24
+ * Classifies every verdict-capable step in the workflow. When none is
25
+ * configured, returns a single synthetic `review` entry — the absence is itself
26
+ * the answer, and callers should not have to special-case an empty array.
27
+ */
28
+ export function describeVerdictSteps(workflowSteps, outcomes) {
29
+ const steps = workflowSteps.filter(s => isVerdictStep(s.type));
30
+ if (steps.length === 0)
31
+ return [{ step: 'review', disposition: 'not_configured' }];
32
+ const ran = new Set(outcomes?.ran ?? []);
33
+ const skipped = new Map((outcomes?.skipped ?? []).map(s => [s.step, s.reason]));
34
+ return steps.map(({ name }) => {
35
+ if (ran.has(name)) {
36
+ // `verdict: null` means the step ran and its output had no parseable
37
+ // verdict. A missing key means the step recorded no verdict field at all,
38
+ // which for a dispatched verdict step means it ran normally.
39
+ const detail = outcomes?.ranDetail?.[name];
40
+ return detail && 'verdict' in detail && detail.verdict === null
41
+ ? { step: name, disposition: 'ran_no_verdict' }
42
+ : { step: name, disposition: 'ran' };
43
+ }
44
+ const reason = skipped.get(name);
45
+ if (reason !== undefined)
46
+ return { step: name, disposition: 'skipped', reason };
47
+ return { step: name, disposition: 'not_dispatched' };
48
+ });
49
+ }
50
+ // Guards that stop the run deliberately. Re-running the same step walks into the
51
+ // same cap, so these get an explanation instead of a command.
52
+ const DELIBERATE_STOPS = new Set(['max_rounds', 'commit_limit_reached', 'vendor_limit']);
53
+ // Reasons that mean no vendor was available to do the work — a config gap, not
54
+ // a per-run accident.
55
+ const ROUTING_GAPS = new Set(['no_vendor', 'no_reviewer', 'unsupported_vendor', 'human_origin']);
56
+ function describeSkipReason(reason, step, prUrl) {
57
+ if (ROUTING_GAPS.has(reason)) {
58
+ return {
59
+ next: [
60
+ { text: 'set routing.fallback_reviewer in ~/.crosscheck/config.yml', recommended: true },
61
+ { text: `ck run ${prUrl} --reviewer <claude|codex>`, recommended: false },
62
+ ],
63
+ notes: ['No vendor was available to judge this PR — nothing about the code caused this.'],
64
+ };
65
+ }
66
+ if (DELIBERATE_STOPS.has(reason)) {
67
+ return {
68
+ next: [],
69
+ notes: [`${step} stopped on \`${reason}\` — crosscheck gave up on purpose, so this needs a human.`],
70
+ };
71
+ }
72
+ if (reason === 'no_change_since_review') {
73
+ return {
74
+ next: [{ text: `ck run ${prUrl} --steps review`, recommended: false }],
75
+ notes: ['Nothing has changed since the last review, so there was nothing new to judge.'],
76
+ };
77
+ }
78
+ return { next: [{ text: `ck run ${prUrl} --steps ${step}`, recommended: true }], notes: [] };
79
+ }
80
+ function shortSha(sha) {
81
+ return sha === undefined ? undefined : sha.slice(0, 7);
82
+ }
83
+ function formatRanDetail(detail) {
84
+ const parts = [];
85
+ if (detail.vendor)
86
+ parts.push(detail.vendor);
87
+ if (detail.tokensUsed !== undefined)
88
+ parts.push(`${fmtTokensRaw(detail.tokensUsed)} tokens`);
89
+ if (detail.appliedCount === 0)
90
+ parts.push('no changes applied');
91
+ else if (detail.appliedCount !== undefined)
92
+ parts.push(`${detail.appliedCount} change${detail.appliedCount === 1 ? '' : 's'} applied`);
93
+ if (detail.verdict === null)
94
+ parts.push('no verdict parsed');
95
+ return parts.join(' · ');
96
+ }
97
+ /**
98
+ * Explains an empty verdict and says what to do about it. Pure — every input it
99
+ * needs is already in memory at the reporting site.
100
+ */
101
+ export function buildNoVerdictReport(input) {
102
+ const { workflowSteps, outcomes, prUrl } = input;
103
+ const verdictSteps = describeVerdictSteps(workflowSteps, outcomes);
104
+ const ran = (outcomes?.ran ?? []).map(step => ({
105
+ step,
106
+ detail: outcomes?.ranDetail?.[step] ? formatRanDetail(outcomes.ranDetail[step]) : '',
107
+ }));
108
+ const why = [];
109
+ let next = [];
110
+ const notes = [];
111
+ // Precedence: a step that ran and produced nothing outranks one that skipped,
112
+ // which outranks one that never ran. The most specific thing that happened is
113
+ // the thing worth reporting.
114
+ // Fix steps by type: conflict-resolve records `applied_count: 0` too, and a
115
+ // branch with no conflicts to resolve says nothing about a review's findings.
116
+ const fixStepNames = new Set(workflowSteps.filter(s => s.type === 'fix').map(s => s.name));
117
+ const noopFixStep = (outcomes?.ran ?? []).find(step => fixStepNames.has(step) && outcomes?.ranDetail?.[step]?.appliedCount === 0);
118
+ // True when every step this run actually ran was a fix that changed nothing —
119
+ // i.e. the run's whole output was nothing at all.
120
+ const scopedFixDidNothing = noopFixStep !== undefined
121
+ && (outcomes?.ran ?? []).every(step => outcomes?.ranDetail?.[step]?.appliedCount === 0)
122
+ ? noopFixStep
123
+ : undefined;
124
+ const ranNoVerdict = verdictSteps.find(v => v.disposition === 'ran_no_verdict');
125
+ const skippedStep = verdictSteps.find(v => v.disposition === 'skipped');
126
+ const allNotConfigured = verdictSteps.every(v => v.disposition === 'not_configured');
127
+ const allNotDispatched = verdictSteps.every(v => v.disposition === 'not_dispatched');
128
+ let cause;
129
+ let expected;
130
+ if (input.strategySkipped) {
131
+ cause = 'strategy_skip';
132
+ expected = true;
133
+ why.push(`PR classified ${input.strategySkipped} — the strategy skipped it, so no step produced a verdict.`);
134
+ }
135
+ else if (allNotConfigured) {
136
+ cause = 'not_configured';
137
+ expected = true;
138
+ why.push('This workflow has no review or recheck step, so it produces no verdict by design.');
139
+ }
140
+ else if (ranNoVerdict) {
141
+ cause = 'ran_no_verdict';
142
+ expected = false;
143
+ why.push(`${ranNoVerdict.step} ran and posted, but its output carried no parseable VERDICT: line.`);
144
+ why.push('The findings are in the comment; the workflow has no verdict to gate on.');
145
+ next = [{ text: `ck run ${prUrl} --steps ${ranNoVerdict.step}`, recommended: true }];
146
+ notes.push('Malformed vendor output is usually transient — a re-run normally settles it.');
147
+ }
148
+ else if (skippedStep) {
149
+ cause = 'skipped';
150
+ expected = false;
151
+ why.push('Only review and recheck produce verdicts, and neither judged this commit.');
152
+ const advice = describeSkipReason(skippedStep.reason ?? 'unknown', skippedStep.step, prUrl);
153
+ next = advice.next;
154
+ notes.push(...advice.notes);
155
+ }
156
+ else if (scopedFixDidNothing) {
157
+ // `ck fix` asks for exactly one step, so a missing verdict is expected and the
158
+ // run would otherwise close green — the same green a fix that rewrote ten files
159
+ // prints. The absent verdict is indeed expected; the absent *change* is not,
160
+ // and it is the only thing the invocation was for.
161
+ cause = 'fix_noop';
162
+ expected = false;
163
+ why.push(`This run was scoped to ${scopedFixDidNothing}, which ran and applied no changes.`);
164
+ why.push('Nothing was written to the PR — the review it read may carry no actionable findings, or they may already be resolved in HEAD.');
165
+ next = [{ text: `ck run ${prUrl} --steps review`, recommended: true }];
166
+ notes.push('A review that could not resolve the diff produces a findings-free body that no fix can act on — check the review comment the fix step read.');
167
+ }
168
+ else {
169
+ cause = 'not_dispatched';
170
+ // A run that asked for one step got what it asked for. An unscoped run that
171
+ // dispatched no verdict step did not.
172
+ expected = allNotDispatched && input.stepsExplicitlyScoped === true;
173
+ why.push(expected
174
+ ? 'This run was scoped to steps that do not produce verdicts.'
175
+ : 'Only review and recheck produce verdicts, and neither was dispatched.');
176
+ if (!expected) {
177
+ next = [{ text: `ck run ${prUrl} --steps review`, recommended: true }];
178
+ }
179
+ }
180
+ // Context that holds regardless of cause: what the PR still says, and whether
181
+ // it still describes the code under review.
182
+ if (!expected && input.standingVerdict?.verdict) {
183
+ const sha = shortSha(input.standingVerdict.sha);
184
+ const head = shortSha(input.headSha);
185
+ // shaCovers is the codebase's one definition of "the same commit" — it
186
+ // handles the short/full forms annotations carry either of. A verdict whose
187
+ // SHA is unknown proves nothing about HEAD, so it is reported without the
188
+ // staleness claim rather than guessed at.
189
+ const covers = input.headSha !== undefined && shaCovers(input.standingVerdict.sha, input.headSha);
190
+ why.push(sha !== undefined && head !== undefined && !covers
191
+ ? `The PR's standing verdict is ${input.standingVerdict.verdict} on ${sha}, which is not HEAD (${head}).`
192
+ : `The PR's standing verdict is still ${input.standingVerdict.verdict}${sha ? ` on ${sha}` : ''}.`);
193
+ }
194
+ // A fix that applied nothing is the strongest hint available that the findings
195
+ // are already resolved — which is precisely what a recheck confirms. Not repeated
196
+ // under `fix_noop`, where it is already the headline.
197
+ if (!expected && noopFixStep && cause !== 'ran_no_verdict' && cause !== 'fix_noop') {
198
+ why.push(`The ${noopFixStep} step applied no changes — the findings may already be resolved in HEAD.`);
199
+ }
200
+ // Offer the other verdict step as an alternative: recheck judges against the
201
+ // original findings, review sets them aside. Not for malformed output, where
202
+ // the action is to re-run the step that misbehaved and a second command only
203
+ // blurs that.
204
+ const recommendedStep = next.find(n => n.recommended)?.text.match(/--steps (\S+)/)?.[1];
205
+ const alternative = verdictSteps.find(v => v.step !== recommendedStep && v.disposition !== 'not_configured');
206
+ if (cause !== 'ran_no_verdict' && recommendedStep && alternative
207
+ && !next.some(n => n.text.includes(`--steps ${alternative.step}`))) {
208
+ next.push({ text: `ck run ${prUrl} --steps ${alternative.step}`, recommended: false });
209
+ }
210
+ // Skipped steps of any type bear on the outcome, so all of them are listed. A
211
+ // step that was never dispatched earns a line only when it produces verdicts
212
+ // AND its absence is the problem: a conflict-resolve that sat out explains
213
+ // nothing about a missing verdict, and on a run that was never going to be
214
+ // judged, `why` has already said so in a sentence.
215
+ const order = new Map(workflowSteps.map((s, i) => [s.name, i]));
216
+ const didNotRun = [
217
+ ...(outcomes?.skipped ?? []).map(s => ({ step: s.step, note: `skipped — ${s.reason}` })),
218
+ ...(expected ? [] : verdictSteps
219
+ .filter(v => v.disposition === 'not_dispatched')
220
+ .map(v => ({ step: v.step, note: 'not dispatched' }))),
221
+ ].sort((a, b) => (order.get(a.step) ?? Infinity) - (order.get(b.step) ?? Infinity));
222
+ return { expected, cause, verdictSteps, ran, didNotRun, why, next, notes };
223
+ }
224
+ function pad(entries) {
225
+ return entries.reduce((w, e) => Math.max(w, e.step.length), 0);
226
+ }
227
+ /** Renders the report as plain lines, unindented and uncoloured. The caller
228
+ * owns presentation; this owns what is said and in what order. */
229
+ export function renderNoVerdictReport(report) {
230
+ const lines = ['no verdict this run'];
231
+ const width = Math.max(pad(report.ran), pad(report.didNotRun));
232
+ if (report.ran.length > 0) {
233
+ lines.push('', 'ran');
234
+ for (const { step, detail } of report.ran) {
235
+ lines.push(detail ? ` ${step.padEnd(width)} ${detail}` : ` ${step}`);
236
+ }
237
+ }
238
+ if (report.didNotRun.length > 0) {
239
+ lines.push('', 'did not run');
240
+ for (const { step, note } of report.didNotRun) {
241
+ lines.push(` ${step.padEnd(width)} ${note}`);
242
+ }
243
+ }
244
+ if (report.why.length > 0) {
245
+ lines.push('', 'why');
246
+ for (const line of report.why)
247
+ lines.push(` ${line}`);
248
+ }
249
+ if (report.next.length > 0) {
250
+ lines.push('', 'next');
251
+ for (const { text, recommended } of report.next) {
252
+ lines.push(recommended ? `→ ${text}` : ` ${text}`);
253
+ }
254
+ }
255
+ if (report.notes.length > 0) {
256
+ lines.push('');
257
+ for (const note of report.notes)
258
+ lines.push(` ${note}`);
259
+ }
260
+ return lines;
261
+ }
262
+ //# sourceMappingURL=no-verdict.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-verdict.js","sourceRoot":"","sources":["../../src/lib/no-verdict.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,40 @@
1
+ export declare const INSTANCE_ID: string;
2
+ /** What a holder writes into its lock file so other instances can reason about it. */
3
+ interface LockRecord {
4
+ instance: string;
5
+ host: string;
6
+ pid: number;
7
+ /** Head SHA being worked on. Diagnostic — the lock itself is per-PR. */
8
+ sha?: string;
9
+ startedAt: number;
10
+ heartbeatAt: number;
11
+ }
1
12
  export declare function handleLockSignal(signal: NodeJS.Signals): void;
2
- export declare function acquirePRLock(owner: string, repo: string, pr: number, _sha?: string): boolean;
13
+ /** Why a lock held by someone else was taken over. Exported for tests and logging. */
14
+ export type StealReason = 'holder_dead' | 'heartbeat_stale' | 'legacy_lock_expired';
15
+ export declare function evaluateExistingLock(record: LockRecord | null, mtimeMs: number, now: number): {
16
+ stealable: true;
17
+ reason: StealReason;
18
+ } | {
19
+ stealable: false;
20
+ };
21
+ /**
22
+ * Take the local lock for a PR. Returns false when another live process holds it.
23
+ *
24
+ * The lock records who holds it, so a peer can tell a live holder from a crashed
25
+ * one instead of guessing from a fixed timeout.
26
+ */
27
+ export declare function acquirePRLock(owner: string, repo: string, pr: number, sha?: string): boolean;
28
+ /** Describe the current holder of a PR's lock, for diagnostics. Null when free. */
29
+ export declare function inspectPRLock(owner: string, repo: string, pr: number): LockRecord | null;
30
+ /**
31
+ * Release the local lock — but only if this process still owns it.
32
+ *
33
+ * The ownership check is what stops a late release from clearing a lock that a
34
+ * peer has since taken over: without it, an instance whose lock was stolen as
35
+ * stale would delete the new holder's lock on its way out, and a third instance
36
+ * would walk straight in on a PR two others were already working.
37
+ */
3
38
  export declare function releasePRLock(owner: string, repo: string, pr: number, _sha?: string): void;
39
+ export {};
4
40
  //# sourceMappingURL=pr-lock.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pr-lock.d.ts","sourceRoot":"","sources":["../../src/lib/pr-lock.ts"],"names":[],"mappings":"AAwBA,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAS7D;AAsBD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAsB7F;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAI1F"}
1
+ {"version":3,"file":"pr-lock.d.ts","sourceRoot":"","sources":["../../src/lib/pr-lock.ts"],"names":[],"mappings":"AAwBA,eAAO,MAAM,WAAW,QAAmE,CAAA;AAE3F,sFAAsF;AACtF,UAAU,UAAU;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,wEAAwE;IACxE,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;CACpB;AAmBD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAU7D;AA8CD,sFAAsF;AACtF,MAAM,MAAM,WAAW,GAAG,aAAa,GAAG,iBAAiB,GAAG,qBAAqB,CAAA;AAMnF,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,UAAU,GAAG,IAAI,EACzB,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,GACV;IAAE,SAAS,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,WAAW,CAAA;CAAE,GAAG;IAAE,SAAS,EAAE,KAAK,CAAA;CAAE,CAgBjE;AA6BD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CA2C5F;AAED,mFAAmF;AACnF,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAExF;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAW1F"}
@@ -1,10 +1,27 @@
1
- import { openSync, closeSync, rmSync, mkdirSync, statSync } from 'fs';
1
+ import { openSync, closeSync, writeSync, readFileSync, rmSync, mkdirSync, statSync, utimesSync } from 'fs';
2
+ import { hostname } from 'os';
3
+ import { randomBytes } from 'crypto';
2
4
  import { homedir } from 'os';
3
5
  import { join } from 'path';
4
6
  const LOCK_DIR = join(homedir(), '.crosscheck', 'locks');
5
- const STALE_MS = 20 * 60 * 1000;
6
- // Track active lock paths so signal handlers can clean them up
7
- const activeLocks = new Set();
7
+ // A lock is abandoned when its holder has not heartbeated within this window.
8
+ // The holder refreshes every HEARTBEAT_INTERVAL_MS, so this only trips on a
9
+ // process that died without running its cleanup — a SIGKILL, an OOM, a power cut.
10
+ //
11
+ // It used to be 20 minutes measured from the lock file's CREATION time, which is
12
+ // the wrong clock: a review that legitimately ran longer than 20 minutes (routine
13
+ // on a large PR — the thorough tier alone allows 1200s per reviewer call, before
14
+ // clone, fix and recheck) had its lock declared stale and stolen while it was
15
+ // still running. Measuring from the last heartbeat means a live review is never
16
+ // stale no matter how long it takes, which in turn lets this window be short.
17
+ const STALE_MS = 5 * 60 * 1000;
18
+ const HEARTBEAT_INTERVAL_MS = 60 * 1000;
19
+ // Identifies this process for the lifetime of the process. The random suffix
20
+ // matters: pids are recycled, so host+pid alone can make a dead holder look alive
21
+ // once the OS hands its pid to something else.
22
+ export const INSTANCE_ID = `${hostname()}:${process.pid}:${randomBytes(4).toString('hex')}`;
23
+ // Locks this process holds, by path, with the heartbeat timer keeping each fresh.
24
+ const heldLocks = new Map();
8
25
  let signalHandlersRegistered = false;
9
26
  // Exported for unit testing. Cleans up local lock files on signal, then —
10
27
  // when no other listener will drive termination — restores the default
@@ -20,13 +37,14 @@ let signalHandlersRegistered = false;
20
37
  // watch/serve graceful shutdown handlers referenced in the original
21
38
  // comment), we let them drive termination via their own finally blocks.
22
39
  export function handleLockSignal(signal) {
23
- for (const p of activeLocks) {
40
+ for (const [path, held] of heldLocks) {
41
+ clearInterval(held.timer);
24
42
  try {
25
- rmSync(p);
43
+ rmSync(path);
26
44
  }
27
45
  catch { /* ignore */ }
28
46
  }
29
- activeLocks.clear();
47
+ heldLocks.clear();
30
48
  if (process.listenerCount(signal) <= 1) {
31
49
  process.removeListener(signal, handleLockSignal);
32
50
  process.kill(process.pid, signal);
@@ -42,47 +60,176 @@ function registerSignalCleanup() {
42
60
  function lockPath(owner, repo, pr) {
43
61
  return join(LOCK_DIR, `${owner}-${repo}-${pr}.lock`);
44
62
  }
45
- function isStale(path) {
63
+ function readRecord(path) {
46
64
  try {
47
- const age = Date.now() - statSync(path).mtimeMs;
48
- return age > STALE_MS;
65
+ const raw = readFileSync(path, 'utf8').trim();
66
+ // A lock written by an older crosscheck is an empty file. It carries no owner
67
+ // and no heartbeat, so the only honest reading is the file's mtime.
68
+ if (raw === '')
69
+ return null;
70
+ const parsed = JSON.parse(raw);
71
+ if (typeof parsed.instance !== 'string' || typeof parsed.pid !== 'number')
72
+ return null;
73
+ return {
74
+ instance: parsed.instance,
75
+ host: typeof parsed.host === 'string' ? parsed.host : '',
76
+ pid: parsed.pid,
77
+ ...(typeof parsed.sha === 'string' && { sha: parsed.sha }),
78
+ startedAt: typeof parsed.startedAt === 'number' ? parsed.startedAt : 0,
79
+ heartbeatAt: typeof parsed.heartbeatAt === 'number' ? parsed.heartbeatAt : 0,
80
+ };
49
81
  }
50
82
  catch {
51
- return false;
83
+ return null;
52
84
  }
53
85
  }
54
- export function acquirePRLock(owner, repo, pr, _sha) {
55
- mkdirSync(LOCK_DIR, { recursive: true });
56
- registerSignalCleanup();
57
- const path = lockPath(owner, repo, pr);
86
+ // Whether a process is still running on THIS host. Cross-host liveness is
87
+ // unknowable from here, which is what the heartbeat is for.
88
+ function pidAlive(pid) {
58
89
  try {
59
- const fd = openSync(path, 'wx');
60
- closeSync(fd);
61
- activeLocks.add(path);
90
+ process.kill(pid, 0);
62
91
  return true;
63
92
  }
64
93
  catch (e) {
65
- if (e.code !== 'EEXIST')
66
- throw e;
67
- if (isStale(path)) {
94
+ // EPERM means the pid exists but belongs to another user — alive, not ours.
95
+ return e.code === 'EPERM';
96
+ }
97
+ }
98
+ // Decide whether an existing lock may be taken over. Ownership is proven two ways,
99
+ // in order of strength: a same-host holder whose pid is gone is definitively dead;
100
+ // otherwise the heartbeat is the only evidence, and only silence beyond STALE_MS
101
+ // counts. A lock held by a live peer is never stolen, however long it has run.
102
+ export function evaluateExistingLock(record, mtimeMs, now) {
103
+ if (record === null) {
104
+ // Legacy empty lock file: mtime is all there is, and it is never refreshed,
105
+ // so fall back to the old generous window rather than stealing from a run
106
+ // that may still be going.
107
+ return now - mtimeMs > 20 * 60 * 1000
108
+ ? { stealable: true, reason: 'legacy_lock_expired' }
109
+ : { stealable: false };
110
+ }
111
+ if (record.host === hostname() && !pidAlive(record.pid)) {
112
+ return { stealable: true, reason: 'holder_dead' };
113
+ }
114
+ if (now - record.heartbeatAt > STALE_MS) {
115
+ return { stealable: true, reason: 'heartbeat_stale' };
116
+ }
117
+ return { stealable: false };
118
+ }
119
+ function writeRecord(path, record) {
120
+ const fd = openSync(path, 'wx');
121
+ try {
122
+ writeSync(fd, JSON.stringify(record));
123
+ }
124
+ finally {
125
+ closeSync(fd);
126
+ }
127
+ }
128
+ function startHeartbeat(path, record) {
129
+ const timer = setInterval(() => {
130
+ const held = heldLocks.get(path);
131
+ if (!held)
132
+ return;
133
+ held.record.heartbeatAt = Date.now();
134
+ try {
135
+ // Rewrite rather than touch: the timestamp inside the file is what a peer
136
+ // on another host reads, and mtime alone would not reach it.
137
+ const fd = openSync(path, 'w');
68
138
  try {
69
- rmSync(path);
139
+ writeSync(fd, JSON.stringify(held.record));
70
140
  }
71
- catch { /* already gone */ }
72
- try {
73
- const fd = openSync(path, 'wx');
141
+ finally {
74
142
  closeSync(fd);
75
- activeLocks.add(path);
76
- return true;
77
143
  }
78
- catch { /* lost the race after clearing stale lock */ }
144
+ utimesSync(path, new Date(), new Date());
79
145
  }
146
+ catch { /* best-effort — a failed beat is covered by the next one */ }
147
+ }, HEARTBEAT_INTERVAL_MS);
148
+ // Never hold the event loop open on the heartbeat alone.
149
+ timer.unref?.();
150
+ heldLocks.set(path, { record, timer });
151
+ }
152
+ /**
153
+ * Take the local lock for a PR. Returns false when another live process holds it.
154
+ *
155
+ * The lock records who holds it, so a peer can tell a live holder from a crashed
156
+ * one instead of guessing from a fixed timeout.
157
+ */
158
+ export function acquirePRLock(owner, repo, pr, sha) {
159
+ mkdirSync(LOCK_DIR, { recursive: true });
160
+ registerSignalCleanup();
161
+ const path = lockPath(owner, repo, pr);
162
+ const now = Date.now();
163
+ const record = {
164
+ instance: INSTANCE_ID,
165
+ host: hostname(),
166
+ pid: process.pid,
167
+ ...(sha !== undefined && { sha }),
168
+ startedAt: now,
169
+ heartbeatAt: now,
170
+ };
171
+ try {
172
+ writeRecord(path, record);
173
+ startHeartbeat(path, record);
174
+ return true;
175
+ }
176
+ catch (e) {
177
+ if (e.code !== 'EEXIST')
178
+ throw e;
179
+ }
180
+ // Re-entering our own lock is not a collision: the same process asking twice for
181
+ // the same PR means a second event arrived for it, and the caller's own inFlight
182
+ // guard decides that. Report it as taken so the caller's skip path runs.
183
+ const existing = readRecord(path);
184
+ if (existing?.instance === INSTANCE_ID)
185
+ return false;
186
+ let mtimeMs = 0;
187
+ try {
188
+ mtimeMs = statSync(path).mtimeMs;
189
+ }
190
+ catch {
191
+ return false;
192
+ }
193
+ const verdict = evaluateExistingLock(existing, mtimeMs, now);
194
+ if (!verdict.stealable)
195
+ return false;
196
+ try {
197
+ rmSync(path);
198
+ }
199
+ catch { /* already gone */ }
200
+ try {
201
+ writeRecord(path, record);
202
+ startHeartbeat(path, record);
203
+ return true;
204
+ }
205
+ catch {
206
+ // Lost the race to another instance that cleared the same stale lock.
80
207
  return false;
81
208
  }
82
209
  }
210
+ /** Describe the current holder of a PR's lock, for diagnostics. Null when free. */
211
+ export function inspectPRLock(owner, repo, pr) {
212
+ return readRecord(lockPath(owner, repo, pr));
213
+ }
214
+ /**
215
+ * Release the local lock — but only if this process still owns it.
216
+ *
217
+ * The ownership check is what stops a late release from clearing a lock that a
218
+ * peer has since taken over: without it, an instance whose lock was stolen as
219
+ * stale would delete the new holder's lock on its way out, and a third instance
220
+ * would walk straight in on a PR two others were already working.
221
+ */
83
222
  export function releasePRLock(owner, repo, pr, _sha) {
84
223
  const path = lockPath(owner, repo, pr);
85
- activeLocks.delete(path);
224
+ const held = heldLocks.get(path);
225
+ if (held)
226
+ clearInterval(held.timer);
227
+ heldLocks.delete(path);
228
+ const existing = readRecord(path);
229
+ // An unreadable or legacy record cannot prove another owner. Removing it matches
230
+ // the pre-ownership behaviour and keeps an upgrade from stranding old locks.
231
+ if (existing !== null && existing.instance !== INSTANCE_ID)
232
+ return;
86
233
  try {
87
234
  rmSync(path);
88
235
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pr-lock.js","sourceRoot":"","sources":["../../src/lib/pr-lock.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pr-lock.js","sourceRoot":"","sources":["../../src/lib/pr-lock.ts"],"names":[],"mappings":"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"}
@@ -4,6 +4,13 @@ export interface PRRef {
4
4
  number: number;
5
5
  /** Canonical https URL for the PR. */
6
6
  url: string;
7
+ /**
8
+ * The `#issuecomment-<id>` anchor, when the token carried one. Callers that act
9
+ * on a specific review comment use it to target exactly the comment the user
10
+ * pasted; before this existed the fragment was silently dropped and `ck fix`
11
+ * ran against whatever the latest review happened to be.
12
+ */
13
+ commentId?: number;
7
14
  }
8
15
  export interface ParsePRSpecOptions {
9
16
  /** Hard cap on the number of PRs a single spec may expand to. */
@@ -1 +1 @@
1
- {"version":3,"file":"pr-spec.d.ts","sourceRoot":"","sources":["../../src/lib/pr-spec.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,KAAK;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,sCAAsC;IACtC,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,kBAAkB;IACjC,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,eAAO,MAAM,eAAe,MAAM,CAAA;AAyBlC,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,KAAK,EAAE,CA6CnF"}
1
+ {"version":3,"file":"pr-spec.d.ts","sourceRoot":"","sources":["../../src/lib/pr-spec.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,KAAK;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,sCAAsC;IACtC,GAAG,EAAE,MAAM,CAAA;IACX;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,eAAO,MAAM,eAAe,MAAM,CAAA;AA2BlC,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,KAAK,EAAE,CAsDnF"}