Closing the loop
Measuring is not the end. Fix, verify, and measure the effect — in one command.
Why measure at all
"Fixing hotspots gives the best return" is a hypothesis. Until you actually fix one and see how far the complexity moved, the claim is untested.
dowse measures complexity before and after, and reports the difference.
packages/cli/src/analyze.ts
cognitive -41 / cyclomatic -22 / LoC -111
verified: typecheck ✓ / tests ✓
cost: $1.86What you need
Claude Code, installed and logged in.
claude --versionNo API key configuration required — it runs on your existing authentication and permission settings. Without Claude Code, an ANTHROPIC_API_KEY is enough for diagnosis only; applying a fix needs a harness that can edit files.
Diagnose only
dowse diagnose --top 3An LLM reads the top hotspots and returns why they are risky, how to fix them, and roughly what it will cost.
packages/cli/src/analyze.ts
why risky: five subcommand entry points each re-implement option parsing,
the JSON/human output split, and table formatting inside one file;
display logic and command control are not separated
estimate: medium (half a day)
plan:
1. extract couplingFilters and asProxy as pure functions
makes them testable, so later output changes cannot silently break aggregation
risks:
- the CLI's stdout is itself a contract for users; even column order breaks downstreamThis is an opinion, not a measurement
Keep it clearly separate from the deterministic metrics (hotspot, health). It is a reading produced by an LLM.
Cost is proportional to the number of files. Control it with --top.
Let it do the fix
dowse fixTakes the top hotspot through diagnose → fix → verify → measure. You can also name a file.
dowse fix packages/core/src/foo.tsDry run by default
The change is always discarded. The run verifies and measures so you can see what fixing it would do.
To keep it, pass --apply.
dowse fix --applyChanges that pass verification stay on a working branch.
The safety valves
Because this loop rewrites files, several guards are in place.
| Guard | Behaviour |
|---|---|
| Uncommitted change detection | Refuses to run if the working tree is dirty |
| Branch isolation | Works on dowse/fix-<target>-<sha> |
| Independent verification | dowse runs the type check and tests itself |
| Diff ceiling | Discards changes larger than 400 lines by default |
| Dry run by default | Nothing is kept unless you pass --apply |
Verification is not left to the agent
An agent reporting "fixed it" is not evidence. dowse runs the type check and tests directly and decides on the exit code.
It works out how to run them from the repository itself — the packageManager field first, then the lockfile, falling back to npm. pnpm, bun, yarn: it uses whatever that repository uses.
Failed changes are discarded and the failure is shown.
packages/foo.ts: discarded — type check failed
verified: typecheck ✗ / tests ✓
src/foo.ts(42,10): error TS2322: Type 'string' is not assignable to 'number'.When there are no verification scripts
In a workspace whose package.json has neither a typecheck nor a test script, nothing can be verified. dowse then refuses to report a pass and discards the change, rather than letting through something it never checked.
Sometimes it changes nothing
If the agent concludes there is nothing worth improving, it leaves the file alone.
packages/core/src/metrics/churn.ts: the agent made no changes
total: 0 of 1 improved / cost $0.56That is correct behaviour. Mechanically churning an already-simple file buys nothing.
Reading the result
┌────────────────────┬─────────┬───────────┬────────────┬──────┬───────┬───────┐
│ target │ outcome │ cognitive │ cyclomatic │ LoC │ diff │ cost │
├────────────────────┼─────────┼───────────┼────────────┼──────┼───────┼───────┤
│ src/analyze.ts │ applied │ -41 │ -22 │ -111 │ 439 │ $1.86 │
└────────────────────┴─────────┴───────────┴────────────┴──────┴───────┴───────┘| Outcome | Meaning |
|---|---|
| applied | Passed verification (discarded after measuring on a dry run) |
| discarded — verification | Type check or tests failed |
| discarded — too large | Exceeded the diff ceiling |
| no change | The agent judged no improvement was needed |
| aborted | The working tree had uncommitted changes |
Negative numbers are improvements. When nothing improved, that is reported as it is.