@humanbased/crosscheck 1.3.1-beta.5 → 1.5.0

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 (146) 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__/codex-env.test.js +4 -0
  9. package/dist/__tests__/codex-env.test.js.map +1 -1
  10. package/dist/__tests__/credential-free-origin.test.js +134 -2
  11. package/dist/__tests__/credential-free-origin.test.js.map +1 -1
  12. package/dist/__tests__/demo-arc.test.d.ts +2 -0
  13. package/dist/__tests__/demo-arc.test.d.ts.map +1 -0
  14. package/dist/__tests__/demo-arc.test.js +73 -0
  15. package/dist/__tests__/demo-arc.test.js.map +1 -0
  16. package/dist/__tests__/doc-only-verdict.test.d.ts +2 -0
  17. package/dist/__tests__/doc-only-verdict.test.d.ts.map +1 -0
  18. package/dist/__tests__/doc-only-verdict.test.js +79 -0
  19. package/dist/__tests__/doc-only-verdict.test.js.map +1 -0
  20. package/dist/__tests__/error-classification.test.js +14 -0
  21. package/dist/__tests__/error-classification.test.js.map +1 -1
  22. package/dist/__tests__/fix-requires-recheck.test.d.ts +2 -0
  23. package/dist/__tests__/fix-requires-recheck.test.d.ts.map +1 -0
  24. package/dist/__tests__/fix-requires-recheck.test.js +56 -0
  25. package/dist/__tests__/fix-requires-recheck.test.js.map +1 -0
  26. package/dist/__tests__/human-feedback.test.d.ts +2 -0
  27. package/dist/__tests__/human-feedback.test.d.ts.map +1 -0
  28. package/dist/__tests__/human-feedback.test.js +132 -0
  29. package/dist/__tests__/human-feedback.test.js.map +1 -0
  30. package/dist/__tests__/inconclusive-review.test.d.ts +2 -0
  31. package/dist/__tests__/inconclusive-review.test.d.ts.map +1 -0
  32. package/dist/__tests__/inconclusive-review.test.js +110 -0
  33. package/dist/__tests__/inconclusive-review.test.js.map +1 -0
  34. package/dist/__tests__/merge-gate.test.d.ts +2 -0
  35. package/dist/__tests__/merge-gate.test.d.ts.map +1 -0
  36. package/dist/__tests__/merge-gate.test.js +220 -0
  37. package/dist/__tests__/merge-gate.test.js.map +1 -0
  38. package/dist/__tests__/no-verdict.test.d.ts +2 -0
  39. package/dist/__tests__/no-verdict.test.d.ts.map +1 -0
  40. package/dist/__tests__/no-verdict.test.js +415 -0
  41. package/dist/__tests__/no-verdict.test.js.map +1 -0
  42. package/dist/__tests__/pr-spec.test.js +37 -0
  43. package/dist/__tests__/pr-spec.test.js.map +1 -1
  44. package/dist/__tests__/run.test.js +50 -1
  45. package/dist/__tests__/run.test.js.map +1 -1
  46. package/dist/__tests__/runner.test.js +82 -1
  47. package/dist/__tests__/runner.test.js.map +1 -1
  48. package/dist/__tests__/standing-verdict-history.test.d.ts +2 -0
  49. package/dist/__tests__/standing-verdict-history.test.d.ts.map +1 -0
  50. package/dist/__tests__/standing-verdict-history.test.js +109 -0
  51. package/dist/__tests__/standing-verdict-history.test.js.map +1 -0
  52. package/dist/__tests__/step-plan.test.d.ts +2 -0
  53. package/dist/__tests__/step-plan.test.d.ts.map +1 -0
  54. package/dist/__tests__/step-plan.test.js +129 -0
  55. package/dist/__tests__/step-plan.test.js.map +1 -0
  56. package/dist/cli.js +12 -0
  57. package/dist/cli.js.map +1 -1
  58. package/dist/commands/merge.d.ts +11 -0
  59. package/dist/commands/merge.d.ts.map +1 -0
  60. package/dist/commands/merge.js +188 -0
  61. package/dist/commands/merge.js.map +1 -0
  62. package/dist/commands/review.d.ts.map +1 -1
  63. package/dist/commands/review.js +29 -7
  64. package/dist/commands/review.js.map +1 -1
  65. package/dist/commands/run.d.ts +53 -0
  66. package/dist/commands/run.d.ts.map +1 -1
  67. package/dist/commands/run.js +197 -32
  68. package/dist/commands/run.js.map +1 -1
  69. package/dist/commands/watch.d.ts.map +1 -1
  70. package/dist/commands/watch.js +14 -3
  71. package/dist/commands/watch.js.map +1 -1
  72. package/dist/config/loader.d.ts +4 -0
  73. package/dist/config/loader.d.ts.map +1 -1
  74. package/dist/config/loader.js +8 -0
  75. package/dist/config/loader.js.map +1 -1
  76. package/dist/config/review-strategy.json +5 -5
  77. package/dist/github/client.d.ts +18 -1
  78. package/dist/github/client.d.ts.map +1 -1
  79. package/dist/github/client.js +19 -3
  80. package/dist/github/client.js.map +1 -1
  81. package/dist/github/merge.d.ts +28 -0
  82. package/dist/github/merge.d.ts.map +1 -1
  83. package/dist/github/merge.js +41 -0
  84. package/dist/github/merge.js.map +1 -1
  85. package/dist/github/webhook.d.ts +1 -0
  86. package/dist/github/webhook.d.ts.map +1 -1
  87. package/dist/github/webhook.js.map +1 -1
  88. package/dist/lib/board.d.ts +1 -0
  89. package/dist/lib/board.d.ts.map +1 -1
  90. package/dist/lib/board.js +1 -1
  91. package/dist/lib/board.js.map +1 -1
  92. package/dist/lib/clone.d.ts +65 -1
  93. package/dist/lib/clone.d.ts.map +1 -1
  94. package/dist/lib/clone.js +252 -16
  95. package/dist/lib/clone.js.map +1 -1
  96. package/dist/lib/human-feedback.d.ts +9 -0
  97. package/dist/lib/human-feedback.d.ts.map +1 -0
  98. package/dist/lib/human-feedback.js +83 -0
  99. package/dist/lib/human-feedback.js.map +1 -0
  100. package/dist/lib/logger.d.ts +1 -1
  101. package/dist/lib/logger.d.ts.map +1 -1
  102. package/dist/lib/logger.js +6 -0
  103. package/dist/lib/logger.js.map +1 -1
  104. package/dist/lib/merge-gate.d.ts +70 -0
  105. package/dist/lib/merge-gate.d.ts.map +1 -0
  106. package/dist/lib/merge-gate.js +138 -0
  107. package/dist/lib/merge-gate.js.map +1 -0
  108. package/dist/lib/no-verdict.d.ts +104 -0
  109. package/dist/lib/no-verdict.d.ts.map +1 -0
  110. package/dist/lib/no-verdict.js +262 -0
  111. package/dist/lib/no-verdict.js.map +1 -0
  112. package/dist/lib/pr-spec.d.ts +7 -0
  113. package/dist/lib/pr-spec.d.ts.map +1 -1
  114. package/dist/lib/pr-spec.js +12 -1
  115. package/dist/lib/pr-spec.js.map +1 -1
  116. package/dist/lib/pr-workflow-state.d.ts +37 -0
  117. package/dist/lib/pr-workflow-state.d.ts.map +1 -1
  118. package/dist/lib/pr-workflow-state.js +58 -1
  119. package/dist/lib/pr-workflow-state.js.map +1 -1
  120. package/dist/lib/review-strategy.d.ts +14 -0
  121. package/dist/lib/review-strategy.d.ts.map +1 -1
  122. package/dist/lib/review-strategy.js +32 -1
  123. package/dist/lib/review-strategy.js.map +1 -1
  124. package/dist/lib/runner.d.ts +51 -2
  125. package/dist/lib/runner.d.ts.map +1 -1
  126. package/dist/lib/runner.js +211 -105
  127. package/dist/lib/runner.js.map +1 -1
  128. package/dist/lib/verdict.d.ts +30 -1
  129. package/dist/lib/verdict.d.ts.map +1 -1
  130. package/dist/lib/verdict.js +94 -9
  131. package/dist/lib/verdict.js.map +1 -1
  132. package/dist/reviewers/codex-env.d.ts.map +1 -1
  133. package/dist/reviewers/codex-env.js +2 -0
  134. package/dist/reviewers/codex-env.js.map +1 -1
  135. package/dist/reviewers/codex.d.ts.map +1 -1
  136. package/dist/reviewers/codex.js +3 -1
  137. package/dist/reviewers/codex.js.map +1 -1
  138. package/dist/reviewers/fix.d.ts +2 -2
  139. package/dist/reviewers/fix.d.ts.map +1 -1
  140. package/dist/reviewers/fix.js +10 -4
  141. package/dist/reviewers/fix.js.map +1 -1
  142. package/docs/growth/launch/README.md +59 -0
  143. package/docs/growth/launch/essay-stop-letting-one-agent-review-its-own-code.md +118 -0
  144. package/docs/growth/launch/posts.md +175 -0
  145. package/docs/trust.md +16 -1
  146. package/package.json +11 -3
