@humanbased/crosscheck 1.3.0-beta.91 → 1.3.0-beta.93

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 (48) hide show
  1. package/README.md +1 -1
  2. package/crosscheck.config.example.yml +9 -0
  3. package/dist/__tests__/adoption.test.d.ts +2 -0
  4. package/dist/__tests__/adoption.test.d.ts.map +1 -0
  5. package/dist/__tests__/adoption.test.js +175 -0
  6. package/dist/__tests__/adoption.test.js.map +1 -0
  7. package/dist/__tests__/detector.test.js +87 -1
  8. package/dist/__tests__/detector.test.js.map +1 -1
  9. package/dist/__tests__/runner.test.js +161 -1
  10. package/dist/__tests__/runner.test.js.map +1 -1
  11. package/dist/cli.js +8 -0
  12. package/dist/cli.js.map +1 -1
  13. package/dist/commands/adoption.d.ts +6 -0
  14. package/dist/commands/adoption.d.ts.map +1 -0
  15. package/dist/commands/adoption.js +132 -0
  16. package/dist/commands/adoption.js.map +1 -0
  17. package/dist/commands/onboard.d.ts.map +1 -1
  18. package/dist/commands/onboard.js +30 -3
  19. package/dist/commands/onboard.js.map +1 -1
  20. package/dist/commands/run.d.ts.map +1 -1
  21. package/dist/commands/run.js +30 -2
  22. package/dist/commands/run.js.map +1 -1
  23. package/dist/commands/watch.d.ts.map +1 -1
  24. package/dist/commands/watch.js +4 -0
  25. package/dist/commands/watch.js.map +1 -1
  26. package/dist/config/schema.d.ts.map +1 -1
  27. package/dist/config/schema.js +7 -0
  28. package/dist/config/schema.js.map +1 -1
  29. package/dist/github/detector.d.ts.map +1 -1
  30. package/dist/github/detector.js +28 -1
  31. package/dist/github/detector.js.map +1 -1
  32. package/dist/github/webhook.d.ts +1 -0
  33. package/dist/github/webhook.d.ts.map +1 -1
  34. package/dist/github/webhook.js.map +1 -1
  35. package/dist/lib/adoption.d.ts +64 -0
  36. package/dist/lib/adoption.d.ts.map +1 -0
  37. package/dist/lib/adoption.js +165 -0
  38. package/dist/lib/adoption.js.map +1 -0
  39. package/dist/lib/runner.d.ts +24 -0
  40. package/dist/lib/runner.d.ts.map +1 -1
  41. package/dist/lib/runner.js +115 -16
  42. package/dist/lib/runner.js.map +1 -1
  43. package/dist/lib/workflow.d.ts +4 -0
  44. package/dist/lib/workflow.d.ts.map +1 -1
  45. package/dist/lib/workflow.js.map +1 -1
  46. package/docs/metrics.md +115 -0
  47. package/get-started.md +67 -0
  48. package/package.json +1 -1
