@humanbased/crosscheck 1.2.0-beta.78 → 1.2.0-beta.80

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.
@@ -0,0 +1,286 @@
1
+ # Dynamic thoroughness
2
+
3
+ **Strategy version 1.0.0 · updated 2026-08-07 · re-verify every 60 days.**
4
+
5
+ This document is the analysis behind [`src/config/review-strategy.json`](../src/config/review-strategy.json).
6
+ The JSON is the policy crosscheck executes; this file is why the policy says what it
7
+ says. They are versioned together: a change to one without the other is drift, and
8
+ `npm run verify:strategy` is the check that makes that drift loud.
9
+
10
+ ---
11
+
12
+ ## 1. The problem
13
+
14
+ One PR is not like another. A lockfile bump and a rewrite of the session handler
15
+ are both "a PR", and crosscheck used to review them identically — one configured
16
+ `quality.tier`, one model, one effort level, the same review→fix→recheck loop.
17
+ That is wrong in both directions at once: it overpays on the lockfile and
18
+ under-reviews the session handler.
19
+
20
+ The failure that prompted this was quieter than either. `review-model-tiers.json`
21
+ pinned `claude-opus-4-8` for the `thorough` tier and kept pinning it after
22
+ `claude-opus-5` shipped at identical $5/$25 pricing with a higher coding score.
23
+ Nothing broke. Nothing warned. Every `thorough` review just got worse than it had
24
+ to be, for months. A model pin is a perishable asset, and nothing in the repo
25
+ treated it as one.
26
+
27
+ So the policy has two jobs: choose per PR, and tell a human when the inputs it
28
+ chose from have moved.
29
+
30
+ ---
31
+
32
+ ## 2. Sources, and how to re-verify
33
+
34
+ `sources[]` in the JSON lists the pages the numbers came from, each with the
35
+ strings that must still appear on it. `scripts/verify-review-strategy.mjs`
36
+ fetches them and compares.
37
+
38
+ | Source | What it fixes |
39
+ |---|---|
40
+ | [Anthropic model catalog](https://platform.claude.com/docs/en/about-claude/models/overview) | Claude model IDs and availability |
41
+ | [Anthropic effort parameter](https://platform.claude.com/docs/en/build-with-claude/effort) | The effort ladder, including `xhigh` |
42
+ | [OpenAI model catalog](https://developers.openai.com/api/docs/models) | Codex model IDs |
43
+
44
+ Benchmarks are recorded in `benchmarks` with their own `as_of` dates and caveats,
45
+ because they age faster than prices:
46
+
47
+ - **SWE-bench Verified** — resolving real GitHub issues in Python repos. A
48
+ backend/general proxy only. Comparable within one leaderboard run; cross-run
49
+ positions are indicative.
50
+ - **Frontend Code Arena** — blind pairwise human preference on generated frontend
51
+ output. Measures *generation*, not defect detection. Claude Opus 5 is absent
52
+ from the retrieved leaderboard: a missing measurement, not a low score.
53
+
54
+ Re-verification is not automatic and is not meant to be. Model choice is a
55
+ judgement call backed by benchmarks that need reading. The weekly
56
+ `Review Strategy` workflow fetches the sources and opens an issue when a check
57
+ string disappears; a human then re-reads and edits. The job's job is to say *the
58
+ inputs moved*, never to move the policy itself.
59
+
60
+ Network failures are deliberately not drift. A fetch that never landed says
61
+ nothing about the policy, so the verifier treats unreachable sources as warnings
62
+ on `pull_request` and as errors only on `schedule`, where someone is looking.
63
+
64
+ ---
65
+
66
+ ## 3. The model catalog
67
+
68
+ Prices are USD per million tokens.
69
+
70
+ | Model | Vendor | In | Out | SWE-bench V | Frontend Elo | Effort levels |
71
+ |---|---|---:|---:|---:|---:|---|
72
+ | `claude-opus-5` | claude | 5 | 25 | 0.960 | — | low…max |
73
+ | `claude-sonnet-5` | claude | 3 | 15 | 0.852 | — | low…max |
74
+ | `claude-haiku-4-5-20251001` | claude | 1 | 5 | 0.733 | — | *(none)* |
75
+ | `gpt-5.6-sol` | codex | 5 | 30 | — | 1618 | none…max |
76
+ | `gpt-5.6-terra` | codex | 2 | 12 | — | — | none…max |
77
+ | `gpt-5.6-luna` | codex | 0.2 | 1.2 | — | — | none…max |
78
+ | `kimi-k3` | opencode *(planned)* | 3 | 15 | — | 1679 | *(none)* |
79
+ | `glm-5.2` | opencode *(planned)* | 1.4 | 4.4 | — | 1587 | *(none)* |
80
+ | `deepseek-v4-pro` | opencode *(planned)* | 0.435 | 0.87 | 0.806 | — | none, high, max |
81
+ | `deepseek-v4-flash` | opencode *(planned)* | 0.14 | 0.28 | 0.790 | — | none, high, max |
82
+
83
+ `banned_models` is a separate list from "not currently routed", because the
84
+ reason matters. `claude-fable-5` is banned on cost: $50/MTok output is 2× Opus 5
85
+ for a *lower* SWE-bench Verified score (95.0 vs 96.0). At the measured ~48k
86
+ output tokens per review that is $2.40 a call with no capability justification.
87
+ The verifier fails if any vendor tier or domain preference ever routes to it.
88
+
89
+ ---
90
+
91
+ ## 4. Tiers and domains
92
+
93
+ Tiers are the vendor-facing ladder:
94
+
95
+ | Tier | Claude | Codex | OpenCode *(planned)* |
96
+ |---|---|---|---|
97
+ | `fast` | Haiku 4.5 | GPT-5.6 Luna | deepseek-v4-flash |
98
+ | `balanced` | Sonnet 5 | GPT-5.6 Terra | glm-5.2 |
99
+ | `thorough` | Opus 5 | GPT-5.6 Sol | kimi-k3 |
100
+
101
+ Capability, though, is domain-shaped rather than scalar. Opus 5 leads backend
102
+ defect resolution; kimi-k3 leads the frontend arena. A single ladder cannot
103
+ express that, so `domains` carries a preference list per tier. The domain is
104
+ detected from changed-file extensions; `mixed` and `unknown` both fall back to
105
+ `backend`, the conservative default — the backend list is `measured`, the
106
+ frontend list is still a `hypothesis` pending a local A/B.
107
+
108
+ ---
109
+
110
+ ## 5. PR classes
111
+
112
+ **Order is the routing logic — first match wins.** The list is in
113
+ `pr_classes`, and `risky` sits second, immediately after `generated`, so
114
+ consequence-based promotion dominates every cheapening rule below it. A
115
+ deletion-only PR that removes auth code, or a two-file change to a migration,
116
+ must not fall through to `fast`.
117
+
118
+ | # | Class | Tier · effort | Steps |
119
+ |---|---|---|---|
120
+ | 1 | `generated` — every file generated or vendored | *skip* | — |
121
+ | 2 | `risky` — security / data-critical | thorough · high | review, fix, recheck |
122
+ | 3 | `deletion_only` | fast · medium | review |
123
+ | 4 | `docs` | balanced · high | review |
124
+ | 5 | `test_only` | fast · medium | review, fix |
125
+ | 6 | `config_infra` | balanced · high | review, fix, recheck |
126
+ | 7 | `trivial` | fast · medium | review, fix |
127
+ | 8 | `standard` — fallthrough | balanced · medium | review, fix, recheck |
128
+
129
+ Two classes drop the fix loop on purpose. `docs` is open-ended design
130
+ negotiation that no automated fix loop terminates. `deletion_only` carries
131
+ reference-breakage risk rather than defect density — a wrong deletion is
132
+ reverted, not patched — so it gets a review focused on dangling imports, call
133
+ sites, config keys, route registrations and stale doc pointers.
134
+
135
+ The last entry must have an empty `match`: it is the fallthrough, and
136
+ `resolveReviewStrategy` falls back to it when nothing matches. The verifier
137
+ asserts this.
138
+
139
+ ### Path patterns are anchored
140
+
141
+ Class patterns match path *segments and extensions*, not substrings. An
142
+ unanchored list is worse than no list:
143
+
144
+ - `auth` matched `author.ts`, `authors.ts` and `oauth.ts`;
145
+ - `migration` matched this repo's own `MIGRATION_AND_GROWTH.md`, promoting a
146
+ pure-docs PR to `thorough` with a full fix/recheck loop;
147
+ - `spec` matched `src/lib/pr-spec.ts` and `test` matched any `latest.ts`,
148
+ routing ordinary source changes to `test_only` with a test-focused prompt.
149
+
150
+ Over-promotion is safe but expensive; under-review is neither. Both are bugs.
151
+
152
+ ### Size caps fail closed
153
+
154
+ `src_churn_max` can only be evaluated when churn is attributable — the PR
155
+ context carries whole-PR `additions`/`deletions`, not per-file counts, so the
156
+ total is only meaningful when every changed file is source. When it is not, the
157
+ cap fails **closed** (the churn reads as `Infinity` and the class does not
158
+ match). Failing open cost us a real misroute: `['package.json',
159
+ 'src/lib/runner.ts', 'src/lib/board.ts']` at +3000/−2000 satisfied
160
+ `files_max: 3`, the unattributable churn read as `0`, and a 5,000-line change to
161
+ the core runner was routed to `fast` with no recheck.
162
+
163
+ ---
164
+
165
+ ## 6. Design decisions
166
+
167
+ ### 6.1 Why a versioned policy file, not code
168
+
169
+ Every crosscheck comment cites `strategy=<version>`. A review that ran three
170
+ months ago has to stay explicable by the policy in force when it ran, and that is
171
+ only possible if the policy is a versioned artifact rather than a diff of
172
+ `if` statements. It also puts the routing decision and the sentence explaining it
173
+ in the same record, so the two cannot drift apart: `reason` is quoted verbatim
174
+ into the PR comment, which is why the verifier fails any class missing one.
175
+
176
+ `review-model-tiers.json` is retained for backwards compatibility and is
177
+ superseded by this file.
178
+
179
+ ### 6.2 Why capability is not a single number
180
+
181
+ See §4. A tier ladder is a cost ladder that happens to correlate with capability
182
+ inside one vendor. Across vendors and across domains it stops correlating, which
183
+ is why `domains.*.preferred` exists and why each entry carries a `confidence`
184
+ field. `measured` and `hypothesis` are load-bearing words here — the frontend
185
+ list is a hypothesis and is labelled as one.
186
+
187
+ ### 6.3 Why `thorough` moved to Opus 5
188
+
189
+ Identical $5/$25 pricing, higher SWE-bench Verified (0.960 vs the 4.8 pin it
190
+ replaced). There is no trade here; the old pin was a silent capability loss, and
191
+ it persisted only because nothing was watching. §2 exists so the next one does
192
+ not persist.
193
+
194
+ ### 6.4 Models with no effort ladder — the OpenCode case
195
+
196
+ Most open-weight models expose no effort parameter at all: `kimi-k3`, `glm-5.2`
197
+ and `claude-haiku-4-5-20251001` all have `effort_levels: []`, and the deepseek
198
+ models expose only `none`/`high`/`max`. An escalation step defined purely as
199
+ "raise effort" silently no-ops on every one of them.
200
+
201
+ So the ladder declares `effort_fallback: "promote_tier"`: where effort cannot
202
+ rise, the escalation degrades to a model step instead. `escalate()` implements
203
+ this by clamping the round's target effort to what the model actually accepts and
204
+ promoting a tier only when the clamped value is not an increase over the round
205
+ before — otherwise a model whose ladder tops out below `xhigh` comes out of
206
+ round 3 *weaker* than it left round 2.
207
+
208
+ The verifier enforces the invariant directly: if any model has no effort levels,
209
+ `ladder.effort_fallback` must be set.
210
+
211
+ ### 6.5 Escalate on measured non-convergence, never on predicted complexity
212
+
213
+ The ladder is bounded — 3 rounds, 5 blocking findings, 60 wall-clock minutes —
214
+ and each round narrows scope rather than widening it. Round 2 rechecks the delta
215
+ plus the open findings, not the whole PR again. The model never weakens across
216
+ rounds; only its scope shrinks. Round 4 is a handoff to a human with a digest,
217
+ not a fourth attempt.
218
+
219
+ ### 6.6 Cross-vendor vs single-vendor
220
+
221
+ Cross-vendor buys an independent second perspective: in the census a vendor
222
+ switch broke a stalled loop twice (PR #2057, #2060) after same-vendor escalation
223
+ had failed for three to six rounds. Its round-3 lever is *switch vendor and raise
224
+ effort*, and it caps at 3 rounds. Single-vendor buys one CLI to authenticate,
225
+ version and monitor, with no routing logic; its round-3 lever is *raise effort,
226
+ then promote one tier*, because no fresh-eyes option exists, and it caps at 2.
227
+
228
+ ### 6.7 Conflict resolution is not a review
229
+
230
+ `conflict_resolve` runs at `fast`/`low` regardless of class. Resolving conflict
231
+ markers is mechanical text surgery bounded by the markers themselves — measured
232
+ at 37s against ~643s for a review. Spending review-grade capability on it buys
233
+ nothing.
234
+
235
+ ### 6.8 What classification may and may not conclude
236
+
237
+ Two rules constrain the classifier, and they are the reason it stays a small
238
+ static function instead of growing into a difficulty predictor:
239
+
240
+ 1. **It may set a floor, or promote on consequence.** A security path is
241
+ reviewed thoroughly because a miss there is expensive. That is a statement
242
+ about cost of failure, which the file list genuinely supports.
243
+ 2. **It may not predict that a PR will be hard.** Static features — file count,
244
+ line count, extension mix — do not support that inference. Difficulty is
245
+ discovered by reviewing, not before it. So there is no "this looks tricky,
246
+ use the big model" rule, and there never should be.
247
+
248
+ Escalation is how difficulty gets handled, and it is driven by *measured*
249
+ non-convergence: the review came back BLOCK, the fix did not resolve it, so the
250
+ next round escalates. That is evidence. A file-count heuristic is not.
251
+
252
+ ---
253
+
254
+ ## 7. Runtime status
255
+
256
+ `runtime_status: "defined_not_wired"`. This file and the JSON are the adopted
257
+ policy, but no runtime code reads `resolveReviewStrategy()` yet —
258
+ `resolveClaudeModel` / `resolveCodexModel` still apply the single configured
259
+ `quality.tier`. Verification, documentation and the citation contract are in
260
+ place ahead of the resolver so the policy is settled before it is enforced.
261
+
262
+ Consequently:
263
+
264
+ - `quality.mode` stays **optional**. Unset means `fixed` — the legacy behaviour —
265
+ and it will stay that way until the resolver lands, so nothing silently changes
266
+ tier on upgrade.
267
+ - `citation.annotation_fields` (`strategy`, `class`, `tier`) is a declared
268
+ contract, not yet emitted by `src/lib/annotation.ts`.
269
+
270
+ Wiring is the next change, and it is a separate one. It needs the PR's changed-file
271
+ list threaded to the reviewers, the `strategy` argument passed at each
272
+ `resolve*Model` call site, and the annotation fields emitted — none of which
273
+ belongs in the PR that settles the policy.
274
+
275
+ ---
276
+
277
+ ## 8. Cadence
278
+
279
+ `review_interval_days: 60`. The verifier warns past 45 days and fails past 60.
280
+ The `Review Strategy` workflow runs Mondays at 09:15 UTC — fifteen minutes after
281
+ `model-tier-sources`, so the two do not race the same provider docs — and on any
282
+ PR touching the strategy, the verifier or this document.
283
+
284
+ To refresh: re-read the `sources` and `benchmarks` pages, update prices, scores
285
+ and tier assignments in `src/config/review-strategy.json`, refresh the analysis
286
+ here, bump `version` and `updated`, then run `npm run verify:strategy`.
package/get-started.md CHANGED
@@ -1418,7 +1418,7 @@ Every comment crosscheck posts closes with the model and effort the step actuall
1418
1418
 
1419
1419
  ```
1420
1420
  ---
1421
- _Reviewed with Claude Code via Crosscheck_ _(Opus 4.8 · high effort)_
1421
+ _Reviewed with Claude Code via Crosscheck_ _(Opus 5 · high effort)_
1422
1422
 
1423
1423
  _Skills: code-review-skill (by @awesome-skills, MIT)_
1424
1424
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humanbased/crosscheck",
3
- "version": "1.2.0-beta.78",
3
+ "version": "1.2.0-beta.80",
4
4
  "description": "AI code review pipeline that turns agent-written PRs into merge-ready patches",
5
5
  "bin": {
6
6
  "crosscheck": "dist/cli.js",
@@ -23,12 +23,13 @@
23
23
  "node": ">=18"
24
24
  },
25
25
  "scripts": {
26
- "build": "tsc && rm -rf dist/harness && cp -r src/harness dist/harness && cp src/config/review-model-tiers.json dist/config/review-model-tiers.json && chmod +x dist/cli.js dist/ck.js",
26
+ "build": "tsc && rm -rf dist/harness && cp -r src/harness dist/harness && cp src/config/review-model-tiers.json dist/config/review-model-tiers.json && cp src/config/review-strategy.json dist/config/review-strategy.json && chmod +x dist/cli.js dist/ck.js",
27
27
  "dev": "tsx src/cli.ts",
28
28
  "typecheck": "tsc --noEmit",
29
29
  "test": "vitest run",
30
30
  "lint": "eslint src --ext .ts",
31
- "prepublishOnly": "npm run typecheck && npm run build"
31
+ "prepublishOnly": "npm run typecheck && npm run build",
32
+ "verify:strategy": "node scripts/verify-review-strategy.mjs"
32
33
  },
33
34
  "dependencies": {
34
35
  "chalk": "^5.3.0",