@@ -0,0 +1,138 @@
1
+ import { shaCovers } from './pr-workflow-state.js';
2
+ import { hasBlockingFindings } from './verdict.js';
3
+ function verdictCoversHead(input) {
4
+ return input.verdictSha !== undefined && shaCovers(input.verdictSha, input.headSha);
5
+ }
6
+ /**
7
+ * A conflicted PR is refused at every strictness including `force`.
8
+ *
9
+ * `force` overrides crosscheck's *opinion*; it does not override git. GitHub
10
+ * would reject the merge anyway, so attempting it just turns a clear local
11
+ * refusal into an opaque API error — and a flag that appears to promise "merge
12
+ * regardless" should fail honestly rather than look broken.
13
+ */
14
+ function gitRefusal(input) {
15
+ if (input.mergeable === false) {
16
+ const state = input.mergeStateStatus ? ` (mergeable_state: ${input.mergeStateStatus})` : '';
17
+ return `GitHub reports this PR cannot be merged${state} — resolve conflicts first. Not overridable by --force.`;
18
+ }
19
+ if (input.mergeable === null) {
20
+ return 'GitHub has not finished computing mergeability — retry in a moment.';
21
+ }
22
+ if (input.mergeStateStatus === 'dirty') {
23
+ return 'The PR has merge conflicts — resolve them first. Not overridable by --force.';
24
+ }
25
+ return null;
26
+ }
27
+ /**
28
+ * Whether the verdict that stands still carries an unresolved blocking finding.
29
+ *
30
+ * Read from the comment that *carries the standing verdict*, not from the latest
31
+ * review comment. Those differ after a fix: on crosscheck#319 the review BLOCKed
32
+ * with real critical findings, the fix step resolved them, and the recheck
33
+ * approved — so reading the review body reported a blocker that no longer existed,
34
+ * and `--tight` refused a PR whose findings were fixed. Any PR that had ever been
35
+ * blocked would have been permanently un-tight-mergeable.
36
+ *
37
+ * Two verdicts are treated as having no unresolved blockers regardless of their
38
+ * body text, because in both cases something has already adjudicated them:
39
+ *
40
+ * - an APPROVE from a recheck, which is the judge saying the findings it raised
41
+ * are addressed;
42
+ * - an APPROVE produced by the documentation-only cap, which is a deliberate
43
+ * policy decision that prose findings do not block. Re-blocking here would
44
+ * undo it at merge time and make "never block a doc-only PR" false.
45
+ */
46
+ export function standingHasBlockingFindings(records) {
47
+ const judged = records.filter(r => (r.type === 'review' || r.type === 'recheck') && r.verdict).at(-1);
48
+ if (!judged)
49
+ return false;
50
+ if (judged.verdict === 'APPROVE')
51
+ return false;
52
+ return hasBlockingFindings(judged.commentBody);
53
+ }
54
+ export function evaluateMergeGate(input) {
55
+ const reasons = [];
56
+ const satisfied = [];
57
+ const git = gitRefusal(input);
58
+ if (git)
59
+ return { allowed: false, reasons: [git], satisfied };
60
+ satisfied.push('GitHub reports the PR mergeable');
61
+ if (input.strictness === 'force') {
62
+ // Recorded rather than silent: the confirmation line and the log both say what
63
+ // was skipped, so a forced merge is never indistinguishable from a gated one.
64
+ return {
65
+ allowed: true,
66
+ reasons: [`--force: merged without a verdict gate (standing verdict: ${input.verdict ?? 'none'})`],
67
+ satisfied,
68
+ };
69
+ }
70
+ if (!input.verdict) {
71
+ reasons.push('No review verdict stands on this PR — run `crosscheck run <pr>` first.');
72
+ return { allowed: false, reasons, satisfied };
73
+ }
74
+ if (input.strictness === 'loose') {
75
+ // Loose still requires the verdict to describe the code being merged. A stale
76
+ // APPROVE from three pushes ago is not a weaker signal, it is a different
77
+ // commit's signal, so coverage is not what `--loose` relaxes.
78
+ if (!verdictCoversHead(input)) {
79
+ reasons.push(`The standing ${input.verdict} was written against ${input.verdictSha ?? 'an unknown commit'}, not HEAD (${input.headSha.slice(0, 9)}) — re-review before merging.`);
80
+ return { allowed: false, reasons, satisfied };
81
+ }
82
+ if (input.verdict === 'BLOCK') {
83
+ reasons.push('The standing verdict is BLOCK. --loose accepts NEEDS WORK but not a block; use --force to override deliberately.');
84
+ return { allowed: false, reasons, satisfied };
85
+ }
86
+ satisfied.push(`standing verdict is ${input.verdict}, covering HEAD`);
87
+ return { allowed: true, reasons, satisfied };
88
+ }
89
+ // default and tight both require an APPROVE on the exact commit being merged.
90
+ if (input.verdict !== 'APPROVE') {
91
+ reasons.push(`The standing verdict is ${input.verdict}, not APPROVE. Use --loose to merge a non-blocking verdict, or --force to override.`);
92
+ }
93
+ else if (!verdictCoversHead(input)) {
94
+ reasons.push(`The APPROVE was written against ${input.verdictSha ?? 'an unknown commit'}, not HEAD (${input.headSha.slice(0, 9)}) — push moved the code the approval covered.`);
95
+ }
96
+ else {
97
+ satisfied.push(`APPROVE covers HEAD (${input.headSha.slice(0, 9)})`);
98
+ }
99
+ if (input.strictness === 'tight') {
100
+ const failing = input.failingChecks ?? [];
101
+ const pending = input.pendingChecks ?? [];
102
+ if (failing.length > 0)
103
+ reasons.push(`--tight: ${failing.length} check${failing.length === 1 ? '' : 's'} failing (${failing.slice(0, 4).join(', ')}${failing.length > 4 ? ', …' : ''}).`);
104
+ if (pending.length > 0)
105
+ reasons.push(`--tight: ${pending.length} check${pending.length === 1 ? '' : 's'} still running (${pending.slice(0, 4).join(', ')}${pending.length > 4 ? ', …' : ''}).`);
106
+ if (input.hasBlockingFindings)
107
+ reasons.push('--tight: the standing review still carries an unresolved blocking finding.');
108
+ if (failing.length === 0 && pending.length === 0)
109
+ satisfied.push('all checks green');
110
+ if (!input.hasBlockingFindings)
111
+ satisfied.push('no unresolved blocking findings');
112
+ }
113
+ return { allowed: reasons.length === 0, reasons, satisfied };
114
+ }
115
+ /** How the chosen strictness reads in the confirmation line. */
116
+ export function describeStrictness(strictness) {
117
+ switch (strictness) {
118
+ case 'force': return 'force (no verdict gate)';
119
+ case 'loose': return 'loose (verdict must not be BLOCK)';
120
+ case 'tight': return 'tight (APPROVE on HEAD, checks green, no open findings)';
121
+ default: return 'default (APPROVE on HEAD)';
122
+ }
123
+ }
124
+ /**
125
+ * Resolves the mutually exclusive strictness flags.
126
+ *
127
+ * Rejecting combinations rather than picking a winner: `--loose --tight` has no
128
+ * defensible reading, and silently honouring one of them on a command that
129
+ * merges code is how somebody gets a merge they did not ask for.
130
+ */
131
+ export function resolveStrictness(flags) {
132
+ const chosen = ['loose', 'tight', 'force'].filter(f => flags[f]);
133
+ if (chosen.length > 1) {
134
+ return { error: `--${chosen.join(' and --')} cannot be combined — they ask for different gates.` };
135
+ }
136
+ return chosen[0] ?? 'default';
137
+ }
138
+ //# sourceMappingURL=merge-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-gate.js","sourceRoot":"","sources":["../../src/lib/merge-gate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,104 @@
1
+ import type { StepOutcomes } from './runner.js';
2
+ /** What became of a step that can produce a verdict. Exactly one holds. */
3
+ export type VerdictStepDisposition =
4
+ /** No review/recheck step exists in the resolved workflow. */
5
+ 'not_configured'
6
+ /** Configured, but excluded before dispatch — --steps, repo override,
7
+ * strategy narrowing, or a resume that started past it. */
8
+ | 'not_dispatched'
9
+ /** Dispatched and skipped; carries the reason from its step_skipped entry. */
10
+ | 'skipped'
11
+ /** Ran, posted, and its output carried no parseable verdict. */
12
+ | 'ran_no_verdict'
13
+ /** Ran and produced a verdict. Present for completeness — a run in this state
14
+ * has a verdict and never reaches this report. */
15
+ | 'ran';
16
+ export interface VerdictStepStatus {
17
+ step: string;
18
+ disposition: VerdictStepDisposition;
19
+ reason?: string;
20
+ }
21
+ export type NoVerdictCause = 'strategy_skip' | 'not_configured' | 'ran_no_verdict' | 'skipped' | 'not_dispatched' | 'fix_noop';
22
+ /** Minimal shape of a workflow step — name and type are all this needs, so
23
+ * callers can pass WorkflowStep directly without a conversion. */
24
+ export interface VerdictStepShape {
25
+ name: string;
26
+ type: string;
27
+ }
28
+ /** Minimal shape of a step history record — type and verdict are all this needs. */
29
+ export interface JudgedRecordShape {
30
+ type: string;
31
+ verdict?: string;
32
+ sha?: string;
33
+ }
34
+ /**
35
+ * The verdict that still governs the PR: the newest review or recheck that
36
+ * actually carries one.
37
+ *
38
+ * A review can run, post its findings, and record no verdict at all — that is
39
+ * the `ran_no_verdict` disposition, and it says nothing about the `BLOCK` that
40
+ * was standing before it. Taking the newest review-or-recheck record
41
+ * unconditionally would let one malformed review erase a verdict that still
42
+ * gates the PR, and the report would go quiet about it in exactly the state
43
+ * that most needs saying out loud.
44
+ */
45
+ export declare function selectStandingVerdict(history: readonly JudgedRecordShape[]): {
46
+ verdict: string;
47
+ sha?: string;
48
+ } | undefined;
49
+ export interface NoVerdictInput {
50
+ /** The resolved workflow BEFORE step filtering. Filtering is what separates
51
+ * `not_configured` from `not_dispatched`, so the pre-filter list is required. */
52
+ workflowSteps: readonly VerdictStepShape[];
53
+ /** Accumulated across every round of the invocation. */
54
+ outcomes?: StepOutcomes;
55
+ /** PR class when the review strategy skipped the PR outright. */
56
+ strategySkipped?: string | null;
57
+ /** True when this invocation asked for a subset of steps on purpose — an
58
+ * explicit --steps flag or a kickass one-step dispatch. Not set by resume,
59
+ * which starts mid-workflow but still runs to the end. */
60
+ stepsExplicitlyScoped?: boolean;
61
+ prUrl: string;
62
+ /** Verdict already standing on the PR from an earlier run, if known. */
63
+ standingVerdict?: {
64
+ verdict?: string;
65
+ sha?: string;
66
+ };
67
+ headSha?: string;
68
+ }
69
+ export interface NoVerdictReport {
70
+ /** False when this invocation could have produced a verdict and didn't.
71
+ * Drives the ⚠/✓ marker — coverage is universal, alarm is not. */
72
+ expected: boolean;
73
+ cause: NoVerdictCause;
74
+ verdictSteps: VerdictStepStatus[];
75
+ ran: {
76
+ step: string;
77
+ detail: string;
78
+ }[];
79
+ didNotRun: {
80
+ step: string;
81
+ note: string;
82
+ }[];
83
+ why: string[];
84
+ next: {
85
+ text: string;
86
+ recommended: boolean;
87
+ }[];
88
+ notes: string[];
89
+ }
90
+ /**
91
+ * Classifies every verdict-capable step in the workflow. When none is
92
+ * configured, returns a single synthetic `review` entry — the absence is itself
93
+ * the answer, and callers should not have to special-case an empty array.
94
+ */
95
+ export declare function describeVerdictSteps(workflowSteps: readonly VerdictStepShape[], outcomes: StepOutcomes | undefined): VerdictStepStatus[];
96
+ /**
97
+ * Explains an empty verdict and says what to do about it. Pure — every input it
98
+ * needs is already in memory at the reporting site.
99
+ */
100
+ export declare function buildNoVerdictReport(input: NoVerdictInput): NoVerdictReport;
101
+ /** Renders the report as plain lines, unindented and uncoloured. The caller
102
+ * owns presentation; this owns what is said and in what order. */
103
+ export declare function renderNoVerdictReport(report: NoVerdictReport): string[];
104
+ //# sourceMappingURL=no-verdict.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-verdict.d.ts","sourceRoot":"","sources":["../../src/lib/no-verdict.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAiB,MAAM,aAAa,CAAA;AAe9D,2EAA2E;AAC3E,MAAM,MAAM,sBAAsB;AAChC,8DAA8D;AAC5D,gBAAgB;AAClB;4DAC4D;GAC1D,gBAAgB;AAClB,8EAA8E;GAC5E,SAAS;AACX,gEAAgE;GAC9D,gBAAgB;AAClB;mDACmD;GACjD,KAAK,CAAA;AAET,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,sBAAsB,CAAA;IACnC,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,MAAM,cAAc,GACtB,eAAe,GACf,gBAAgB,GAChB,gBAAgB,GAChB,SAAS,GACT,gBAAgB,GAChB,UAAU,CAAA;AAEd;mEACmE;AACnE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb;AAED,oFAAoF;AACpF,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,SAAS,iBAAiB,EAAE,GACpC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAI/C;AAED,MAAM,WAAW,cAAc;IAC7B;sFACkF;IAClF,aAAa,EAAE,SAAS,gBAAgB,EAAE,CAAA;IAC1C,wDAAwD;IACxD,QAAQ,CAAC,EAAE,YAAY,CAAA;IACvB,iEAAiE;IACjE,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B;;+DAE2D;IAC3D,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,wEAAwE;IACxE,eAAe,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACpD,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B;uEACmE;IACnE,QAAQ,EAAE,OAAO,CAAA;IACjB,KAAK,EAAE,cAAc,CAAA;IACrB,YAAY,EAAE,iBAAiB,EAAE,CAAA;IACjC,GAAG,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IACvC,SAAS,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAC3C,GAAG,EAAE,MAAM,EAAE,CAAA;IACb,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,OAAO,CAAA;KAAE,EAAE,CAAA;IAC9C,KAAK,EAAE,MAAM,EAAE,CAAA;CAChB;AAMD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,aAAa,EAAE,SAAS,gBAAgB,EAAE,EAC1C,QAAQ,EAAE,YAAY,GAAG,SAAS,GACjC,iBAAiB,EAAE,CAqBrB;AAmDD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,cAAc,GAAG,eAAe,CAiI3E;AAMD;mEACmE;AACnE,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,EAAE,CA+BvE"}
@@ -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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"}
@@ -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"}
@@ -19,6 +19,8 @@ export const DEFAULT_MAX_PRS = 100;
19
19
  // Full-URL token: optional scheme, github.com host, owner/repo, /pull/<numspec>.