@@ -0,0 +1,165 @@
1
+ // Adoption metrics answer "is crosscheck actually being used, and does it reach a
2
+ // verdict fast enough to matter" — a different question from `impact`, which prices
3
+ // the value of reviews that already happened.
4
+ //
5
+ // Every number here is derived from the local NDJSON logs in ~/.crosscheck/logs.
6
+ // Nothing is transmitted: there is no endpoint, no account, and no network call in
7
+ // this file. See docs/metrics.md for the field-by-field inventory.
8
+ // A verdict-bearing latency is only meaningful for a review that produced one; a
9
+ // verdictless review (from `crosscheck review`) has nothing to time to.
10
+ export function prOpenToVerdictMs(createdAt, verdict, now = Date.now()) {
11
+ if (verdict === null || createdAt === undefined)
12
+ return undefined;
13
+ const opened = new Date(createdAt).getTime();
14
+ if (Number.isNaN(opened))
15
+ return undefined;
16
+ const elapsed = now - opened;
17
+ // A negative elapsed time means the clocks disagree, not that the PR was
18
+ // reviewed before it existed. Drop it rather than publish a nonsense number.
19
+ return elapsed >= 0 ? elapsed : undefined;
20
+ }
21
+ export function isoWeekMonday(ts) {
22
+ const d = new Date(ts);
23
+ if (Number.isNaN(d.getTime()))
24
+ return null;
25
+ const day = d.getUTCDay();
26
+ const diff = day === 0 ? -6 : 1 - day;
27
+ return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() + diff))
28
+ .toISOString().slice(0, 10);
29
+ }
30
+ export function percentile(sorted, fraction) {
31
+ if (sorted.length === 0)
32
+ return 0;
33
+ // Nearest-rank on the sorted sample: no interpolation, so every reported value
34
+ // is one that actually occurred.
35
+ const rank = Math.ceil(fraction * sorted.length) - 1;
36
+ return sorted[Math.min(Math.max(rank, 0), sorted.length - 1)];
37
+ }
38
+ export function buildAdoptionReport(lines, period) {
39
+ const weekly = new Map();
40
+ const activeRepos = new Set();
41
+ const latencies = [];
42
+ let unmeasured = 0;
43
+ const counts = {
44
+ onboard_started: 0,
45
+ onboard_completed: 0,
46
+ reviews_started: 0,
47
+ reviews_completed: 0,
48
+ rechecks_completed: 0,
49
+ blocking: 0,
50
+ fixes: 0,
51
+ };
52
+ for (const line of lines) {
53
+ switch (line.event) {
54
+ case 'onboard_started':
55
+ counts.onboard_started++;
56
+ break;
57
+ case 'onboard_completed':
58
+ // The event is emitted for both outcomes so an abandoned setup is
59
+ // visible; only a success counts as completed.
60
+ if (line.outcome === 'completed')
61
+ counts.onboard_completed++;
62
+ break;
63
+ case 'review_started':
64
+ counts.reviews_started++;
65
+ break;
66
+ case 'review_complete': {
67
+ counts.reviews_completed++;
68
+ if (line.step_type === 'recheck')
69
+ counts.rechecks_completed++;
70
+ if (line.repo) {
71
+ activeRepos.add(line.repo);
72
+ const week = isoWeekMonday(line.ts);
73
+ if (week) {
74
+ const bucket = weekly.get(week) ?? { repos: new Set(), reviews: 0 };
75
+ bucket.repos.add(line.repo);
76
+ bucket.reviews++;
77
+ weekly.set(week, bucket);
78
+ }
79
+ }
80
+ if (typeof line.open_to_verdict_ms === 'number')
81
+ latencies.push(line.open_to_verdict_ms);
82
+ else if (line.verdict)
83
+ unmeasured++;
84
+ break;
85
+ }
86
+ case 'blocking_finding_posted':
87
+ counts.blocking++;
88
+ break;
89
+ case 'fix_complete':
90
+ // delivery: 'comment' still counts — a suggestion the author applied is
91
+ // adoption. A no-op fix (applied_count 0) is not.
92
+ if ((line.applied_count ?? 0) > 0)
93
+ counts.fixes++;
94
+ break;
95
+ }
96
+ }
97
+ latencies.sort((a, b) => a - b);
98
+ return {
99
+ period,
100
+ onboarding: {
101
+ started: counts.onboard_started,
102
+ completed: counts.onboard_completed,
103
+ abandoned: Math.max(0, counts.onboard_started - counts.onboard_completed),
104
+ },
105
+ activity: {
106
+ reviews_started: counts.reviews_started,
107
+ reviews_completed: counts.reviews_completed,
108
+ rechecks_completed: counts.rechecks_completed,
109
+ blocking_findings_posted: counts.blocking,
110
+ fixes_applied: counts.fixes,
111
+ active_repos: activeRepos.size,
112
+ },
113
+ weekly: [...weekly.entries()]
114
+ .sort(([a], [b]) => a.localeCompare(b))
115
+ .map(([week, { repos, reviews }]) => ({ week, active_repos: repos.size, reviews })),
116
+ open_to_verdict: {
117
+ count: latencies.length,
118
+ p50_ms: percentile(latencies, 0.5),
119
+ p90_ms: percentile(latencies, 0.9),
120
+ max_ms: latencies.at(-1) ?? 0,
121
+ unmeasured,
122
+ },
123
+ first_run_failures: firstRunFailures(lines),
124
+ };
125
+ }
126
+ // "First run" is a session that never completed a review. Its first error is the
127
+ // one that stopped a new install from reaching a verdict — the category worth
128
+ // fixing. Sessions that did complete a review are excluded even if they later
129
+ // errored: those are operational failures, not activation failures.
130
+ export function firstRunFailures(lines) {
131
+ const sessions = [];
132
+ let current = null;
133
+ const close = () => {
134
+ if (current)
135
+ sessions.push(current);
136
+ current = null;
137
+ };
138
+ for (const line of lines) {
139
+ if (line.event === 'session_start') {
140
+ close();
141
+ current = { completedReview: false, firstErrorCategory: null };
142
+ continue;
143
+ }
144
+ // Logs from before session events existed, or a truncated file, would
145
+ // otherwise drop every entry on the floor.
146
+ if (current === null)
147
+ current = { completedReview: false, firstErrorCategory: null };
148
+ if (line.event === 'review_complete')
149
+ current.completedReview = true;
150
+ if (line.event === 'error' && current.firstErrorCategory === null) {
151
+ current.firstErrorCategory = line.category ?? 'unknown';
152
+ }
153
+ if (line.event === 'session_end')
154
+ close();
155
+ }
156
+ close();
157
+ const failures = {};
158
+ for (const session of sessions) {
159
+ if (session.completedReview || session.firstErrorCategory === null)
160
+ continue;
161
+ failures[session.firstErrorCategory] = (failures[session.firstErrorCategory] ?? 0) + 1;
162
+ }
163
+ return failures;
164
+ }
165
+ //# sourceMappingURL=adoption.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adoption.js","sourceRoot":"","sources":["../../src/lib/adoption.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":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAoB,MAAM,qBAAqB,CAAA;AAEnE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AACnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,EAAqB,KAAK,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAQjE,OAAO,EAAiC,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAQ9F,OAAO,EAAuE,KAAK,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAKjJ,OAAO,EAAuE,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAA;AACzH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAsB9C,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAGzD;AAaD,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,GAAG,MAAM,CAEpF;AAWD,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAA;IACb;;;;OAIG;IACH,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,wBAAgB,mCAAmC,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,qBAAqB,CAqB1G;AAED,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnF;AAOD,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,WAAW,GAAG,SAAS,CAAA;AAE7F,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,EAAE,MAAM,CAAA;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IACnC,cAAc,EAAE,OAAO,CAAA;IACvB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,eAAe,CAAA;IACzB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAOD,MAAM,MAAM,mBAAmB,GAC3B,WAAW,GACX,OAAO,GACP,eAAe,GACf,gBAAgB,GAChB,cAAc,CAAA;AAElB,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,sBAAsB,GAC7B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAsCzB;AAKD,wBAAgB,gBAAgB,CAC9B,aAAa,EAAE,MAAM,EACrB,gBAAgB,EAAE,MAAM,EACxB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GAAG,SAAS,GACxB,OAAO,CAOT;AAKD,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,OAAO,CAE1E;AAED,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,QAAQ,GAAG,SAAS,CAAA;AAanE,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,yBAAyB,GAAG,SAAS,CAAA;AACzE,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,eAAe,GAAG,UAAU,CAM3E;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,EAAE,EAAE,OAAO,CAAC,cAAc,CAAC,CAAA;IAC3B,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,QAAQ,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1B,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,IAAI,CAAA;IAE1D,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAG3B,YAAY,CAAC,EAAE,OAAO,CAAA;IAEtB,KAAK,CAAC,EAAE,MAAM,CAAA;IAGd,MAAM,CAAC,EAAE,OAAO,CAAA;IAGhB,UAAU,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAA;IAGtC,KAAK,CAAC,EAAE,OAAO,eAAe,EAAE,YAAY,EAAE,CAAA;IAG9C,mBAAmB,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAA;IAMxC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;IAIrB,oBAAoB,CAAC,EAAE;QACrB,EAAE,CAAC,EAAE,MAAM,CAAA;QACX,IAAI,EAAE,MAAM,CAAA;KACb,CAAA;IAID,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAG1B,SAAS,CAAC,EAAE,OAAO,GAAG,WAAW,CAAA;IAGjC,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAE1B,OAAO,CAAC,EAAE,eAAe,CAAA;IAIzB,YAAY,CAAC,EAAE,MAAM,CAAA;IAIrB,aAAa,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;CAChH;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB;uEACmE;IACnE,eAAe,CAAC,EAAE,MAAM,CAAA;IAGxB,eAAe,CAAC,EAAE,MAAM,CAAA;IAIxB,aAAa,CAAC,EAAE,MAAM,CAAA;IAItB,mBAAmB,CAAC,EAAE;QACpB,EAAE,CAAC,EAAE,MAAM,CAAA;QACX,IAAI,EAAE,MAAM,CAAA;KACb,CAAA;IACD;;;uDAGmD;IACnD,YAAY,CAAC,EAAE,YAAY,CAAA;CAC5B;AAED,MAAM,WAAW,YAAY;IAC3B;wEACoE;IACpE,GAAG,EAAE,MAAM,EAAE,CAAA;IACb;kCAC8B;IAC9B,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAC5C;AAKD,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,GAClC,YAAY,CAQd;AAWD,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,YAAY,GAAG,SAAS,EAC9B,IAAI,EAAE,YAAY,GAAG,SAAS,GAC7B,YAAY,GAAG,SAAS,CAY1B;AAkDD,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAE3E;AAED,wBAAgB,4BAA4B,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAE1F;AA8DD,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,QAAQ,EAChB,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,GAC5B;IAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,GAAG,IAAI,CAAC;IAAC,iBAAiB,EAAE,OAAO,CAAC;IAAC,uBAAuB,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAA;CAAE,CAEjH;AAKD,wBAAgB,4BAA4B,CAC1C,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,QAAQ,EAChB,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,GAC5B;IAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,GAAG,IAAI,CAAC;IAAC,iBAAiB,EAAE,OAAO,CAAC;IAAC,uBAAuB,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAA;CAAE,CAEjH;AAgCD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,eAAe,GAAG,SAAS,GAAG,IAAI,CAsCrE;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,EAC7C,QAAQ,EAAE,gBAAgB,GAAG,IAAI,GAChC,eAAe,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,CAGtC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,EAC1D,MAAM,EAAE,CAAC,EACT,QAAQ,EAAE,gBAAgB,GAAG,IAAI,EACjC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B,CAAC,CAKH;AASD;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EACjC,QAAQ,EAAE,gBAAgB,GAAG,IAAI,EACjC,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAIT;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,eAAe,GAAG,gBAAgB,GAAG,IAAI,CAKlF;AAED,MAAM,WAAW,cAAc;IAC7B,oEAAoE;IACpE,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAAA;IACjC,OAAO,EAAE,MAAM,CAAC,SAAS,CAAC,CAAA;IAC1B,YAAY,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAA;IACzC,WAAW,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,CAAA;IACvC,iFAAiF;IACjF,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,OAAO,CAAA;CACnB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,gBAAgB,GAAG,IAAI,EACjC,KAAK,EAAE,MAAM,GACZ,cAAc,CA4ChB;AAuHD,wBAAsB,WAAW,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CA2qC/E"}
@@ -23,6 +23,7 @@ import { resolveClaudeModel, resolveCodexModel } from '../lib/review-models.js';
23
23
  import { resolveReviewStrategy, escalate, clampToLevels } from './review-strategy.js';
