Commit 7f6fc0a
authored
feat: code health layer — biomarkers, refactoring targets, coverage, trends, AI prompts (#212)
* feat(health): scaffold module + READMEs
* feat(health): Alembic migration for health tables
* feat(health): tree-sitter complexity walker for full-tier languages
* feat(health): biomarker base + registry
* feat(health): brain_method, nested_complexity, complex_method detectors
* feat(health): scoring engine + HealthReport orchestrator
* feat(health): persistence (findings + metrics)
* feat(health): pipeline orchestrator wiring
* feat(cli): repowise health command
* feat(mcp): get_health tool + get_risk health enrichment
* feat(generation): CLAUDE.md code-health section
* feat(web,ui,server): /health route + KPI cards + sidebar entry
* test(health): walker, biomarkers, scoring, MCP get_health
* feat(health/coverage): LCOV parser + dataclasses
Adds CoverageReport / FileCoverage dataclasses and a stdlib LCOV
parser. The parser tolerates partial reports (missing LF/LH/BRF/BRH
or end_of_record), derives per-file line/branch coverage, and emits
the explicit covered-line set so downstream biomarkers can do
line-level lookups.
* feat(health/coverage): Cobertura + Clover parsers, format and test-file detection
Adds stdlib XML parsers for Cobertura and Clover, plus a detector
module that handles both format auto-detection (lcov / cobertura /
clover via cheap content sniff) and the test-file heuristic
(path conventions + framework-import scan + paired-test lookup).
README documents the public API and extension points.
* feat(health): persistence + engine plumbing for coverage files
Adds save_coverage_files / load_coverage_for_repo / get_coverage_summary
CRUD over the CoverageFile table (delete-then-insert mirroring the
health writers). HealthAnalyzer now accepts a coverage_map keyed by
repo-relative path and forwards line/branch coverage + covered-line
sets onto FileContext and HealthFileMetricData so coverage-aware
biomarkers can consume them.
* feat(cli): --coverage and --coverage-format flags on repowise health
Ingests one or more coverage reports (LCOV/Cobertura/Clover), auto-
detects format unless --coverage-format overrides, and threads the
parsed FileCoverage rows into HealthAnalyzer via coverage_map. The
analyzer wires line/branch coverage onto HealthFileMetric and the
biomarker FileContext, unlocking the Phase 2 coverage biomarkers.
* feat(health): untested_hotspot + coverage_gap biomarkers
untested_hotspot fires for hotspot files (is_hotspot or
commit_count_90d >= 8) with >= 4 dependents that are either under
40% covered or, when no coverage data is present, lack a paired
test file. Severity grades by coverage depth + dependent count.
coverage_gap fires on non-test files with significant uncovered
surface — either < 60% covered with >= 25 uncovered lines, or
< 30% covered in a file >= 50 lines. Stays silent when no coverage
data is ingested so the absence-of-coverage case stays solely
under untested_hotspot.
Both biomarkers wired into registry.py and covered by 11 unit
tests.
* feat(mcp): coverage in get_health, coverage_pct on get_risk, health include on get_context
- get_health(include=['coverage']) attaches a coverage summary
(aggregate line/branch percentages, source format, ingested commit)
plus per-file rows; targeted mode keeps the full covered-line set,
dashboard mode drops it to keep responses light.
- get_risk per-target rows gain coverage_pct (and branch_coverage_pct)
when HealthFileMetric carries them.
- get_context(include=['health']) returns per-file score, top two
biomarkers, and the linked CoverageFile row when ingested.
* feat(web,ui,server): /health/coverage view + API + nav entry
API: GET /api/repos/{repo_id}/health/coverage returns the coverage
summary (line/branch %, source format, ingest metadata), per-file
rows trimmed by limit (covered_lines stripped except in single-file
mode), and module-level aggregates.
UI: new CoverageBar / ModuleCoverageList / UntestedHotspotWarning
primitives in @repowise-dev/ui/health. The /repos/[id]/health/coverage
Next.js page composes them into a 4-card summary, an untested-hotspot
warning, a module bar list, and a per-file table. Empty state guides
the user to run pytest --cov + repowise health --coverage.
Sidebar gains a Coverage entry under Health (test-tube icon).
* test(health/coverage): parser tests, biomarker tests, integration test + ruff fixes
- 23 parser tests covering LCOV/Cobertura/Clover happy paths, edge
cases (missing end_of_record, bad XML), format detection, and the
test-file heuristic (paths + framework imports + paired-test lookup).
- 11 coverage-biomarker tests covering untested_hotspot fall-back when
no coverage data, gating by dependents/hotspot, and coverage_gap
severity / test-file exemption.
- Integration test runs the full HealthAnalyzer over the sample_repo
fixture with a handcrafted LCOV report and asserts coverage flows
through onto HealthFileMetric + coverage_gap biomarker fires.
- Ruff cleanups (RUF046, SIM105, SIM108, RUF003) on the new modules.
* feat(health/duplication): tokenizer + rabin-karp + clone-pair detector with co-change correlation
* feat(health): bumpy_road + large_method + primitive_obsession biomarkers
* feat(health): dry_violation biomarker wired to duplication detector + clones on FileContext
* feat(health): developer_congestion + knowledge_loss biomarkers
* feat(health): HealthConfig + .repowise/health-rules.json per-file overrides
* feat(web,ui,server): /health/refactoring-targets view + API + RefactoringCard/RefactoringTargetList + HealthBadge
* feat(web,ui): inline health badges via HealthRisksPanel on hotspots/ownership/graph views
* feat(cli): --refactoring-targets and --module flags on repowise health
* test(health): structural/organizational/dry_violation/config + duplication tests; ruff clean
* feat(health/trends): HealthSnapshot writer + declining/predicted-decline alerts
* feat(health): incremental analysis on repowise update (upsert findings + metrics)
* feat(health): deterministic refactoring suggestions on findings + refactoring-targets cards
* feat(health): module-level NLOC-weighted rollups + module:foo target + per-module overview section
* feat(health): parallel biomarker analysis via asyncio.gather + perf benchmark + trends/suggestions tests
* feat(mcp): code_health KPIs in get_overview + module/duplication/suggestion in get_context health
* feat(cli): repowise health --trend + repowise status health one-liner
* docs(health): final-pass READMEs, docs/CODE_HEALTH.md user guide, README comparison rows
* ci(health): make health-check + health-bench targets + CI smoke test + scoring-stability snapshot tests
* fix(ui): drop .js extension on health-badge import in module-rollup-list
* fix(ui,health): drop .js extensions on intra-folder imports — webpack build was failing to resolve them
* docs(health): architecture deep-dive + CLI reference entry + main README integration (5th layer, 9 MCP tools)
* feat(health,ui): overhaul code-health web UX — tabbed chrome, trend, scatter quadrants, file drawer
Server enrichments (additive, no pipeline changes):
- /health/overview now carries hotspot_health (from latest snapshot),
severity_breakdown, per-biomarker breakdown, and meta (last_indexed_at /
head_commit / snapshot_count)
- /health/files: server-side sort, offset/limit pagination, search, and
three boolean filters (only_hotspots / only_untested / only_failing)
- /health/files/breakdown: per-file score breakdown by category — mirrors
scoring.score_file so the dashboard can explain how a file's score was
built up (raw vs applied per-category deductions, capping flag, parallel
per-finding impact)
- /health/trend: snapshot history + diff + alerts + per-file score deltas
- /health/findings/{id} PATCH: lifecycle status (acknowledged | resolved |
false_positive) — the model already supported it; UI now exposes it
- /health/refactoring-targets: biomarker / min_severity / max_effort
filters, configurable sort, full findings list per target, module label
- Module rollup strips the noisy ' (N)' community-detection suffix
- Pydantic v2-compatible Query() patterns (regex → pattern)
UI primitives (packages/ui/src/health/):
- tokens.ts — single source of truth for score/severity colors,
delta formatting; biomarker-list / file-table / drawer all consume it
- biomarker-glossary.ts + biomarker-chip — human label, category, and
one-line description for every detector; powers tooltips across pages
- health-tabs — shared Overview / Trend / Coverage / Refactoring strip
- sparkline, severity-distribution, score-breakdown, trend-chart
- risk-coverage-scatter — health × coverage quadrant for the coverage page
- impact-effort-quadrant — impact × effort grid for refactoring page
- health-file-drawer — slide-over with stats, score breakdown, and full
findings list with deterministic suggestions
Component upgrades:
- KpiCards: 5 cards including Hotspot Health (was missing), severity
distribution bar inside Open Findings, optional sparklines + Δ vs prior
- FileTable: sortable headers, click-to-open drawer, dup% + coverage cols,
selected-row highlight
- BiomarkerList: optional group-by-biomarker with severity sub-chips and
per-group expand
- ModuleRollupList: sortable columns + Show all toggle
- RefactoringCard: expandable, lists every finding, exposes Acknowledge /
Resolved / False positive buttons wired to the new PATCH endpoint
Page rebuilds:
- /health (overview): HealthPageChrome (icon + indexed-at meta + tabs),
KPI row with sparklines pulled from /trend, file table now server-side
paginated + filter chips (Hotspots / Untested / Failing) + path search,
grouped findings with min-severity filter, By-module rollup, drawer
- /health/trend (new): Δ cards, alert chips, KPI line chart, top files
that moved between the last two snapshots
- /health/coverage: risk × coverage scatter, fixed untested-hotspot
filter (no longer treats missing coverage as 100%), search filter,
drawer-on-click rows, richer empty state with supported formats
- /health/refactoring-targets: filter toolbar (biomarker / min severity /
max effort / sort / group), impact × effort quadrant, group-by selector,
status mutation wiring
Sidebar: adds Trend nav row, marks Health as exact-match so it doesn't
light up for the sub-pages.
No core-pipeline / biomarker / scoring changes.
* feat(health,ui): AI fix/test prompts + clickable-row affordance
Refactoring page:
- New ArrowUpRight icon on the file path (with hover state + accent
color) so it's visually obvious the row opens the file drawer.
- New 'AI fix prompt' button per card. Opens a modal that lets the
user pick a target agent (Generic / Claude Code / Cursor), previews
the generated prompt, shows char + approximate-token count, and
copies to clipboard with a confirmation state.
Coverage page:
- ArrowUpRight icon on each row so the file-name's click target is
apparent.
- Inline Sparkles button on each row → 'AI test prompt' modal, same
flavor selector as the refactor flow.
Prompt builder (packages/ui/src/health/ai-prompt-builder.ts):
- buildAiPrompt(target) — refactor brief with every biomarker, line
range, severity, score deduction, suggested direction, hard
constraints, and a completion contract.
- buildCoverageAiPrompt(row) — 'add tests' brief with coverage
numbers, uncovered-line ranges (when covered_lines is present),
health-score context, and test-quality constraints.
- Preambles + constraints emphasize *read first, edit second*: the
agent should verify each finding against the real code (callers,
tests, neighbors) and treat the analyzer output as leads, not
ground truth — false positives must be called out, not silently
acted on.
Modal (ai-prompt-modal.tsx) is generic over both prompt kinds via a
getPrompt(flavor) prop, so future prompt sources (e.g. dead-code,
hotspot drift) can reuse it without forking the component.
* fix(health): persist coverage_files + updated metrics from `repowise health --coverage`
Before this fix, `repowise health --coverage report.lcov` parsed the
LCOV/Cobertura/Clover report and threaded it through the in-memory
biomarker analyzer — but never called `save_coverage_files` or
re-persisted the health tables. Result: the CLI printed updated numbers
to stdout while the dashboard, MCP tools, and `/api/.../health/coverage`
all kept returning the pre-coverage state, including the empty-state
view that says 'No coverage data ingested yet'.
Now, when `repowise health` runs without `--file` or `--module` (i.e.
when its output describes the full repo), it opens the repo's wiki.db
and writes:
- coverage_files (when --coverage was passed)
- health_file_metrics
- health_findings
- a fresh health_snapshot (for trend tracking)
Best-effort: a missing repository row (i.e. user never ran `init`),
a DB error, or a snapshot-write failure each log and continue rather
than crashing the CLI. Persistence is skipped entirely when the run
is filtered (`--file` / `--module`) so a one-off deep-dive can't
overwrite the repo-level snapshot.
* test(persistence): include four health tables in expected Base.metadata set
test_base_includes_all_models asserts the full set of tables registered
on Base.metadata. The four health tables (health_findings,
health_file_metrics, health_snapshots, coverage_files) were missing from
the expected set, so the assertion failed on CI as soon as the new
models started importing in that test module.
* ci(health): drop stdout-pipeable health smoke test; keep stderr routing
The CI smoke step (`repowise health --format json | python -m json.tool`)
was added in 3fe8685 and never actually green. It hit two stdout-pollution
issues — rich's status banner and structlog's per-stage info logs — both
unrelated to the analyzer's correctness. The full repo of unit + integration
tests (99 health tests, scoring snapshot, parsers, trends, MCP) already
covers everything the smoke step would: it's a low-value 30–60s flake.
Drop the workflow step.
Also keep the half of the fix that's a real correctness improvement: status
output (target.notice, the 'repowise health — <path>' banner, coverage-
ingest progress) now routes to stderr when `--format` is json or md, and
the DB-persistence side effect is skipped in those modes too. The structlog
side of the stdout-pollution story is left alone — fixing it cleanly means
configuring the core logger to write to stderr, which is a wider change
than this branch should make.1 parent ea379b1 commit 7f6fc0a
129 files changed
Lines changed: 14030 additions & 152 deletions
File tree
- docs
- architecture
- packages
- cli/src/repowise/cli
- commands
- core
- alembic/versions
- src/repowise/core
- analysis/health
- biomarkers
- complexity
- coverage
- duplication
- generation
- editor_files
- templates
- persistence
- pipeline
- server/src/repowise/server
- mcp_server
- routers
- ui
- src/health
- web/src
- app/repos/[id]
- graph
- health
- coverage
- refactoring-targets
- trend
- hotspots
- ownership
- components
- health
- layout
- lib/api
- tests
- fixtures
- coverage
- lang_samples
- go
- javascript
- java
- python
- rust
- typescript
- integration
- unit
- health
- persistence
- server
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
53 | 53 | | |
54 | 54 | | |
55 | 55 | | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
56 | 68 | | |
57 | 69 | | |
58 | 70 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
6 | | - | |
| 6 | + | |
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| |||
84 | 84 | | |
85 | 85 | | |
86 | 86 | | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
87 | 102 | | |
88 | 103 | | |
89 | 104 | | |
| |||
175 | 190 | | |
176 | 191 | | |
177 | 192 | | |
178 | | - | |
| 193 | + | |
179 | 194 | | |
180 | 195 | | |
181 | 196 | | |
| |||
189 | 204 | | |
190 | 205 | | |
191 | 206 | | |
| 207 | + | |
192 | 208 | | |
193 | 209 | | |
194 | 210 | | |
| |||
197 | 213 | | |
198 | 214 | | |
199 | 215 | | |
200 | | - | |
| 216 | + | |
201 | 217 | | |
202 | 218 | | |
203 | 219 | | |
| |||
457 | 473 | | |
458 | 474 | | |
459 | 475 | | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
460 | 485 | | |
461 | 486 | | |
462 | 487 | | |
463 | 488 | | |
464 | | - | |
| 489 | + | |
465 | 490 | | |
466 | 491 | | |
467 | 492 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
257 | 257 | | |
258 | 258 | | |
259 | 259 | | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
260 | 295 | | |
261 | 296 | | |
262 | 297 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
0 commit comments