Read-only audit log. Findings are immutable F-IDs; the status field tracks state.
Shipped/fixed findings are condensed to a one-line closure row once validated against main (full history in CHANGELOG.md + git); refuted findings stay documented to prevent rediscovery.
Last pass: 2026-07-02. The 2026-07-01 validation + 5-dimension discovery pass added F200–F230; the 2026-07-02 implementation pass landed 28 of them on main (PRs #71, #74) and refuted F188/F202. See §3 "Implemented" tables + §5 for the current disposition.
CodeLore is a multi-crate Rust workspace:
- codelore-rca: Vendored fork of Mozilla
rust-code-analysis— structural syntax hashing + complexity metrics. Hands-off (MPL); out of audit scope. - codelore-lib: Core engine — repository walk abstraction, identity resolution, fact-store management, analyses, caching, output emitters.
- codelore-cli: Argument parsing, option consolidation, dispatch, output routing.
graph TD
A[GixRepo / GitCliRepo] -->|walk_commits → CommitEvent stream| B[Bounded crossbeam channel]
B -->|producer → consumer| C[FactsDb ingest]
C -->|DuckDB Appender bulk-insert| D[(DuckDB fact store)]
E[HEAD-time blob walk @ HEAD] -->|tree-sitter parsing via rayon| F[Complexity + clones + imports extraction]
F -->|HEAD-time metrics| D
D -->|SQL views / parameterized queries| G[42 behavioral analyses]
G -->|emitters| H[CSV · JSON · SARIF 2.1.0 · Markdown · Parquet · SQLite · HTML · SPA · GHA]
- Repository Traversal:
GixRepo(pure-Rustgitoxide, hot path) +GitCliRepo(differential-testing oracle). - Event Ingestion:
duckdb::Connectionis!Send + !Sync. Producer-consumer: background thread walks commits → boundedcrossbeam-channel→ connection-owning thread runs DuckDB Appender (facts/ingest/consumer.rs::ingest_loop). - HEAD-time work: complexity, clones, imports extraction read blobs from the gix ODB, parse via tree-sitter on a rayon pool, drain serially into the DuckDB Appender.
- SQL-Driven Analyses: 42 behavioral analyses run as parameterised DuckDB queries. Path-aggregating analyses opt into rename-aware aggregation via the
changes_lineageCTE rewriter.
All prior findings (F1–F87) shipped and were validated against main. Per-finding evidence is in CHANGELOG.md. Audit-trail summary:
| Batch | Scope | Outcome |
|---|---|---|
| F1–F17 | Schema timestamps, chunked walker, rename-aware aggregation, CLI-boundary validation | Shipped |
| PAR-1–PAR-9, DEEP-1–DEEP-15 | Code-maat parity sweep | Shipped |
| F18–F28 | Back-test isolation, HTML pagination, cache-write concurrency, parallel filtering | Shipped |
| F29–F34 | Time-bucket changeset semantics, path-relative skip checks, binary diff guards | Shipped |
| F35–F42 | Numstat brace expansion, explain-mode params, quadratic Kamei rewrite | Shipped |
| F43–F56 | Blob clone elision, single-pass templating, COUNT(DISTINCT) elimination, SPA X-Ray join | Shipped |
| F57–F67 | ECharts theme reload, prefix matching, ODB blob reads, hash aggregation sweep, SIMD line counting | Shipped |
| F68–F76 | AI attribution rollup, lockstep rev equality, lineage rename-index, NULL-safe distinct elimination | Shipped |
| F77–F87 | Bare-repo clone discovery, theme-controller migration, multi-column SPA grid, cognitive-color sunburst, JSX/TSX grammar coverage | Shipped |
| F84, F88 | Refuted at source-quote level | Refuted (preserved) |
Every row below was re-verified against main HEAD by read-only source inspection on 2026-07-01. Fixed rows are condensed to one line (details in CHANGELOG.md / git). Rows still open carry forward to §4.
| F-ID | Subject | Status |
|---|---|---|
| F89 | Producer-thread .expect panic mapping |
Fixed (8e52984) |
| F90 | SPA X-Ray sunburst hardcoded ring colors | Fixed (7f36a7f) |
| F91 | Markdown emitter unescaped | in cells |
Fixed (7f36a7f+49196ad) |
| F92 | Provenance sidecar atomicity gap | Fixed (38df3d0) |
| F93 | cache_key silent canonicalize fallback |
Fixed (8e52984) |
| F94 | ingest.rs monolithic → facts/ingest/ module split |
Fixed |
| F95 | communication.rs window filter |
Refuted (filter at ingest level) |
| F96 | ECharts mount + dispose duplicated | Fixed (7f36a7f) |
| F97 | SPA boot-time render storm blocks first paint | Fixed (async bootWidgets) |
| F98 | Chart-click drawer no keyboard equivalent | Fixed |
| F99 | Container OCI label <owner> placeholder |
Fixed (f6848e6) |
| F100 | cut-release.sh trap hang on stuck gh api |
Fixed (f6eb953) |
| F101 | CI cache keys omit rust-toolchain.toml |
Fixed (f204088) |
| F102 | bench.yml kernel-snapshot no error handling |
Fixed (957b3dd) |
| F103 | softprops/action-gh-release@v3 mutable |
Fixed (dc6ec60) |
| F104 | Fisher-exact contingency degenerate cells | Fixed (b15da46) |
| F105 | ureq = "2" maintenance-only |
Fixed (ec33cf9; now ureq 3) |
| F106 | Provenance manifest no schema-version | Fixed (7f36a7f) |
| F107/F108 | SPA runtime errors hotfix | Shipped (v0.5.1) |
| F109 | diff_output.rs missed by F91 sweep |
Shipped (PR #53) |
| F110 | Differential test only 4 of 8 trait methods | Fixed (PR #57) |
| F111 | FactsDb::conn() leaks &Connection |
Fixed (pub(crate) + safe methods) |
| F112 | Provenance manifest missing reproducibility fields | Fixed (PR #57) |
| F113 | CLI reaches into many lib submodules — no façade | Fixed (cli_api) |
| F114 | Single-CDN dependence for SPA assets | Fixed (url_fallbacks) |
| F115 | Container base images use mutable tags | Fixed (@sha256: pins) |
| F116 | Renovate AND Dependabot duplicate ecosystems | Refuted (partitioned by ecosystem) |
| F117 | First-party GHA credential actions use floating tags | Fixed (SHA-pinned) |
| F118 | gix walker thread panic silently swallowed | Fixed (PR #62) |
| F120 | SARIF schema URL on legacy host | Fixed (canonical schemastore URL) |
| F121 | fishers_exact crate unmaintained |
Fixed (in-tree stats::fisher_two_tail_pvalue) |
| F122 | toml = "0.8" one major behind |
Fixed (toml = "1") |
| F123 | codelore-rca crossbeam/num-format stale | Refuted (both are current releases) |
| F124 | MSRV pin has zero buffer | Fixed (documented policy) |
| F125 | Redundant queries fire 4× per ingest | Fixed (PR #58, hoisted once) |
| F126 | N single-row UPDATEs in resolve_imports | Fixed (PR #58, bulk UPDATE…FROM) |
| F127 | Kamei enrich_diffusion correlated subqueries |
Fixed (full, incl. entropy block) |
| F128 | Kamei enrich_size correlated subqueries |
Fixed (PR #64) |
| F129 | arch_violations materialise-then-truncate |
Fixed (direct-iterate + early-break) |
| F130 | pair_programming O(P²) with String::clone |
Fixed (integer-interned keys) |
| F131 | Provenance tooltip 14×14 px target | Fixed (24×24, WCAG 2.5.5) |
| F132 | Hardcoded hex in widgets.js breaks light theme | Fixed (CSS tokens) |
| F133 | No responsive layout < ~1280px | Fixed (md:grid-cols-2) |
| F134 | Hotspot 'Show all' synchronous HTML build | Fixed (chunked + yieldToMain) |
| F135 | Theme toggle re-runs full d3.pack layout | Fixed (yield between rerenderers) |
| F136 | Color-mode tablist mismatches WAI-ARIA | Fixed (aria-selected) |
| F137 | Knowledge-islands rows not keyboard-activable | Fixed (wireRowKbActivation) |
| F138 | startViewTransition ignores reduced-motion |
Fixed (PR #62) |
| F139 | DiffGates parsed but never evaluated |
Fixed (549c460) |
| F140 | Six new analyses lack integration tests | Fixed (7b43593) |
| F141 | imports_factsdb_test only asserts unresolved |
Fixed (7b43593) |
| F142 | Sparse tracing across analyses/ |
Fixed — residual: 6 dashboard.rs fns still uninstrumented → new F224 |
| F143 | SPA headless-browser smoke test | Fixed (PR #56) |
| F144 | No CI dogfooding of codelore on itself |
Fixed (dogfood job) |
| F145 | main.rs dispatch boilerplate |
Fixed (1-D dispatch fns) |
| F146 | json.rs trivial write_*_json shims |
Fixed (generic write_json) |
| F147 | AnalysisName 3-way sync no exhaustiveness guard |
Fixed (registry! macro) |
| F149 | hunks table lacks PK + NOT NULL + index |
Fixed (schema + full ingest wiring) |
| F150 | Schema version disjoint, no startup validation | Fixed (PR #61) |
| F151 | Leiden communities non-deterministic | Fixed (PR #61, LEIDEN_SEED) |
| F152 | clone_group_id non-deterministic |
Fixed (PR #61, BTreeMap) |
| F153 | --team-map config IO error exits 5 not 3 |
Fixed (RepoIo) — residual: other config readers still wrong → new F211 |
| F154 | codelore diff base==head empty SARIF |
Fixed (PR #62) |
| F155 | DiffOutput medians default silent 0.0 |
Fixed (PR #60, Option<f64>) |
| F156 | Thresholds/Gates/DiffGates no deny_unknown_fields |
Fixed (PR #60) |
| F157 | F147 guard wraps the wrong list | Fixed (PR #60) |
| F158 | SARIF informationUri wrong project URL |
Fixed (PR #63) |
| F159 | SARIF artifactLocation.uri not percent-encoded |
Fixed (PR #63) |
| F160 | Kamei NDEV/EXP same-second < vs <= |
Fixed (PR #64) |
| F162 | Parquet column types drift from CSV row-type | Fixed (shared SQL generators) |
| F163 | SARIF automationDetails.id static |
Fixed (PR #63) |
| F164 | Task-ID (F<NN>) refs in code comments |
Fixed — residual: Plan 1/4 markers in gix_repo.rs → new F205 |
| F165 | --format ndjson/gha panics unreachable! |
Fixed (clean bail!) |
| F166 | codelore schema row-type list drifted |
Fixed (derives from AnalysisName::all()) |
| F167 | stale-code/delivery-friction wall-clock anchor | Fixed (MAX(commits.date) + age_time_now) |
| F168 | lead-time ORDER BY lacks tiebreaker |
Fixed (, rev ASC) |
| F169 | Treemap breadcrumb undefined --bg-elev-1 |
Fixed (--bg-elev) |
| F170 | CSV emitter no formula-injection guard | Fixed (' prefix + force-quote) |
| F171 | bus-factor drops root files + not rename-aware |
Fixed (<root> bucket + lineage) |
| F172 | Calendar heatmap Math.min.apply RangeError |
Fixed (single-pass loop) |
| F174 | run_coupling recomputed 2–5×, no memoization |
Fixed (per-FactsDb Rc memo) |
| F175 | SPA detail drawer no focus management | Fixed (focus enter/return + aria-labelledby) |
| F176 | Six SQL analyses in output/spa.rs, exit-5 leak |
Fixed (moved to analyses/dashboard.rs, exit-4) — residual: no tests/tracing → new F223/F224 |
| F178 | query_map_collect under-adopted |
Fixed (~73% adoption) |
| F179 | Tablists lack arrow-key nav / roving tabindex | Fixed (full WAI-ARIA tabs pattern) |
| F180 | Charts expose no text alternative | Fixed (role=img+aria-label at 12 sites) |
| F181 | No prefers-reduced-motion CSS block |
Fixed |
| F182 | Dynamic updates silent to screen readers | Fixed (aria-live summary) |
| F183 | Selection-listener stale-closure leak | Fixed (dedup-by-source) |
| F184 | changes_lineage rebuilt every analysis call |
Fixed (build-once guard) |
| F185 | Clone Fingerprint retains unused sequence |
Fixed (field removed) |
| F187 | just test diverges from CI invocation |
Fixed (feature scope matches CI) |
| F189 | vendor-duckdb-rs.sh no retry / mutable-tag TOFU |
Fixed (retry + SHA pin + stamp) |
| F190 | explain covers 15/32; no anti-drift test |
Fixed (31 topics + enforced coverage test) |
| F192 | mi/communities/centrality no run_* tests |
Fixed (3 integration tests) |
| F193 | resolve_imports_at_head builds path set twice |
Fixed (built once, shared) |
| F194 | Kamei enrich re-materializes changes_lineage |
Fixed (subsumed by F184 guard) |
| F195 | deny.toml multiple-versions, no skip-list |
Fixed (explicit skip-list) |
| F196 | release.yml no sccache warm-cache reuse |
Fixed (sccache wired) |
| F198 | Two SQL source-swap mechanisms, false-symmetry doc | Fixed (doc corrected; both intentional) |
| F199 | Options::validate() reports Provenance variant |
Fixed (InvalidOptions + test) |
Implemented 2026-07-01 (this session — CI-exact gate green: fmt + clippy --all-features -D warnings + test --features test-support,spa (75 binaries) + cargo deny)
| F-ID | Subject | Fix |
|---|---|---|
| F191 | Usage errors (--complexity-sample) exit 1 |
Typed CodeLoreError::InvalidOptions (exit 2); leaked Plan markers dropped from the user-facing message |
| F201 | read_blob_at directory-path divergence |
GitCliRepo uses git cat-file blob (errors on non-blob → Ok(None), matching GixRepo); differential test read_blob_at_returns_none_for_a_directory_path |
| F203 | Dead Options.commit_range knob |
Field + Default + two doc refs removed; cache key auto-updates via the serde derive |
| F204 | Dead CodeLoreError::Provenance variant |
Variant removed; exit-code test re-anchored on InvalidOptions |
| F205 | Stale banned Plan 1/4 markers on compute_changed_files |
Comment rewritten to the current contract (real loc + hunks). Broader Plan N sweep → new F231 |
| F207 | cycle-origins no rev-keyed graph memo |
HashMap<rev, Rc<ImportGraph>> shared across bisections via graph_at_rev_cached |
| F208 | Structural import graph rebuilt per arch analysis | Per-FactsDb Rc<ImportGraph> memo; build_import_graph returns the shared handle |
| F209 | apply_grouping row-by-row INSERT |
DuckDB Appender + flush() |
| F210 | clone-coupling 4 String clones/edge |
Borrowed (&str, &str) probe-map keys — zero clones |
| F211 | Config file-reads → Analysis not RepoIo |
arch-rules / thresholds / group-file read failures → RepoIo (exit 3); parse failures stay Analysis (exit 4). Finishes the F153 job |
| F212 | unwrap_or_default masks head-rev read error |
Typed map_err (consistent with the surrounding plumbing) |
| F213 | pair_programming dead params! lint-defeat |
use duckdb::params; + throwaway let _ = params![…] removed |
| F214 | is_bot allocates 2 Strings/commit |
Non-allocating ASCII contains_ignore_ascii_case; both is_bot sites |
| F216 | SPA coupling Sankey/drawer read non-existent fields | Read real shared/degree; band width, "top 30" sort, and drawer label all corrected |
| F217 | Reset-zoom buttons never installed (async-boot race) | Idempotent installer re-run at end of bootWidgets() |
| F219 | Trends shortPath label collisions |
Collision falls back to the unique full path; series keyed by the disambiguated label |
| F220 | Calendar visualMap degenerate at min===max |
Anchor low end at 0 so the single value paints a visible band |
| F221 | Tooltip ? in <th> triggers sort |
Sort handler skips clicks originating from .tooltip-trigger |
| F222 | Hotspot zero-filter-match blank body | Inline "No paths match '…'" empty-state row |
| F223 | Six dashboard.rs fns no integration test |
New tests/dashboard_test.rs drives all six over the ingested fixture |
| F224 | Six dashboard.rs fns lack tracing spans |
#[tracing::instrument] on all six (matches every sibling analysis) |
| F225 | No exit-2 CLI test | invalid_options_exit_with_code_2 (--min-coupling > --max-coupling → exit 2) |
| F226 | build_import_graph_from_edges HashSet iteration order |
Sorted edge list before adjacency build (removes per-process nondeterminism) |
| F227 | ANY_VALUE() nondeterministic in bucketed/grouped ingest |
arg_max(…, ROW(m.date, -m.rowid)) (bucketed) / arg_max(…, c.path) (grouped) |
| F228 | hotspot-velocity window consts doc-only | SQL is now a {recent}/{baseline}/{boundary} template resolved from the consts (byte-identical) |
Deep validation of the deferred backlog: one clean win shipped, two findings refuted as intentional designs, one confirmed ready-to-execute.
| F-ID | Subject | Outcome |
|---|---|---|
| F200 | commit_metadata stub divergence + vacuous differential test |
Fixed — deleted the unused commit_metadata trait method (both backends) + the CommitMetadata type + the vacuous commit_metadata_match test + two other dead references. Kept changed_files/diff_hunks (their differential tests are real cross-checks, not vacuous). Narrower than "delete all three oracle-only methods" — only commit_metadata was both divergent (gix stub vs cli-real) and vacuously tested. Gate green: fmt + clippy --all-features -D warnings + test (75 binaries) + deny. |
| F188 | cut-release.sh ruleset body omits spa-browser/dogfood |
Refuted — the omission is intentional + documented. cut-release.sh:230-232 explicitly names spa-browser/dogfood as known-excluded; dogfood is continue-on-error (correctly not a required gate); and a live-vs-hardcoded drift-detector (:250) guards against divergence. Adding spa-browser would be a policy change (make it gate release tags), not a bug fix — the maintainer's call. |
| F202 | Fan-in/out computed three inconsistent ways | Refuted (mostly) — god-classes fan_out intentionally counts external (npm/pypi/std) imports (documented as "total dependency breadth"), while crossing/instability measure internal coupling. That divergence is by design. The only genuine gap is crossing-vs-instability self-loop handling (a rare re-export edge case) — low value, output-changing, needs a semantics decision. Not the broad "three different numbers" defect the finding implied. |
| F229 | Vendored libduckdb-sys fork (duckdb-rs#786) |
Fixed (merged to main via #71 — full CI matrix green incl. test (windows-latest)) — bumped duckdb → =1.10504.0 (upstream #786 fix; the released build.rs:66 emits rustc-link-lib=dylib=rstrtmgr) and removed the whole vendoring apparatus: the [patch.crates-io] block, vendor/duckdb-rs/ + tracked stubs, scripts/vendor-duckdb-rs.sh, patches/duckdb-rs-msvc-1940.patch, the .gitignore stub-path handling, and the "Vendor patched libduckdb-sys" step at all 9 sites across ci/release/container/bench. No arrow-version conflict; a version-drift guard caught + forced provenance::DUCKDB_VERSION + banner test refs to 1.10504.0. The MSVC build (the whole reason the fork existed) is confirmed green on the Windows runner. |
F84, F88 (silent ODB skip rationale), F95 (window filter at ingest level), F116 (Renovate/Dependabot partitioned by ecosystem — Renovate owns cargo, Dependabot owns github-actions), F123 (crossbeam 0.8.4 + num-format 0.4.4 are current releases), plus the earlier-report set: apply_grouping JOIN shape, renderHeader listener leak, parquet/SQLite backslash escape, hotspots CTE leak, color-mode aria-label, Kamei SEXP < vs <=, tree-sitter kind_id ABI, AI-assist false positives, NULL-conflated AI attribution, DuckDB pinning speculation, code-health weights citation, SoC inclusive thresholds. Rationale in f1aa0e7 (PR #36) + 13fefcb (PR #38).
- Location:
facts/ingest/mod.rs:145,158,165(three sequential passes); each*_head.rs:55independently callsread_blob_at_head - Severity: HIGH · Category: performance (redundant I/O) · Status: Active
- State on main: Still three sequential HEAD passes each re-reading live blobs. Only
head_rev/live_pathswere hoisted once (SQL path-queries no longer repeated); blob reads + tree walks still happen 3×. Deepened by the newly-found per-file re-resolution cost in F206 — even a single deduped pass keeps paying F206's per-file HEAD/commit/tree decode. - Deferral blocker: divergent extractor error contracts (clones aborts ingest via
collect::<Result>>?; complexity/imports warn-and-skip) + the memory-regression risk of hoisting all live blobs into one map. Needs a bounded shared-blob LRU or unified error contracts first.
- Location:
output/csv.rs - Severity: MED · Category: tool replacement · Status: Active (re-scoped)
- State on main: Still hand-rolled (
wc -l= 1122, up from ~826; nocsvdep). Re-scope note: no longer a clean byte-identical swap — the emitter now carries a deliberate formula-injection guard (F170) and\nline endings; thecsvcrate would change both. Any migration must preserve the injection guard + line-ending contract, or the swap is rejected.
- Location:
output/csv.rs(~34 KB, 43write_*fns),output/markdown.rs(~36 KB) - Severity: LOW · Category: copy-paste drift · Status: Active
- State on main: Both grew past the previously-noted ~25 KB; still one
write_*fn per analysis, noTabularEmit/row trait. Coupled to F119 (csv-crate) + F161 (streaming) — treat as one output-layer cluster.
- Location:
output/json.rs:29,sarif.rs:90,markdown.rs— allrows: &[T] - Severity: LOW · Category: memory architecture · Status: Active
- State on main: All emitters still take a fully-materialized slice; no
EmitterStream. Peak memory grows with row count; a 200k-path monorepo CSV export can spike multi-GB. SARIF stays batch (needs run-level totals); CSV/JSON/markdown are the streamable targets.
- Location:
facts/schema.rs:10(CURRENT_SCHEMA_VERSION="3"),cache.rs:25(CACHE_EPOCH="schema_v5"),schema_v1.sqlfilename literal; stray"schema_v3"help-text atmain.rs:373 - Severity: MED · Category: duplicated source-of-truth · Status: PARTIAL
- State on main: Both named sub-fixes landed — CLI
profilenow derives the schema string fromCURRENT_SCHEMA_VERSION, and the cache sentinel was renamed to the honestCACHE_EPOCH(matches CLAUDE.md). But three version constants remain structurally disjoint (none derived from another) and a stray"schema_v3"help literal persists. Residual: unify or cross-reference the three; fix the stray literal.
- Location:
.github/workflows/bench.yml:3(schedule+workflow_dispatch, nopull_request),:116(fail-on-alert: false) - Severity: MED · Category: CI coverage / design tradeoff · Status: Active (design decision)
- State on main: Unchanged and explicitly documented as intentional post-merge advisory behavior. Kept as a design-review item, not a plain bug: a perf regression can merge unflagged until the Monday cron. Decision point — leave advisory, or add a non-gating PR-triggered bench comment.
- Location:
.github/workflows/ci.yml:210(continue-on-error: true),:231(shared-key: release-dogfood),:235(cargo build --release) - Severity: LOW · Category: CI cost · Status: PARTIAL
- State on main: The "cold" aspect is mitigated (sccache warms release objects cross-workflow), but the job is still a per-PR
--releasebuild, advisory-only, on a deliberately separate cache slot. Residual (deliberate bake-in): decide when to gate + whether to share the CI cache slot.
The 5-dimension fan-out logged F200–F230; 25 landed in the 2026-07-01 pass and F200 (+ F188/F202 refutations) in the 2026-07-02 pass (see the §3 "Implemented" tables). The entries below are the deferred remainder — each is a large refactor with regression surface, a dependency-migration needing CI validation, or a low-value mechanical sweep, not a quick safe change.
F206 — read_blob_at re-resolves HEAD→commit→root-tree per file and discards the gix object cache each call
- Location:
repo/gix_repo.rs:293-329(to_thread_local()per call →rev_parse_single→find_commit→commit.tree()→lookup_entry_by_path); default wrapperrepo/mod.rs:99-101 - Severity: HIGH · Category: blob I/O / redundant recomputation · Status: Deferred (own perf pass)
- Description: Every HEAD-time blob read mints a fresh thread-local
Repository(cold object cache), re-resolvesHEAD, re-decodes the commit + root tree, and re-walks + re-decodes every intermediate directory tree — for each file. A file at depthdre-decodesdtree objects; every sibling re-decodes its parent tree again. Three HEAD passes × F live files = 3F redundant resolves. This is distinct from and deeper than F173 (which only dedups the blob across passes — the per-file HEAD/commit/tree re-decode remains even in one deduped pass). Dominant cost of HEAD scans on large deep-nested monorepos. - Suggested improvement: Resolve HEAD → root tree once per pass and reuse it (batch
read_blobs_at_head(paths)walking a single cached tree), or hold oneto_thread_local()repo withobject_cache_sizeenabled across the file loop. Same bytes returned — output-neutral, faster. - Deferral reason: Restructures the hot HEAD-scan loop and overlaps the F173 blocker (divergent extractor error contracts); wants its own focused perf pass rather than riding this batch.
- Location:
codelore-cli/src/main.rs:705+ sibling dispatch fns; alsoargs.output…expect("validated above")at:751/757 - Severity: LOW · Category: type-safety / simplification (optional) · Status: Deferred (large refactor)
- Description:
--formatis validated once then re-matched in many dispatchers, each carrying anunreachable!("format validated…")arm — a hand-maintained invariant a parse-onceenum Formatwould make compile-time-total, deleting the arms + the "validated above" coupling. - Suggested improvement: Parse
--formatinto aFormatenum at the boundary and thread it through dispatch. A non-trivial refactor across ~11 dispatchers — flagged, not forced.
- Location:
output/spa/template.html(oneAlpine.effectsubscribing to all layout knobs → all_codeloreRerenderers) - Severity: MED-HIGH · Category: render performance · Status: Partially Fixed (Unreleased)
- Description: Bumping the Kamei window 30→60 (one sparkline) re-runs
d3.packover the whole hotspot tree, rebuilds every ECharts instance, and re-lays-out the arch graph + DSM. The code yields between rerenderers to stay responsive — treating the symptom. The scenario toggle also auto-clicks the knowledge-loss tab, double-rendering the circle-pack on the first pick. - Partial fix: Split the monolithic
Alpine.effectinto (a) a pure theme effect that reads onlystore.theme.isDarkand fires_codeloreRerendererswith cooperative yield (F135), and (b) a separate layout/offboarding effect that readsstore.layout.*andstore.scenario.departed— so theme toggles no longer chain through layout/scenario subscriptions or trigger the CSS-token invalidation pass from unrelated clicks. The cross-widget selection and brush effects were already separate. - Residual (open): layout/scenario changes still fire ALL registered rerenderers — the headline Kamei-window scenario above is unchanged. Remaining follow-on: key the rerenderer registry by the store fields each widget depends on, so a layout change re-renders only its subscribers.
- Location: 62 comment sites across 25 files (validated 2026-07-02 via
grep -rn "Plan [0-9]" crates/codelore-lib/src crates/codelore-cli/src→ 62; files includetypes.rs,analysis.rs,constants.rs,options.rs,output/{mod,sarif,parquet}.rs,provenance/mod.rs,clones/*,complexity/*,repo/{mod,git_cli_repo,gix_repo}.rs,facts/{schema_v1.sql,ingest/*},arrow_facade.rs,codelore-cli/src/{args,diff,diff_output,main}.rs) - Severity: LOW · Category: comment rule violation (no version/task markers) · Status: Active (deferred — dedicated scripted sweep)
- Description: F164 swept
F<NN>finding-IDs out of comments but leftPlan Nphase markers, which are the same banned class under the project's no-version/task-markers-in-comments hard rule. F205 fixed the one factually-wrong instance (gix_repo.rs:355-356); 62 more remain, several also stale (e.g.repo/mod.rs:1-2"the default impl isgixin Plan 1; aGitCliRepo… lands in Plan 6"). Deferred as a dedicated scripted sweep (like F164) rather than 62 hand-edits riding an unrelated branch — mixing prefixes, parentheticals, and stale future-tense claims, so a blindsedwould mangle grammar. - Suggested improvement: A mechanical comment-only sweep like F164's — drop each
Plan Nmarker, keep or correct the surrounding rationale (some are false future-tense claims, e.g. "Plan 4 will add X" for X already shipped). Leave vendoredcodelore-rca(MPL fork) untouched.
Dependency currency (verify latest before acting — assessed offline from declared/resolved versions)
Overall hygiene is strong: thiserror 2, toml 1, ureq 3, anyhow/serde/clap/rayon/time/percent-encoding all current. tree-sitter* + petgraph are deliberately pinned (CLAUDE.md) — out of scope. Two items worth active tracking:
- Location:
crates/codelore-lib/Cargo.toml - Severity: LOW · Category: dependency currency (routine) · Status: Fixed (merged via #74)
- Outcome (2026-07-02): bumped
gix 0.84 → 0.85(latest, published 2026-06-22) withprovenance::GIX_VERSION+ banner refs. API-compatible — the two-backend differential harness (differential_repo_test.rs) passed unchanged, soGixRepostill matches thegit-CLI oracle. Consolidated Dependabot #68 (whose only failure was the drift guard) into #74; full CI matrix green. Related closed dep PRs: #69 (arrow 58→59 — deferred, would desync from duckdb's pinned arrow 58), #70 (duckdb group — superseded by F229).
- Location:
analyses/code_health.rs—SHOTGUN_INSERT(readscoupling_centrality_v1, writesshotgun-surgerybiomarker) +normalizedCTEn_cpterm (also readscoupling_centrality_v1directly) - Severity: LOW · Category: scoring design / weight calibration · Status: Fixed (Unreleased)
- State on main:
coupling_centrality_v1previously fed the composite score via two independent paths: (1) theshotgun-surgerybiomarker (intensity = PERCENT_RANK(ORDER BY centrality)) flowing intostructural_risk; and (2) directly asn_cp = normalize(centrality). A high-centrality file was penalised through both at once — a double-count of the same signal. - Fix: the
n_cpterm and its centrality join were removed from the score SQL; coupling now enters the composite once, as the shotgun-surgery biomarker insidestructural_risk. Score reweighted tow_sr = 0.50, w_cn = 0.30, w_au = 0.20(sum 1.0).CACHE_EPOCHbumped.
F233 — Implicit cross-analysis contract: refactoring-targets consumes code_health_biomarkers_v1 temp table as a side-effect
- Location:
analyses/refactoring_targets.rs(dominant-biomarker lookup query readscode_health_biomarkers_v1);analyses/code_health.rs(materialize_biomarkerscreates the table as a side effect ofrun_code_health) - Severity: MED · Category: implicit contract / latent-robustness · Status: Fixed (Unreleased)
- Description:
refactoring-targetsis the first external consumer of thecode_health_biomarkers_v1temporary table, whichrun_code_healthmaterialises as a side effect viamaterialize_biomarkers. This elevates a private implementation detail ofcode-healthinto an implicit cross-analysis contract: ifcode_healthdropped the table before returning, or the call order inrun_refactoring_targetschanged so the biomarker query ran beforerun_code_health, the dominant-biomarker lookup would fail at runtime with a DuckDB "table not found" error. - Fix: the
BIOMARKERS_DDLinanalyses/code_health.rsnow carries a doc-comment documenting the external consumer and the session-scoped contract (must stay session-scoped and readable afterrun_code_healthreturns; columns not renamed without updating the consumer). The tworefactoring_targetsintegration tests exercise the full path and guard a runtime regression.
- Location:
analyses/refactoring_targets.rs—loc_by_path.get(...).unwrap_or(0).max(1)used for both the displayedloccolumn and the priority denominator - Severity: LOW · Category: display fidelity / cosmetic · Status: Fixed (Unreleased)
- Description: A file with no non-NULL LOC entry (e.g. a file the complexity walker skipped) had its
locstored as0.max(1) = 1— a fabricated value propagated directly into the output row. TheEA_Z_FLOOR(25) already floors the priority denominator independently, so the.max(1)affected only the displayedloc. - Fix:
refactoring-targetsnow reports the trueloc(0= no LOC data); the EA-Z effort floor is confined to the priority denominator (max(loc, EA_Z_FLOOR)). The integration-test assertion was updated accordingly.
- Location:
analyses/code_health.rs—BIOMARKERS_INSERT(ranked/MAX rollup) +file_structural(probabilistic-OR aggregate) - Severity: HIGH · Category: metric quality / scoring · Status: Fixed (Unreleased)
- Description: Empirical run on this repo showed 67 of 69 files at
structural_risk = 1.0000and 100%redband — the metric did not discriminate. Two compounding causes: (1)BIOMARKERS_INSERTranked FUNCTIONS byPERCENT_RANKthen took the per-fileMAX, so any file with enough functions had one in the top percentile → intensity ≈ 1.0; (2)file_structuralcombined intensities with a probabilistic-OR (1 − Π(1−intensity)) × co-occurrence multiplier, which drove any file with ≥2 high smells to theLEAST(1.0, …)clamp. The per-row invariant tests (range/monotonicity/determinism) all passed while the distribution was degenerate. - Fix: (1) rank FILES not functions — aggregate to the file first (
MAXper file), thenPERCENT_RANKacross files, so each smell's intensity is uniformly spread; (2) replace the probabilistic-OR with a bounded weighted sum (per-smell weights summing to 1.0, absent smells contribute 0, co-occurrence implicit). Empirical result on this repo:structural_riskspreads0.01–0.96, band split 8 red / 32 yellow / 31 green (thresholds0.55 / 0.28). Added a distribution regression test (code_health_structural_risk_discriminates) on a purpose-builtbiomarker_repofixture. Residual (Phase-2): small per-language cohorts (e.g. a handful of JS files) makePERCENT_RANKcoarse — the cross-repo corpus percentile addresses this.
F236 — biomarker normalization inconsistency: god-class/dry used min-max while others used percentile
- Location:
analyses/code_health.rs—materialize_biomarkers(god-class + dry insertion) - Severity: MED · Category: metric quality / scoring · Status: Fixed (Unreleased)
- Description: After F235, three biomarkers (complex-method, large-method, shotgun-surgery) used per-file
PERCENT_RANKbut god-class and dry still used min-max/max— an outlier-dominated scheme that under-discriminated (one extreme god class compresses the rest toward 0). Naively switching them toPERCENT_RANKamong files that have the smell was worse (a lone or tied occurrence — e.g. two files each with one clone — collapses to a 0 rank, losing the DRY signal entirely; verified on the fixture). - Fix: god-class and dry now rank their raw metric over the FULL per-language file universe (absent files contributing 0), matching complex/large's scheme exactly (same
cyclomatic IS NOT NULL AND loc IS NOT NULLfilter). A file with the smell ranks above the zero-majority (e.g. a tied duplicated pair → ~0.8, not 0 or a min-max 1.0). (shotgun-surgery keeps its own pre-existing universe — the coupled-file set fromcoupling_centrality_v1, not language-partitioned; unifying that too is a separate follow-up.) Band thresholds retuned0.50/0.25 → 0.55/0.28to keep the red set selective after god-class intensities rose. Addedbiomarker_repofixture + tests:code_health_structural_risk_discriminates,code_health_biomarkers_fire_distinct_smells(closes the T2-M2 dropped-UNION-arm gap), andrefactoring_targets_dominant_type_varies_on_biomarker_repo(closes the P2-T2-M1 gap).CACHE_EPOCH → schema_v8.
- Location:
main.rs(explain, check gate, refactoring-targets dispatch),analyses/code_health.rs(module doc),analyses/refactoring_targets.rs(dominant-type query),quality_gates/mod.rs, tests - Severity: HIGH (aggregate) · Category: correctness / consistency / test coverage · Status: Fixed (Unreleased)
- Description: A five-stream adversarial audit of the Phase-1/2 changes confirmed the core computations correct (bounded, NaN-free, deterministic) but surfaced: (a) HIGH —
codelore explain code-healthprinted the pre-F232 formula (0.40/0.25/0.15+ a separate coupling term,0.66/0.33bands); (b) HIGH — thecheckgatecode_health_minevaluated the hotspots inline cognitive-only proxy (floored 60), not the composite, so a red file (composite ~20) silently passed acode_health_min = 70gate; (c) MED —refactoring-targetsclaimedhtmlsupport but its dispatch returnedhtml_not_wired; (d) LOW —dominant_typereported a biomarker at intensity 0 (should be "none"); (e) LOW — the module doc overstated per-language-percentile uniformity (shotgun excepted); plus several weak/tautological tests and untested contracts (weights, band thresholds, code-health CSV columns). - Fix: (a) explain tuple updated to the current formula/weights/thresholds; (b)
code_health_minrewired to the composite via a newevaluate_code_health_gate(&[CodeHealthRow])(breaking change, noted in CHANGELOG); (c)htmlwired via the genericwrite_html; (d)WHERE intensity > 0added to the dominant-type query; (e) module doc scoped. Tests: reframed the architecturally-invalidstructural_risk_rewards_multiple_cooccurring_smells, hardened the silent-skip incode_health_penalizes_churn, removed the tautologicalgod_class_and_dry_are_biomarkers, fixed themin_by_key(loc)tie in the ManualUp test, and addedcode_health_csv_column_contract+code_health_band_matches_thresholds. Residual (deferred): exact-weight assertion (needs exposingn_cn/n_au), god-class biomarker firing (needs a fan-in fixture), and churn/ownership global-vs-per-language normalization (a Phase-2 tuning consideration).
A post-slice deep validation of Plan 3b (four selection subscribers) ran three parallel source-level validators (subscribe correctness / publish completeness / test integrity) plus a validation-spec pass. The subscribe side was confirmed fully correct; the findings below are on the publish side and two subscriber edge cases. F239–F241 were fixed together in 0a41b1d; F238 is the one that needs a design decision (tracked for Plan 3c).
- Location:
output/spa/widgets.js— parallel series config (emphasis: { disabled: true }) + theparallel-coordsselection subscriber - Severity: LOW · Category: UX / linked-brushing fidelity · Status: Fixed (Unreleased)
- Description: The pre-existing
parallel-coordssubscriber calls EChartshighlight/downplayon selection, but the parallel series setsemphasis: { disabled: true }, so those actions have NO visible effect — a file selected in another widget does not visibly stand out in the parallel-coordinates plot. The subscriber is wired but inert. - Fix (
dd9cfad): investigation confirmedemphasis: { disabled: true }is a LOAD-BEARING ECharts-6 regression workaround (the hovered polyline disappears under emphasis) — so re-enabling emphasis was NOT an option. Instead the subscriber now restyles the selected line's per-itemlineStyledirectly (bold--color-info, width 3, opacity 1; the rest fade to opacity 0.12; a null selection restores the default--color-warning), extracting the series data intoparallelDataonce so it can mutate + re-apply without a rebuild. No emphasis transform, so the regression stays avoided. Setting every item each fire means no stale A→B highlight.
- Location:
output/spa/widgets.js—trendsselection subscriber - Severity: MED · Category: correctness / linked-brushing · Status: Fixed (Unreleased)
- Description: ECharts
dispatchAction({type:'highlight'})is additive — it does not implicitly clear a prior highlight. Thetrendssubscriber highlighted the selected series without downplaying first, and because the file-detail drawer is NON-modal (.show(), not.showModal()), a user can switch selection directly from file A to file B with no intervening null-clear. Result: both A's and B's trend lines stayed bold+un-blurred — a stale highlight of the previous file. (Thecoupling/dsmsubscribers added in the slice already downplayed-first;trendsandparallel-coordsdid not.) - Fix (
0a41b1d):trendsnowdownplays unconditionally first, then early-returns on null, then highlights only on a match — matching the coupling/dsm shape. (parallel-coordswas validated benign here — itsemphasis.disabledmeans no highlight state persists; see F238.)
- Location:
output/spa/widgets.js— click handlers for the circle-pack map, coupling sankey, treemap, X-Ray sunburst - Severity: MED · Category: feature completeness · Status: Fixed (Unreleased)
- Description: "Select a file in ANY widget → highlight everywhere" needs both a publish and a subscribe half. Plan 3b completed the subscribe half but the publish half was asymmetric: only four surfaces (hotspot table, parallel-coords, KI-table, keyboard treeview) routed clicks through
_codeloreShowDetail(which publishesselection.set); the map canvas, sankey, treemap, and X-Ray sunburst calledshowFileDetailDrawerDIRECTLY — opening the drawer without broadcasting. So the coupling sankey, DSM, trends, and the map canvas were effectively receive-only. - Fix (
0a41b1d): the four direct-drawer file-clicks now route through the guarded_codeloreShowDetailidiom (sankey gated to files mode — a module-depth node name is a prefix, not a file, and must not go on the file-level bus). The map click drops its redundant directselectedCouplingFile/updateCouplingArcs()in the broadcast branch (thehotspot-mapsubscriber does it on fan-out). Validated NOT-VALID and intentionally excluded: the DSM as a publish source — its axes/cells are module-level, so a click cannot identify a single file for the file-level bus. Follow-on (27d666b): the map's canvas background-click now clears the shared selection (bus) instead of only its local arcs, so deselection is symmetric with the new publish-on-select.
- Location:
output/spa/widgets.js—couplingselection subscriber - Severity: LOW · Category: linked-brushing fidelity · Status: Fixed (Unreleased)
- Description: The
couplingsubscriber highlighted the sankey node by the raw full path (name: selectedPath). In files mode (default) node names ARE full paths so it matched, but in module-depth view nodes are named by truncatedmodulePathSegprefixes, so the highlight silently no-op'd — the sankey did not participate in cross-widget selection at non-file depths. - Fix (
0a41b1d): the subscriber now maps the bus path into the current node-name space (modulePathSeg(selectedPath, userSankeyDepth)when depth is numeric, else the full path), mirroring the DSM subscriber's module-mapping. Also recorded as a validated non-issue: a proposed DSM "empty-indices" guard is dead code — the per-index diagonal guide cells guarantee the scan always yields ≥1 index.
- Location:
crates/codelore-lib/tests/spa_browser_test.rs— Step 13 inrendered_spa_boots_without_console_errors - Severity: LOW · Category: test coverage · Status: Fixed (Unreleased)
- Description: The original Step 13 (inside
rendered_spa_boots_without_console_errors) asserted the coupling subscriber highlights a selected file'smodulePathSeg(path, 2)module prefix in module-depth sankey view. The production mapping is correct (verified by source review), but that test's only fixture —differential_repo::build()— has near-zero co-changes, so at depth 2 the change-coupling sankey had no cross-module links and no qualifying node; the step always SKIPPED. Net: the guard executed no assertion in CI and provided zero live regression protection, and it spun a ~3s re-render poll to no effect on every run. - Fix (
373747e,9030159): added a dedicatedcoupling_repofixture (test_support/mod.rs) — three 2-segment modules (src/alpha,src/beta,src/gamma), withalpha/svc.rs↔beta/svc.rsco-changed across 6 commits so asrc/alpha↔src/betadepth-2 edge is guaranteed under any coupling threshold, plus per-file solo churn for hotspot rows — and moved the assertion into its own testsankey_module_depth_highlights_mapped_noderendered from that fixture withpermissive_coupling_opts. The inert Step 13 was removed from the smoke test. The new test FAILS (not skips) if the depth-2 sankey has no qualifying node, and asserts the captured highlight name equals the module prefix (not the raw path). Independently verified:spa_browser_test9/9 (was 8), the new test exercises its assert (full-boot run, ~9.3s),spa_integration_test4/4.
A five-dimension architecture review (four parallel read-only analysts: architecture/boundaries, coding-structure/patterns, performance, SPA; plus two validation-and-spec passes) surveyed the engine + SPA for improvement leverage. Headline: the codebase is genuinely well-built — error handling exemplary (3 non-test unwraps, all justified), the Repo two-backend abstraction + !Send ingest pipeline + cross-crate boundaries clean, CSV-injection closed, test quality high. The real structural debt concentrates in one place: the analysis→output dispatch fan-out (43 stringly-typed dispatchers × per-format arms + 43+43 tabular emitter fns). The low-risk validated wins were fixed this pass; the large refactors are logged own-slice below.
- Location:
codelore-cli/src/main.rs—dispatch_authors/dispatch_top_committers/dispatch_knowledge_islands/dispatch_clone_coupling - Severity: LOW · Category: correctness / user-facing message · Status: Fixed (Unreleased)
- Description: Each of these 4 dispatchers has a working
"html" => write_html(...)arm, but itsunsupported_format(...)error message advertised a format list OMITTINGhtml(e.g."csv|json|markdown"), so a user passing an invalid--formatwas told html isn't supported when it is. Same class as F237(c). This is a live symptom of the stringly-typed dispatch (the advertised list is a hand-maintained string parallel to the actualmatcharms — F215). - Fix (
acd9568): addedhtml(andsarifwhere applicable) to the 4 advertised strings. Byte-identical for every success path — only the error text for an unsupported format changed. This is a symptom patch; the root cause (parallel hand-maintained format lists) is F215, logged own-slice below.
- Status: Fixed (Unreleased) (
52c427c) — previously Active/deferred. - Fix: rather than the deferred one-off scripted sweep, the existing
comment_hygiene_test.rswas extended with ano_plan_phase_markers_in_code_commentstest (aPlan+digit whole-comment scan, sibling to theF<NN>guard), then all 69 existing markers were scrubbed — the majority stripped (parenthetical/provenance tags), ~18 stale future-tense/current-claim comments rewritten to present state (verified against source: single-producer ingest, Type 1/2 clones, all output formats shipped,gixdefault +GitCliRepooracle). Test + fixes landed atomically; the guard makes the convention self-enforcing forever (strictly better than a sweep that can silently regress).
- Clippy
#[allow]justification (356efc9) — 19 previously-unjustified#[allow(clippy::…)]sites gained a true per-site technical reason (Golden Rule #14). The tempting "consolidate casts to a workspace allow" was validated and REJECTED — it would disable the lint repo-wide. - SPA listener-bus unification (
1d645af) — the two byte-identical registries (selection+brush) collapsed to amakeListenerBus(arrayName)factory (behavior-preserving; browser tests green). - SPA browser-test coverage (
e4ec986) — the main browser test's dashboard fixture was broadened so previously-dark widget render branches (arch-trend, fusion overlays, MI tile, ownership/clones colour maps) now execute under the no-console-error / exception gate. No latent render bug surfaced.
- F244 — Central analysis registry /
enum Format+TabularRow(root cause behind the dispatch fan-out). Refines/absorbs F215 (stringly-typed dispatch), F148 (no shared tabular-row trait; 88 csv/markdownwrite_*fns), F119 (hand-rolled CSV). Validated true scope: 43 dispatchers + ~137 match arms; a clapValueEnumFormatis the minimal first increment (deletes the hand-maintained master-listmatch, adds parse-time validation + completions) — but carries an exit-2-vs-exit-4 contract delta for invalid--formatand does NOT alone fix the per-dispatcher advertised-string drift (needs asupported_formats()source of truth).main.rs(3283 LOC) splits intocommands/+dispatch/after the enum lands. L, byte-identical-gated. Sequence AHEAD of the output-emitter cluster. - F245 — SPA
widgets.jsbuild-time module split. 4588-line single IIFE; split into topical source.jsfragments concatenated into the{{WIDGETS_JS}}placeholder inspa.rs(fits the offline-single-file constraint — no build step, one output file). Fixed (Unreleased): implemented as a compile-timeconcat!of seven orderedspa/js/*.jsmodules (byte-identical assembly proven viacmp; full browser suite green), files named to match contents. - F246 — SPA canvas-chart keyboard operability. The arch-graph/DSM/module-chord/X-Ray/treemap have
role="img"+aria-labelbut no keyboard-navigable data equivalent (only the circle-pack has itsrole="tree"). Largest remaining a11y gap; scope to the 1-2 highest-value widgets. M. - Still tracked: F206/F173 (HEAD-scan I/O — the one big perf lever, benchmark-gated), F218 residual (per-widget layout-change routing — the theme-path split landed; see the finding).
- Domain newtypes (Golden Rule #15) — low Rust ROI: values are string-shaped from DuckDB straight into emitters with no cross-type mixup risk; the one primitive-confusion bug (path vs module-prefix, F240/F241) was JS-side. Accepted deviation.
Optionsbuilder — already rejected in the roadmap; its cross-field-validation value is already delivered byOptions::validate().
State after the 2026-07-01 + 2026-07-02 implementation sessions (all merged to main):
- Implemented + merged (28): 2026-07-01 (25) — F191, F201, F203, F204, F205, F207–F214, F216, F217, F219–F228 (PR #71); 2026-07-02 (3) — F200 (deleted the divergent+vacuous
commit_metadata+CommitMetadata, kept the realchanged_files/diff_hunkscross-checks; #71), F229 (dropped the vendoredlibduckdb-sysfork;duckdb → =1.10504.0; #71), F230 (gix 0.84 → 0.85; #74). Full CI matrix green on every merge, includingtest (windows-latest). - Refuted on validation (2026-07-02): F188 (ruleset omission is intentional + drift-guarded), F202 (fan-out divergence is mostly by design — god-classes externals vs internal coupling).
- Deferred — large refactor / focused pass: F206 (HEAD-scan I/O restructure — wants a benchmark), F215 (
enum Format), F218 residual (per-widget layout routing; the theme/layout effect split is Partially Fixed above), F231 (62-sitePlan Nscripted sweep). - Carried-forward Active (output/blob cluster): F119 (csv-crate), F148 (
TabularEmitdedup), F161 (EmitterStream), F173 (HEAD blob dedup) — byte-identical-critical (F206 is the deeper lever for F173). - Carried-forward Partial / design: F177 (schema sentinels), F186 (bench PR gate — design), F197 (dogfood advisory/separate-cache).
- Fixed (Unreleased) 2026-07-03: F232 (coupling double-count —
n_cpremoved, score reweighted), F233 (code_health_biomarkers_v1cross-analysis contract — documented at the DDL), F234 (locdisplay floor — reports true value), F235 (structural_risksaturation — rank files not functions + weighted sum; 67/69-at-1.0 → 8/32/31 band split), F236 (biomarker normalization unified on full-universe per-file percentile;biomarker_repofixture + distribution/vocabulary/dominant-type tests), F237 (deep-validation audit: stale explain, check-gate composite rewiring, refactoring-targets html, dominant-type intensity>0, test hardening), F239 (trends A→B stale highlight — downplay-first), F240 (SPA linked-brushing publish symmetry — map/sankey/treemap/X-Ray now broadcast), F241 (coupling-sankey highlight fires in module-depth view) (all0a41b1d), F238 (parallel-coords highlight made visible via a direct per-itemlineStylerestyle —dd9cfad).
Highest-leverage work remaining:
- HEAD-scan I/O (F206 + F173) — resolve HEAD→tree once per pass with a per-worker cached repo; benchmark via
ingest_capacity_sweep. Biggest large-repo wall-clock lever. - Output-emitter cluster (F119 / F148 / F161) — csv-crate migration (preserve the F170 injection guard +
\nendings),TabularEmitdedup,EmitterStreamstreaming, in one coordinated byte-identical pass. Plan Nmarker sweep (F231) — 62-site scripted comment cleanup (a hard-rule violation).
F242 (module-depth coupling-subscriber browser test made live — new coupling_repo fixture + dedicated test; 373747e, 9030159) closed the last SPA linked-brushing follow-up.
2026-07-04 architecture-review pass: F243 (html un-advertised in 4 dispatchers — Fixed acd9568) and F231 (Plan-N markers — Fixed via self-enforcing hygiene guard 52c427c) closed; clippy-allow justification + SPA listener-bus + browser-fixture coverage landed. New own-slice: F244 (analysis registry / enum Format + TabularRow, absorbs F215/F148/F119), F245 (widgets.js module split), F246 (canvas keyboard a11y).
The next sweep should re-open with F-IDs starting at F249.
F248 (Active) — no integration coverage that health-trend's arch_health falls as the import graph decays. The health-trend analysis (analyses/health_trend.rs) computes arch_health from per-rev GraphMetrics, and the unit tests cover the pure function (empty/acyclic/fully-tangled/clamp). But the integration test (tests/health_trend_test.rs) only asserts shape/ranges/oldest-first, not the spec's "degrading architecture ⇒ arch_health decreasing" case — because the only ≥2-commit fixture, biomarker_repo, is six independent Rust files with no inter-file imports, so its import graph is empty and arch_health is pinned at 100 across every sample. Fix: add an import-structured fixture whose later commits introduce a dependency cycle (mirror architecture_trend's trend_captures_cycle_introduction_over_time), then assert the newest sample's arch_health is below an earlier sample's. Surfaced by the whole-branch review of the Repo Health Timeline (piece 2).
F247 (Active) — run_coupling_scoped cutoff ignores lineage/time-bucket source in good_commits. The rev-parameterizable code_health history cutoff (HealthScanCtx::history_cutoff) routes coupling through run_coupling_scoped(db, opts, "changes_at_ts"), which overrides only the pair-source + Fisher-denominator tables. The internal good_commits_cte(bucket, use_lineage) still reads the opt-derived changes_lineage/changes. For the primary path (no lineage, no time-bucket) this is equivalent — the cutoff-window revset equals full-history ∩ window. But history_cutoff combined with --use-canonical-lineage yields coupling pairs keyed on pre-rename path names, and combined with --time-bucket aggregates buckets over full history. The same class applies to code-health's own churn / revs / author-fragmentation CTEs: under a cutoff {src} becomes the raw, non-lineage changes_at_ts view, so those terms also lose rename-awareness when a cutoff is combined with --use-canonical-lineage. Neither combination is exercised (the timeline consumer uses the primary path — cutoff without lineage/bucket) nor required by the spec; documented in the run_coupling_scoped and CHANGES_AT_TS_DDL doc comments. Fix if a future consumer needs cutoff + lineage/bucket: build changes_at_ts from the lineage-rewritten source and thread changes_source into good_commits_cte. Surfaced by the Task-4 review + the whole-branch review of the rev-parameterizable code-health branch.