Research note · By error-path · Published 19 Oct 2026
Field test: opendrs 0.4.0 on six new repositories
opendrs 0.4.0 was run on about eight merged pull requests in each of six repositories, written in Python, Go, Rust, TypeScript, Ruby and Swift. On mature, active repositories most non-trivial PRs scored red regardless of size. Language gaps silently disabled signals: Go import resolution picked the wrong module, Ruby’s public-declaration detector was a stub, and Swift had no import extractor. The note ends with a ranked list of fixes.
It summarizes six field-test sessions, one per repository, and the raw brief and score JSON
saved for a subset of PRs. It does not re-run opendrs.
Purpose and setup
Six repositories, chosen to spread across language, layout and manifest shape, each scored with
opendrs 0.4.0 (CLI, --no-llm, no API calls) against roughly 8 recently merged pull requests:
| Repo | Language / layout | HEAD at test time |
|---|---|---|
| psf/black | Python, src-layout (src/black, src/blackd, src/blib2to3) |
56a7ee682ada03e086f62d66ac01b752fbda21db (2026-10-18) |
| cli/cli | Go, cmd/ + pkg/ + internal/ |
5b0cc54248a3d069b96147188d13cd2f1e976176 (2026-10-19) |
| actix/actix-web | Rust, Cargo workspace, 11 crates | 2db7119a990511e90e234d8ba118234b51bce68a (2026-10-19) |
| vitejs/vite | TypeScript, pnpm monorepo (packages/*) |
467f7ac3b8c7cca0b33290e7fd86f3fc79ce92cb (2026-10-19) |
| fastlane/fastlane | Ruby, one top-level dir per tool, each with its own lib/+spec/ |
e3ef1469e786892bc8c9d2773ae342d858c8567e (2026-10-17) |
| vapor/vapor | Swift, single module, no import extractor in opendrs | 3966c26927d27a7b950229375820fd084e1897f6 (2026-10-19) |
Each PR was run as opendrs brief --base <base> --head <head> --no-llm --json and
opendrs score --base <base> --head <head> --no-llm --json, base commonly <head>~1. cli/cli’s
history rarely carries a GitHub PR number on trunk (only 9 of 276 sampled commits do), so 3 of its
8 rows are commit-range stand-ins picked via git log --shortstat, per the test instructions, not
real PRs. vapor’s note tested 9 PRs, one more than the nominal 8. fastlane ran with
history.maxCommits: 1500 and history.lookbackDays: 90 set locally (default config crashed
outright — see Findings, theme 4); the other five repos ran default config. fastlane also used a
full (non-shallow) clone; the other five used blobless (--filter=blob:none) clones. These two
setup differences mean fastlane’s raw score numbers are not directly comparable to the other five
repos’ numbers, though its captain-fact and detector findings are.
Every fact was judged by hand against the real diff: TRUE+USEFUL (correct and worth a reviewer’s attention), NOISE (technically true but not worth flagging on this PR), WRONG (the fact is factually incorrect), or MISSED (a fact an architect would expect that did not fire). Facts reported in a note’s per-PR table but not covered by that note’s own per-fact judgment section are called out as unclassified rather than silently counted.
Caveats. Samples are small: 8–9 PRs per repo, one score per PR, no cross-repo replication. All
six sessions ran concurrently on the same Mac, sharing CPU and a slow git-spawn environment (a
security agent intercepts process spawns); wall-clock numbers below are noted as contention-
affected where the source note says so, but the failure modes they expose (fixed timeouts, no
fallback, no progress output) are real regardless of absolute seconds. calibrate/backtest
timed out or were killed in every one of the six repos, inside the 15-minute time box each session
was given. No held-out AUC was obtained for any of the six repos in this field test. Every tier
and priority below is the uncalibrated default model.
Per-repo summary
| Repo | PRs | Score / tier spread | Captain priorities | Facts fired | Notable result |
|---|---|---|---|---|---|
| black | 8 | 0.089–0.956; 1 green, 7 red | 1 quick, 7 careful | 14 (13 individually classified; see below) | 7 of 8 non-docs PRs scored red (0.80–0.96) regardless of size; pyproject PEP 508 marker parser fabricated deps ,/win32 and missed the real new dep winloop (8b9d3e3, PR #4996) |
| cli-cli | 8 | 0.019–0.659; 5 green, 3 yellow | 2 quick, 2 standard, 4 careful | 0 | Go import resolution picks a nested go.mod with the same module path, so opendrs explain reports 0 dependents for heavily-imported files repo-wide; 2 confirmed missed contract.api facts (a45729e87 #14620, ba51bb477 #14430) |
| actix-web | 8 | 0.132–0.752; 3 green, 5 yellow, 0 red | 1 quick, 1 standard, 6 careful | 5 | A platform-gated Cargo dependency (8487ab79 #4335, winapi-util) and two Cargo feature-flag breaks (7a3ef12d #4213, 3bb00075 #4198) are invisible to every signal; contract.api itself was correct on all 3 real public-API changes it saw |
| vite | 8 | 0.36–0.96; 2 yellow, 6 red | 0 quick, 2 standard, 6 careful | 17 | 6 of 8 PRs landed in the same red tier (0.83–0.96) regardless of size or stakes; history.incident folder-level matching and history.fixPattern both inflate scores; the node↔client module boundary never produced a layers.edge fact (0 of 8) |
| fastlane | 8 | 0.057–0.823; 3 green, 4 yellow, 1 red | 2 quick, 2 standard, 4 careful | 2 | Ruby’s public-declaration detector is a hardcoded stub, disabling contract.api for 100% of this Ruby repo; a real symlink-escape security fix (sigh-30380 #30380) scored green/quick/auto-approve |
| vapor | 9 | 0.05–0.95; 1 green, 6 yellow, 2 red | 1 quick, 5 standard, 3 careful | 0 | Every one of 9 briefs produced 0 captain facts; tests.missing/tests.deleted confidently misjudge Swift test files — a pure test-only PR (#3592) scored yellow/standard, and two deleted test files (#3593) were reported as “no test deletion” |
Captain-fact quality, by fact type, across all six repos
Captain facts are the eight types docs/captain.md defines: layers.edge, layers.cycle,
contract.dependency, contract.api, contract.schema, contract.env, coupling.missing,
drift.scope. (tests.missing/tests.deleted are separate score/gate signals, not captain facts;
their failures are covered under Findings, theme 2.) drift.scope runs only inside the GitHub
Action, so this CLI-only field test could not exercise it at all.
| Fact type | Fired | True+useful | Noise | Wrong | Confirmed missed |
|---|---|---|---|---|---|
contract.api |
10 | 8 | 2 | 0 | cli-cli ×2 (graph resolver bug, a45729e87, ba51bb477); vite ×1 (interface-field addition, bc0f21c56); permanently 0 for all of fastlane (Ruby public-decl stub) and all of vapor (no Swift extractor or public-decl rule) — confirmed against ≥3 PRs in each |
contract.dependency |
7 | 5 | 0 | 2 (black, 8b9d3e3) | black ×1 (winloop, 8b9d3e3); cli-cli ×1 (indirect→direct promotion, 2c7ec97fd); actix-web ×1 (cfg-gated dep, 8487ab79) + 1 design gap (major-version bump treated like a patch, efa9c213); vite ×1 (no “removed dependency” fact, ce4293f15); fastlane ×1 (.gemspec unsupported, gemspec-30062); vapor — 0 ever, Package.swift unsupported (#3570, #3593, #3603) |
coupling.missing |
18 | 0 | 18 | 0 | not a documented miss anywhere — it over-fires, it does not under-fire |
layers.edge / layers.cycle |
1 | 0 | 1 (fastlane, newaction-30025) | 0 | cli-cli (structural — graph is broken repo-wide); vite (1 confirmed real case never caught, even at a manually tested moduleDepth: 4, because the coupling is a generated code string, not a literal import, 182f5b2a6); black, actix-web, fastlane (structural moduleDepth blind spots, not demonstrated as false negatives in-sample); vapor (no extractor at all) |
contract.env |
2 | 2 | 0 | 0 | cli-cli (os.LookupEnv unsupported, confirmed present but not exercised by a sampled PR); vapor (no Swift pattern at all, confirmed via a deletion, #3593) |
contract.schema |
0 | 0 | 0 | 0 | black flags one high-blast-radius near-miss not covered by any schema glob or captain.contracts.paths override: src/blib2to3/Grammar.txt (f705197, #4805) |
drift.scope |
not exercised (Action-only) | — | — | — | — |
Bottom line. Where opendrs’s language support is complete (Python, Rust, most of TypeScript),
contract.api and contract.dependency mostly fire correctly and usefully. Where it is not —
Ruby’s public-declaration rule is an unconditional false, Swift has no import extractor at all,
and Go’s import graph resolves against the wrong module on this specific repo — those same two fact
types are silently and completely absent, with no unknown marker to say so. coupling.missing is
the opposite problem: it fired 18 times across the sample and was judged noise all 18 times,
consistent with docs/captain.md’s own note that it does not improve calibrated AUC and is kept as
a captain fact, not a score signal, for exactly this reason.
Findings
1. Score saturation on mature repos
- black: 7 of 8 non-docs PRs scored red, 0.80–0.96 — a3cb9e1 (#5477) 0.876, c45f21d (#5504)
0.880, 1ed66a8 (#5433) 0.956, d078e8e (#5296) 0.901, 8b9d3e3 (#4996) 0.953, f705197 (#4805) 0.829,
27c6fd0 (#5475) 0.803 — indistinguishable from each other whether the PR was a 22-line whitespace
fix or a 759-line release-pipeline rewrite. Root cause per the note:
diffusion.authors,history.churnandhistory.prior_fixesare raw historical counts over any touched file;linegen.py/nodes.pyhave 200–350 distinct authors and 180+ commits/year simply because they are the oldest, busiest files in an 11-year-old repo, which is a property of the file, not of any one diff. - vite: 6 of 8 PRs landed in the same red tier, 0.83–0.96 (77d837351 #23826 0.83, ce4293f15 #23744 0.90, b28db28e9 #23643 0.89, 182f5b2a6 #23674 0.90, bc0f21c56 #23463 0.92, 5b7393588 #23746 0.96) — a clean 101-line single-function bug fix (b28db28e9) scored the same tier as a 651-line lint-tooling migration (5b7393588).
history.fixPatternconflates Conventional-Commitfix:/fix(deps)with real incident fixes. vite: the purely cosmetic docs PR a6d310e42 (#23718, 3 files, 20 lines) scored 0.36/ yellow largely becausehistory.prior_fixescitedfix(deps): update all non-major dependenciesrenovate commits againstdocs/guide/api-plugin.mdas “prior fixes.” vapor:history.incidentcontributed a flat +0.80 on 7 of 9 PRs, including the trivial 1-line Package.swift pin #3603, because the repo’s history is dense with routine “Fix …” PR titles that the same broad regex matches.history.incident’s folder-level revert matching is too coarse for a monorepo. vite: one 8-file revert (2d66b7b14revertingacfe939e1) is matched at the containing-folder level, so it flagshistory.incident = 1(a flat +0.8 contribution) on hundreds of files anywhere underpackages/vite/src/node/that the revert never touched — confirmed on 5 of 8 sampled PRs (b28db28e9, 5b7393588, ce4293f15, bc0f21c56, 182f5b2a6).
2. Language coverage gaps
- Go import resolution picks a nested go.mod with the same module path.
.github/codeql/tests/unsanitized-response-to-terminal/go.modin cli/cli declaresmodule github.com/cli/cli/v2, byte-identical to the real root module. The resolver’s longest-path-first sort has no tie-break for equal-length paths, soArray.prototype.findpicks whichever go.mod was visited first by file order. Confirmed:opendrs explain api/client.goandopendrs explain pkg/extensions/official.goboth reportdirectDependents: 0at trunk HEAD despite real, dozens-deep usage. This single collision gutscontract.api,layers.edge,blast.dependentsandblast.public_surfacefor roughly 1,039 Go files, and concretely swallowed two real changes: a45729e87 (#14620) deletes and replaces an exportedIsOfficialsignature with a live caller, ba51bb477 (#14430) deletes the only call site of an exported, renamed type — both 0 facts. - go.mod
// indirectis not distinguished from direct. cli-cli PR 2c7ec97fd (#14578) promotescharmbracelet/x/ansifrom indirect to direct and drops three libraries (tcell,tview,gdamore/encoding) —contract.dependencyfired nothing, becauseparseGoModbuckets everyrequire (...)name into the same runtime set regardless of the// indirectcomment. os.LookupEnvis not incontract.env’s Go pattern (onlyos.Getenvis). cli-cli’s own non-test code usesos.LookupEnv14 times across 7 files; none of the 8 sampled PRs happened to add an env var this way, so this is a confirmed-present, not-yet-observed gap.- Cargo target-specific dependency tables are unscanned. actix-web PR 8487ab79 (#4335) adds
winapi-utilunder[target.'cfg(windows)'.dependencies]inactix-files/Cargo.toml— a genuine new runtime dependency, 0contract.dependencyfacts. - Cargo
[features]table changes are invisible to every signal. actix-web 7a3ef12d (#4213) silently un-implieshttp2from theopenssl/rustls-*TLS features (a downstream compatibility break); 3bb00075 (#4198) removes theexperimental-io-uringfeature. Neither produced a fact; the only reason either PR was flagged at all is the blanketCargo.tomlpaths.blockrule. - pyproject.toml’s PEP 508 environment-marker parsing is broken. black PR 8b9d3e3 (#4996):
manifests.ts’squotedStringsregex alternates single/double quotes without regard to which quote actually opened the TOML string, so"uvloop>=0.15.2; sys_platform != 'win32'"desyncs the rest of the array. Result: fake “new dependencies”,andwin32reported, and the real new dependencywinloopnever reported. - Ruby’s public-declaration detector is an unconditional stub (
src/graph/public-decl.ts:ruby: () => false). This permanently disablescontract.apiand holdsblast.public_surfaceat 0 for all of fastlane, regardless of what changes. Confirmed misses: refactor-30411 (#30411) deletes 4 public classes with no caller since 2015–2020; crosstool2-27547 (#27547) changes the signature of a public method,Spaceship::ConnectAPI::Profile.create, with live callers. Neither produced a fact. .gemspecadd_dependencyis unsupported. fastlane gemspec-30062 (#30062) addsspec.add_dependency('multi_json', '~> 1.12')tofastlane.gemspec—contract.dependencyhas no.gemspecentry in itsManifestKindunion at all, 0 facts (the file is still hard-blocked bypaths.block, but for an undifferentiated “this file type is blocked” reason, not a named dependency fact).- RSpec and Swift/XCTest test-file conventions are not in the default
paths.testsglobs. fastlane: every one of 8 sampled PRs’ own new*_spec.rbfiles were flagged as “no test changed,” including sigh-30380’s (#30380) own 67-lineresign_sh_spec.rband newaction-30025’s (#30025) 219-lineupdate_app_age_rating_spec.rb. vapor: PR #3592, a pure 2-file test-only change, scoredtests.missing = 1and landed yellow/standard instead of green/quick; PR #3593 deletes two whole test files (DotEnvTests.swift,EnvironmentTests.swift) andtests.deletedstill reports “no test deletion.” Package.swiftis not parsed bycontract.dependencyand is not inpaths.block. vapor #3570 adds two brand-new runtime dependencies (swift-nio-http3,swift-nio-quic) and bumps two more; #3593 removes a dependency; #3603 is a one-line version pin. All three: 0contract.dependencyfacts andpaths.blocked = 0.- Swift has no import extractor or public-declaration rule at all, so
contract.apican never fire. Confirmed misses: #3552 changespublic final class Requesttopublic struct Request(a source- and ABI-breaking change); #3464 adds 5 newpublic macrodeclarations; #3593 removespublic struct Process, twopublic subscripts andpublic static func get/secret. All silent.
3. Module boundaries (captain.moduleDepth: 2)
The default of 2 path segments is the wrong module unit for every layout tested:
- vite —
packages/vite/src/{node,client,shared,module-runner}all collapse to the single modulepackages/vite, hiding the one boundary that matters most in a bundler (dev-time Node code vs. code shipped to the browser). Tested directly on PR 182f5b2a6 with a temporarymoduleDepth: 4override: still 0layers.edgefacts, because that PR’s node↔client coupling is a generated JS string, not a literalimport.layers.edge/layers.cyclefired zero times across all 8 sampled PRs. Suggested:moduleDepth: 4, or better, a glob-based module map (packages/*/src/*). - actix-web — every file is
<crate>/src/<file>.rs, so depth 2 always resolves to<crate>/src: correct for cross-crate boundaries, but structurally blind to intra-crate seams (actix-http/src/h1vs.h2vs.ws). Not demonstrated as a false negative in this 8-PR sample. Suggested:moduleDepth: 3for Cargo workspaces. - cli-cli —
pkg/cmd/<command>/<subcommand>/file.gocollapses 40+ CLI subcommands into one “pkg/cmd” module, solayers.edgecan only ever notice coupling between top-level trees (pkg/cmd,internal/*,api), never between subcommands. - fastlane — every tool’s
lib/is one module at depth 2 (fastlane/lib,match/lib, …), correct for cross-tool edges but collapsing all 400+ files offastlane/lib/fastlane/actions/*.rbinto one. Suggested:moduleDepth: 4forfastlaneitself (other, smaller tool dirs are fine at 2). - black —
src/<pkg>is already 2 segments, so depth 2 correctly separatesblack/blackd/blib2to3but collapses the entire ~20-fileblackcore into one module.
4. Robustness and performance
- Fixed 120-second git-subprocess timeout, surfaced as an opaque, misleading error, with no CLI
override on
brief/score. cli-cli: the firstscorecall on a freshly-cloned blobless repo took 121.2s and failed (exit 2), citing git’s own benign"Auto packing the repository in background..."stderr chatter as the error reason; an identical retry succeeded in 1–7s. fastlane: the same failure mode ondocs-30414with defaulthistory.maxCommits: 20000on a 10+-year-old repo — confirmed in source (src/git/exec.ts) thatgit()hardcodestimeout: opts.timeoutMs ?? 120_000with no override exposed bybrief/score(onlycalibrate/backtestexpose--max-commits/--since). An isolated rawgit log -n20000 --name-status -Mtook 7m47s wall-clock but only 1.78s user + 2.92s system CPU — cheap in actual work, expensive only in wall time under contention, which the fixed timeout cannot tell apart from a real hang. git blamecalls are a significant, silently-degrading cost under slow-git conditions. actix-web: nearly every brief with more than one hunk printed a “blame unavailable” note; a manual repro of one suchgit blame -L ...call took over 120 seconds (it did eventually succeed). vite: blame-unavailable notes appeared on all 8 sampled PRs, 1–14 times each. Not a crash — opendrs degrades gracefully to a note — but it silently loses the “last changed by fix X” provenance that makes “Check specifically” lines concrete.calibrate/backtestcould not complete within the 15-minute time box on any of the six repos. black: killed after 13.5 minutes, no output. cli-cli:backtestalone consumed over 10 minutes with zero output across two attempts;calibratewas never started. actix-web:calibratekilled after 15 minutes, no.opendrs/model.jsonwritten. vite:calibratekilled at ~15:23 elapsed, having accumulated only ~1.4s of actual CPU time (starved on git spawns, not computing). fastlane:calibratekilled at exactly 600s (timeoutexit 124) with zero stdout/stderr. vapor:calibratekilled after 11m37s with no output; the broadpkillpattern used to kill it likely also killed a concurrent fastlane-repocalibrateprocess. No held-out AUC was obtained for any of the six repos. This field test cannot say anything about how well default or calibrated weights rank real PRs on these repos — only that the uncalibrated default tiers look saturated or flat on several of them (theme 1).
5. Rendering
- The captain “why” one-liner truncates almost every fact, sometimes to nothing. Confirmed in
black only (not checked against the other five repos’ output, though the bug is in shared
rendering code, not black-specific).
src/brief/build.ts’sfirstClausesplits each fact’s text on/[;.]/to take “the first clause,” but every fact names a file path with a.in its extension, and workflow paths often start with a dot. On PR d078e8e (#5296) the “why” rendered as two bare backticks — cut at the first.in.github, before any real content. On 8b9d3e3, 27c6fd0 and f705197 it cut mid-filename, e.g. dropping.py:30from a function’s file path. Since this is the line printed in the PR comment (**Captain: careful review** · @alice · why: ...perdocs/captain.md§5), this degrades the single most human-visible summary opendrs produces.
What to fix or build next, ranked
Sizes are as estimated in the source notes (S/M/L), not independently re-estimated here.
| Rank | Problem | Evidence | Suggested change | Size | Languages |
|---|---|---|---|---|---|
| 1 | Go import graph resolves against the wrong module (duplicate go.mod path, no tie-break) — silently zeroes contract.api, layers.edge, blast.* for ~1,039 files |
cli-cli: opendrs explain api/client.go / pkg/extensions/official.go → directDependents: 0; PRs a45729e87 (#14620), ba51bb477 (#14430) |
Resolve each import against the nearest enclosing go.mod only (stop at the first match, Go module semantics), or warn on duplicate module paths and exclude test-fixture trees | S | Go |
| 2 | Ruby public-declaration detector is an unconditional stub (ruby: () => false) — disables contract.api for 100% of Ruby code |
fastlane: refactor-30411 (#30411, 4 dead public classes), crosstool2-27547 (#27547, public method signature change with live callers) | Add a real heuristic: top-level def self.foo/def foo not under private/protected, plus class/module declarations |
M | Ruby |
| 3 | paths.tests defaults don’t match RSpec or Swift/XCTest conventions — tests.missing/tests.deleted give confidently wrong, not unknown, answers |
fastlane: all 8 PRs’ own *_spec.rb files flagged “no test changed”; vapor: #3592 (test-only) scored yellow/standard, #3593 (2 deleted test files) reported “no test deletion” |
Add **/spec/**, **/*_spec.rb (Ruby) and a case-insensitive **/Tests/** / **/*Tests.swift (Swift) to the default paths.tests list |
S | Ruby, Swift |
| 4 | pyproject.toml PEP 508 marker parser desyncs on nested quotes — fabricates dependency names and misses the real new one |
black: 8b9d3e3 (#4996), fake deps ,/win32 reported, real new dep winloop never reported |
Parse each quoted TOML string with its own matching delimiter (separate single-/double-quote regexes, unioned) instead of one alternating character class | S | Python |
| 5 | Captain “why” line truncates on any ./; inside a file path, sometimes to nothing |
black: d078e8e (#5296) rendered as two bare backticks; 8b9d3e3, 27c6fd0, f705197 cut mid-filename | In firstClause, require the period be followed by whitespace/end-of-string, or strip backtick-quoted spans before splitting |
S | All (shared rendering) |
| 6 | go.mod doesn’t distinguish // indirect from direct — misses real dependency promotions |
cli-cli: 2c7ec97fd (#14578), charmbracelet/x/ansi promoted + 3 libraries dropped, 0 facts |
Track direct and indirect name sets separately in parseGoMod; a direct-set addition previously only indirect is “new runtime dependency” |
S | Go |
| 7 | Cargo target-specific dependency tables ([target.'cfg(...)'].dependencies]) unscanned |
actix-web: 8487ab79 (#4335), new winapi-util dep, 0 facts |
Extend the manifest parser to also scan [target.*.dependencies] / [target.*.dev-dependencies] |
S | Rust |
| 8 | No Package.swift manifest support — not parsed, not in paths.block |
vapor: #3570 (2 new deps + 2 bumps), #3593 (removal), #3603 (version pin) — 0 facts, paths.blocked = 0 on all three |
Add **/Package.swift to paths.block (it executes code at resolve time like Cargo.toml/go.mod); add a line-regex parser for .package(url:...) entries |
S (block) + M (parser) | Swift |
| 9 | os.LookupEnv missing from contract.env’s Go pattern (only os.Getenv covered) |
cli-cli: 14 live uses across 7 files, confirmed present, not exercised by a sampled PR | Add /\bos\.LookupEnv\(["']([A-Za-z_]\w*)["']/ to ENV_PATTERNS |
S | Go |
| 10 | No Swift env-read pattern at all (ProcessInfo.processInfo.environment, Environment.get) |
vapor: #3593 deletes Environment.get with 0 contract.env signal |
Add Swift patterns to ENV_PATTERNS |
S | Swift |
| 11 | Swift has no import extractor or public-declaration rule — contract.api/layers.*/blast.* fully disabled |
vapor: #3552 (class→struct), #3464 (5 new public macros), #3593 (3 public removals) — all silent |
A minimal Swift import extractor (import Foo → module Foo); a swift entry in isPublicDeclaration (public/open, public macro). The two are coupled: contract.api also gates on dependentsOf().length > 0 |
L (extractor) + M (public-decl) | Swift |
| 12 | history.incident’s folder-level revert matching is too coarse for a large monorepo directory |
vite: one 8-file revert (2d66b7b14) flags +0.8 on hundreds of unrelated files under packages/vite/src/node/; confirmed on 5 of 8 sampled PRs |
Restrict folder matching to a shallow radius and/or decay the contribution by directory size or time since the incident | M | All (monorepo-shaped repos) |
| 13 | history.fixPattern conflates Conventional-Commit fix:/fix(deps) maintenance commits with real incident fixes |
vite: a6d310e42 (#23718) cosmetic docs PR scored 0.36/yellow from renovate fix(deps) commits; vapor: flat +0.80 history.incident on 7 of 9 PRs from routine “Fix …” titles |
Exclude routine maintenance scopes/types (fix(deps), chore) from fixPattern by default, or support a composable exclude-pattern |
M | All |
| 14 | Diffusion/history signals are raw historical counts, not normalized per repo — saturates tiers on old, active repos | black: 7 of 8 non-docs PRs scored red 0.80–0.96 regardless of size, because linegen.py/nodes.py have 200–350 authors/180+ commits a year simply from repo age |
Normalize diffusion/churn against the repo’s own file-level distribution (e.g. percentile within repo) instead of raw log1p counts; document that calibrate is close to required on a mature repo |
L | All |
| 15 | No “removed dependency” fact, and no major/minor/patch distinction, in contract.dependency |
vite: ce4293f15 (#23744), @vercel/detect-agent removed / std-env added, only the add is a fact; actix-web: efa9c213 (#4319), brotli 8→9 major bump treated like a patch bump |
Add a contract.dependency.removed fact; add an info-level fact for a major-version bump on an existing name |
S | All (manifest-level) |
| 16 | Cargo [features] table changes invisible to every signal |
actix-web: 7a3ef12d (#4213, http2 un-implied from TLS features), 3bb00075 (#4198, experimental-io-uring removed) |
Add a contract.feature signal that diffs [features] tables: a feature removed, or a feature’s implied-feature list shrinking |
M | Rust |
| 17 | Exported-interface field additions under-promoted — contract.api only catches new top-level declarations or changed/removed signatures |
vite: bc0f21c56 (#23463), HotCallback.exports field addition — the PR’s real public-contract change — never became a fact; a test helper’s new exports became the headline instead |
Promote exported-interface field additions to contract.api, not just new top-level declarations |
S–M | TypeScript (likely generalizes) |
| 18 | Default paths.critical (auth/, payment*/, billing/, security/) misses domain-specific critical code |
fastlane: sigh-30380 (#30380), a symlink-escape fix in sigh/lib/assets/resign.sh, scored green/quick/auto-approve; vapor: TLS/certificate code organized by file name, never matched by the folder-name heuristic |
Document and encourage a project-specific paths.critical override per repo; no generic default can cover domain-specific signing/crypto code |
S (config/docs) | All |
| 19 | moduleDepth: 2 default is wrong for every layout tested: packages/*/src (vite), <crate>/src (actix-web), pkg/cmd/* (cli-cli), <tool>/lib (fastlane) |
See Findings, theme 3 | Detect and skip a bare src/lib path segment so depth counts from the package root, or document per-layout recommendations (depth 3 for Cargo workspaces, depth 4 for vite/fastlane); consider a glob-based module map longer-term |
S (per-repo config) / L (structural) | All |
| 20 | Fixed 120s git-subprocess timeout with no override on brief/score, and an error message that surfaces git’s own stderr instead of saying “timeout” |
cli-cli: first score call failed at 121.2s citing git’s benign auto-packing message; fastlane: same crash with default history.maxCommits: 20000 on a 10+-year repo (raw git log -n20000 took 7m47s wall / 1.78s user+2.92s system CPU) |
Check err.killed/err.signal in git()’s reject path and report “timed out after Nms” distinctly from stderr content; expose --max-commits/--since on brief/score the way calibrate/backtest already do |
S/M | All |
| 21 | git blame spawned once per hunk range is slow and failure-prone under contention, silently losing review-focus provenance |
actix-web: every brief with >1 hunk printed blame-unavailable notes; vite: blame-unavailable notes on all 8 PRs, 1–14 times each | A configurable blame timeout, plus a single combined git blame call instead of one per hunk range |
S | All |
| 22 | calibrate/backtest could not complete within a 15-minute time box on any of the six repos; no AUC obtained anywhere in this field test |
All six notes; see Findings, theme 4 | Progress output during the history replay; a documented fast-start recipe (narrow --since, capped --max-commits) for a first run on an unfamiliar repo |
M | All |
What could not be reconciled
- black’s per-PR table reports 14 fact instances total, but the note’s own per-fact judgment section
only individually classifies 13 of them (1ed66a8 is credited with 4 facts in the table but only 3
— two
contract.api, onecoupling.missing— are discussed). The note does not say what the 14th fact was. - vapor’s session tested 9 PRs, one more than the ~8 the other five repos tested; the note does not explain the extra PR.
- fastlane ran with a non-default
history.maxCommits/history.lookbackDays(forced by a crash under default settings) and a full, non-blobless clone, unlike the other five repos. Its detector and fact-quality findings are directly comparable to the other five repos; its raw score numbers are not. - vapor’s note flags that the broad
pkillpattern used to kill its own stuckcalibraterun likely also killed a concurrent fastlanecalibrateprocess on the shared machine. Neither note confirms whether that fastlanecalibraterun was the same one reported as killed at 600s in fastlane’s own note, or a separate, unreported casualty.
Updated 21 Oct 2026 · Edit this page on GitHub