20
20
  // Anything trailing (e.g. /files, query string) is ignored.
21
21
  const URL_TOKEN_RE = /^(?:https?:\/\/)?github\.com\/([^/\s]+)\/([^/\s]+)\/pull\/(\d+(?:-\d+)?)\b/i;
22
+ // `#issuecomment-<id>` fragment on a PR URL — GitHub's anchor for one comment.
23
+ const COMMENT_ANCHOR_RE = /#issuecomment-(\d+)\b/i;
22
24
  // Bare numeric token: a single number or an inclusive range.
23
25
  const NUM_TOKEN_RE = /^\d+(?:-\d+)?$/;
24
26
  function expandNumspec(numspec, maxPRs) {
@@ -51,6 +53,8 @@ export function parsePRSpec(spec, options = {}) {
51
53
  let owner;
52
54
  let repo;
53
55
  let numspec;
56
+ const anchorMatch = token.match(COMMENT_ANCHOR_RE);
57
+ const commentId = anchorMatch ? parseInt(anchorMatch[1], 10) : undefined;
54
58
  const urlMatch = token.match(URL_TOKEN_RE);
55
59
  if (urlMatch) {
56
60
  owner = urlMatch[1];
@@ -74,7 +78,14 @@ export function parsePRSpec(spec, options = {}) {
74
78
  if (seen.has(key))
75
79
  continue;
76
80
  seen.add(key);
77
- refs.push({ owner, repo, number, url: `https://github.com/${owner}/${repo}/pull/${number}` });
81
+ // A range expands to several PRs but the anchor names one comment, which can
82
+ // only belong to one of them — so it is carried only on a single-PR token.
83
+ const anchored = commentId !== undefined && !numspec.includes('-');
84
+ refs.push({
85
+ owner, repo, number,
86
+ url: `https://github.com/${owner}/${repo}/pull/${number}`,
87
+ ...(anchored && { commentId }),
88
+ });
78
89
  if (refs.length > maxPRs) {
79
90
  throw new Error(`Too many PRs requested (>${maxPRs}). Narrow the ranges or split into multiple commands.`);
80
91
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pr-spec.js","sourceRoot":"","sources":["../../src/lib/pr-spec.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pr-spec.js","sourceRoot":"","sources":["../../src/lib/pr-spec.ts"],"names":[],"mappings":"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"}