24
24
  import { CLAUDE_EFFORT_LEVELS, CODEX_EFFORT_LEVELS } from '../config/schema.js';
25
25
  import { buildStepIdentityFields } from '../lib/event-fields.js';
26
+ import { prOpenToVerdictMs } from '../lib/adoption.js';
26
27
  import { buildAttributionFooter, buildFixAppliedCommentBody, buildFixFailedCommentBody, buildConflictResolvedCommentBody, buildRetriedReviewBanner } from '../lib/comment-bodies.js';
27
28
  import { linearWritePossible, loadWorkflow, loadHarnessSection, evaluateWhen } from '../lib/workflow.js';
28
29
  import { isSubscriptionLimitError, isVendorUnavailableError } from '../lib/smart-switch.js';
@@ -147,6 +148,45 @@ export function resolveFixLanding(deliveryMode) {
147
148
  case 'pull_request': return 'branch-then-separate-pr';
148
149
  }
149
150
  }
151
+ // stepsRun holds every step the runner dispatched, skips included; results holds
152
+ // what each one did. Split them so callers can report a run where nothing
153
+ // happened without re-deriving the reasons from the log file.
154
+ export function summariseStepOutcomes(stepsRun, results) {
155
+ const outcomes = { ran: [], skipped: [] };
156
+ for (const name of new Set(stepsRun)) {
157
+ const result = results[name];
158
+ if (result?.skipped)
159
+ outcomes.skipped.push({ step: name, reason: result.skipReason ?? 'unknown' });
160
+ else
161
+ outcomes.ran.push(name);
162
+ }
163
+ return outcomes;
164
+ }
165
+ // Accumulates outcomes across the fix→recheck rounds of one invocation, so the
166
+ // completion line reports whether the run as a whole did work: a first round
167
+ // that skips everything followed by a round that applies a fix is not a
168
+ // "no step ran" run.
169
+ //
170
+ // Running once wins over skipping any number of times — the step demonstrably
171
+ // happened. A step skipped in several rounds is reported once, carrying its
172
+ // latest reason: that is the state the run ended in, and listing the same step
173
+ // several times reads as several distinct problems.
174
+ export function mergeStepOutcomes(base, next) {
175
+ if (!base)
176
+ return next;
177
+ if (!next)
178
+ return base;
179
+ const ran = [...new Set([...base.ran, ...next.ran])];
180
+ const ranSet = new Set(ran);
181
+ // Map.set on an existing key overwrites the reason but keeps the original
182
+ // position, so order stays first-seen while the reason stays last-seen.
183
+ const skipped = new Map();
184
+ for (const entry of [...base.skipped, ...next.skipped]) {
185
+ if (!ranSet.has(entry.step))
186
+ skipped.set(entry.step, entry.reason);
187
+ }
188
+ return { ran, skipped: [...skipped].map(([step, reason]) => ({ step, reason })) };
189
+ }
150
190
  function countComments(reviewText) {
151
191
  const bullets = (reviewText.match(/^[-*•]\s/gm) ?? []).length;
152
192
  const numbered = (reviewText.match(/^\d+\.\s/gm) ?? []).length;
@@ -203,16 +243,38 @@ export function fixPRCommitSubject(prNumber, vendor) {
203
243
  export function conflictResolveCommitSubject(conflictCount, vendor) {
204
244
  return `[crosscheck] resolve: resolve ${conflictCount} conflict${conflictCount !== 1 ? 's' : ''} — by ${vendorDisplayName(vendor)}`;
205
245
  }
206
- // Extends resolveReviewer with a human-origin fallback for the fix step.
246
+ // Extends resolveReviewer with a human-origin fallback for the steps that write
247
+ // code (fix, conflict-resolve).
207
248
  // Scoped to reviewer: 'origin' only — other reviewer types (claude, codex, auto)
208
249
  // already encode explicit vendor intent and need no fallback.
209
250
  // When origin is 'human' and no vendor resolved, honours routing.fallback_reviewer
210
- // so the fix step respects the same routing intent as the review step.
211
- // 'auto' mirrors resolveReviewer's auto path (config-enabled check, codex-first)
212
- // without async auth calls. null disables the fallback entirely.
213
- // Exported so callers can detect when the fallback was applied (e.g. for logging).
214
- export function resolveFixVendor(stepReviewer, origin, config, fallback) {
251
+ // so the step respects the same routing intent as the review step.
252
+ // 'auto' resolves against the vendors that can actually run stepType, so a step
253
+ // only one vendor supports doesn't fall back to the other and skip a line later.
254
+ // An explicit 'claude'/'codex' is an operator decision and is honoured as written
255
+ // even when that vendor cannot run the step — the caller then reports a precise
256
+ // unsupported-step skip instead of silently substituting a different vendor.
257
+ // null disables the fallback entirely.
258
+ function resolveStepVendor(stepType, stepReviewer, origin, config, fallback) {
215
259
  const vendor = resolveReviewer(stepReviewer, origin, config, fallback);
260
+ // Origin detection can assign a vendor that cannot run the step: conflict
261
+ // resolution is Claude-only, so a Codex-origin PR (reviewer: 'origin', origin:
262
+ // 'codex') resolves to 'codex' and the dispatch skips it as unsupported — the
263
+ // conflicts never get resolved. (#284's `Crosscheck-Reviewer: codex` detection
264
+ // introduced this: these crosscheck-authored fix PRs used to detect as 'human'
265
+ // and the auto fallback picked Claude.) Substitute a capable, enabled vendor.
266
+ // Scoped to origin-derived assignment only — an explicit reviewer: claude|codex,
267
+ // reviewer: auto, or routing.fallback_reviewer is an operator decision, left as
268
+ // written so the caller can report the precise unsupported-step skip.
269
+ if (stepReviewer === 'origin' &&
270
+ (origin === 'claude' || origin === 'codex') &&
271
+ vendor !== null &&
272
+ !supportsStep(vendor, stepType)) {
273
+ const capable = vendor === 'claude' ? 'codex' : 'claude';
274
+ if (config.vendors[capable].enabled && supportsStep(capable, stepType)) {
275
+ return { vendor: capable, usedHumanFallback: false, substitutedOriginVendor: vendor };
276
+ }
277
+ }
216
278
  if (vendor !== null || origin !== 'human' || stepReviewer !== 'origin') {
217
279
  return { vendor, usedHumanFallback: false };
218
280
  }
@@ -223,13 +285,25 @@ export function resolveFixVendor(stepReviewer, origin, config, fallback) {
223
285
  else if (fb === 'codex')
224
286
  humanFallback = config.vendors.codex.enabled ? 'codex' : null;
225
287
  else if (fb !== null) {
226
- // 'auto': prefer codex then claude, same as resolveReviewer's auto path
227
- humanFallback = config.vendors.codex.enabled ? 'codex' : config.vendors.claude.enabled ? 'claude' : null;
288
+ // 'auto': prefer codex then claude, same as resolveReviewer's auto path,
289
+ // narrowed to vendors that support this step type.
290
+ const usable = (v) => config.vendors[v].enabled && supportsStep(v, stepType);
291
+ humanFallback = usable('codex') ? 'codex' : usable('claude') ? 'claude' : null;
228
292
  }
229
293
  if (!humanFallback)
230
294
  return { vendor: null, usedHumanFallback: false };
231
295
  return { vendor: humanFallback, usedHumanFallback: true };
232
296
  }
297
+ // Exported so callers can detect when the fallback was applied (e.g. for logging).
298
+ export function resolveFixVendor(stepReviewer, origin, config, fallback) {
299
+ return resolveStepVendor('fix', stepReviewer, origin, config, fallback);
300
+ }
301
+ // The default workflow gives conflict-resolve `reviewer: origin`, so every PR
302
+ // crosscheck cannot attribute resolved to null here and skipped with 'no_vendor'
303
+ // — the fix step honoured routing.fallback_reviewer, this one did not.
304
+ export function resolveConflictResolveVendor(stepReviewer, origin, config, fallback) {
305
+ return resolveStepVendor('conflict-resolve', stepReviewer, origin, config, fallback);
306
+ }
233
307
  // ─── pr_complexity helpers ────────────────────────────────────────────────────
234
308
  const EXT_LANG = {
235
309
  ts: 'typescript', tsx: 'typescript', js: 'javascript', jsx: 'javascript',
@@ -717,7 +791,7 @@ export async function runWorkflow(ctx) {
717
791
  const effectiveType = getEffectiveStepType(step.type, ctx.isRecheckRun === true);
718
792
  if (exceedsMaxRounds(effectiveType, step.type, ctx.overrideMaxRounds ?? step.max_rounds, ctx.round)) {
719
793
  fileLog({ level: 'info', event: 'step_skipped', repo: `${owner}/${repoName}`, pr: prNumber, step: step.name, reason: 'max_rounds' });
720
- results[step.name] = { skipped: true };
794
+ results[step.name] = { skipped: true, skipReason: 'max_rounds' };
721
795
  if (effectiveType === 'fix')
722
796
  onPhaseChange('', { phase: 'fixed', fixCount: 0 });
723
797
  else if (effectiveType === 'recheck')
@@ -729,7 +803,7 @@ export async function runWorkflow(ctx) {
729
803
  // Evaluate when condition — skip step if false
730
804
  if (step.when && !evaluateWhen(step.when, results)) {
731
805
  fileLog({ level: 'info', event: 'step_skipped', repo: `${owner}/${repoName}`, pr: prNumber, step: step.name, reason: 'when_condition' });
732
- results[step.name] = { skipped: true };
806
+ results[step.name] = { skipped: true, skipReason: 'when_condition' };
733
807
  if (effectiveType === 'fix')
734
808
  onPhaseChange('', { phase: 'fixed', fixCount: 0 });
735
809
  else if (effectiveType === 'recheck')
@@ -743,7 +817,7 @@ export async function runWorkflow(ctx) {
743
817
  // review coerced to a recheck (isRecheckRun) is never blocked here.
744
818
  if (step.type === 'recheck' && reviewRanThisSession && !anyFixApplied(results)) {
745
819
  fileLog({ level: 'info', event: 'step_skipped', repo: `${owner}/${repoName}`, pr: prNumber, step: step.name, reason: 'no_change_since_review' });
746
- results[step.name] = { skipped: true };
820
+ results[step.name] = { skipped: true, skipReason: 'no_change_since_review' };
747
821
  onPhaseChange('', { phase: 'rechecked' });
748
822
  continue;
749
823
  }
@@ -752,7 +826,7 @@ export async function runWorkflow(ctx) {
752
826
  let reviewer = resolveReviewer(step.reviewer, origin, config, ctx.smartSwitchFallback);
753
827
  if (!reviewer) {
754
828
  fileLog({ level: 'info', event: 'step_skipped', repo: `${owner}/${repoName}`, pr: prNumber, step: step.name, reason: 'no_reviewer' });
755
- results[step.name] = { skipped: true };
829
+ results[step.name] = { skipped: true, skipReason: 'no_reviewer' };
756
830
  continue;
757
831
  }
758
832
  // The recheck step is confirmed to run — clear the pending-recheck guard
@@ -876,7 +950,12 @@ export async function runWorkflow(ctx) {
876
950
  ? `${buildRetriedReviewBanner(retried.timeoutMs, retried.delayMs)}\n\n${baseBody}`
877
951
  : baseBody;
878
952
  const commentCount = countComments(rawReview);
879
- fileLog({ level: 'info', event: 'review_complete', repo: `${owner}/${repoName}`, pr: prNumber, reviewer, model, ...stepIdentity, verdict, duration_ms: Date.now() - stepStart, tokens_used: tokensUsed, skills_activated: activatedSkills.map(skill => skill.name), ...(inputTokens !== undefined && { input_tokens: inputTokens }), ...(outputTokens !== undefined && { output_tokens: outputTokens }), ...(ctx.round !== undefined && { round: ctx.round }), ...(ctx.roundMode && { mode: ctx.roundMode }), ...triggerField });
953
+ // How long the PR waited for a verdict, measured from when its author opened
954
+ // it — the number a team feels, as distinct from duration_ms (how long the
955
+ // reviewer ran). Omitted rather than guessed when the PR event carried no
956
+ // created_at, so the metric never mixes real latencies with invented ones.
957
+ const openToVerdictMs = prOpenToVerdictMs(pr.created_at, verdict);
958
+ fileLog({ level: 'info', event: 'review_complete', repo: `${owner}/${repoName}`, pr: prNumber, reviewer, model, ...stepIdentity, verdict, duration_ms: Date.now() - stepStart, ...(openToVerdictMs !== undefined && { open_to_verdict_ms: openToVerdictMs }), tokens_used: tokensUsed, skills_activated: activatedSkills.map(skill => skill.name), ...(inputTokens !== undefined && { input_tokens: inputTokens }), ...(outputTokens !== undefined && { output_tokens: outputTokens }), ...(ctx.round !== undefined && { round: ctx.round }), ...(ctx.roundMode && { mode: ctx.roundMode }), ...triggerField });
880
959
  // Recheck verdict is stored separately to preserve the original review's commentCount on the board
881
960
  const phaseUpdate = isRecheck
882
961
  ? { recheckVerdict: verdict, phase: donePhase, recheckTokens: tokensUsed, recheckReviewer: reviewer, qualityTier: quality.tier }
@@ -946,6 +1025,16 @@ export async function runWorkflow(ctx) {
946
1025
  : undefined);
947
1026
  const commentUrl = `github.com/${owner}/${repoName}/pull/${prNumber}`;
948
1027
  fileLog({ level: 'info', event: 'comment_posted', repo: `${owner}/${repoName}`, pr: prNumber, url: `https://${commentUrl}` });
1028
+ // A posted verdict that blocks the merge is the product's whole reason to
1029
+ // exist, so it gets its own event rather than being re-derived downstream.
1030
+ // BLOCK blocks by definition; a NEEDS WORK that reached here survived the
1031
+ // severity gate, which only lets it through when a Critical/High/Medium
1032
+ // finding backs it — a nit-only review was already downgraded to APPROVE.
1033
+ // Logged only here, after the comment actually posted, so dry runs and
1034
+ // failed postReviewComment calls are never counted as posted findings.
1035
+ if (verdict === 'BLOCK' || verdict === 'NEEDS WORK') {
1036
+ fileLog({ level: 'info', event: 'blocking_finding_posted', repo: `${owner}/${repoName}`, pr: prNumber, reviewer, model, ...stepIdentity, verdict, ...(ctx.round !== undefined && { round: ctx.round }), ...triggerField });
1037
+ }
949
1038
  // Mirror the verdict onto the PR's Linear issue. `run` and `watch` both
950
1039
  // land here, so this is the path that matters — reviews posted from
951
1040
  // commands/review.ts are the exception, not the rule.
@@ -985,7 +1074,7 @@ export async function runWorkflow(ctx) {
985
1074
  const skipFix = (reason) => {
986
1075
  lastFixSkipReason = reason;
987
1076
  onPhaseChange('', { phase: 'fixed', fixCount: 0 });
988
- results[step.name] = { skipped: true };
1077
+ results[step.name] = { skipped: true, skipReason: reason };
989
1078
  fileLog({ level: 'info', event: 'step_skipped', repo: `${owner}/${repoName}`, pr: prNumber, step: step.name, reason });
990
1079
  };
991
1080
  if (ctx.dryRun) {
@@ -1380,7 +1469,7 @@ export async function runWorkflow(ctx) {
1380
1469
  else if (effectiveType === 'conflict-resolve') {
1381
1470
  const skipConflictResolve = (reason) => {
1382
1471
  onPhaseChange('', { phase: 'fixed', fixCount: 0 });
1383
- results[step.name] = { skipped: true };
1472
+ results[step.name] = { skipped: true, skipReason: reason };
1384
1473
  fileLog({ level: 'info', event: 'step_skipped', repo: `${owner}/${repoName}`, pr: prNumber, step: step.name, reason });
1385
1474
  };
1386
1475
  if (ctx.dryRun) {
@@ -1427,7 +1516,16 @@ export async function runWorkflow(ctx) {
1427
1516
  skipConflictResolve('no_conflicts');
1428
1517
  continue;
1429
1518
  }
1430
- const vendor = resolveReviewer(step.reviewer, origin, config, ctx.smartSwitchFallback);
1519
+ // resolveConflictResolveVendor extends resolveReviewer with the same human-origin
1520
+ // fallback the fix step uses, so a PR crosscheck cannot attribute still gets its
1521
+ // conflicts resolved instead of skipping with 'no_vendor'.
1522
+ const { vendor, usedHumanFallback, substitutedOriginVendor } = resolveConflictResolveVendor(step.reviewer, origin, config, ctx.smartSwitchFallback);
1523
+ if (usedHumanFallback && vendor) {
1524
+ fileLog({ level: 'info', event: 'conflict_resolve_vendor_fallback', repo: `${owner}/${repoName}`, pr: prNumber, from: 'none', to: vendor, reason: 'human_origin' });
1525
+ }
1526
+ else if (substitutedOriginVendor && vendor) {
1527
+ fileLog({ level: 'info', event: 'conflict_resolve_vendor_fallback', repo: `${owner}/${repoName}`, pr: prNumber, from: substitutedOriginVendor, to: vendor, reason: 'unsupported_vendor' });
1528
+ }
1431
1529
  if (!vendor) {
1432
1530
  try {
1433
1531
  execSync('git merge --abort', { cwd: tmpDir });
@@ -1633,6 +1731,7 @@ export async function runWorkflow(ctx) {
1633
1731
  return {
1634
1732
  verdict: verdict ?? null,
1635
1733
  fixAppliedCount,
1734
+ stepOutcomes: summariseStepOutcomes(stepsRun, results),
1636
1735
  ...(fixAppliedCount === undefined && lastFixSkipReason !== undefined && { fixSkipReason: lastFixSkipReason }),
1637
1736
  ...(latestReviewResult?.commentBody && {
1638
1737
  latestReviewComment: {