How it works HTML Voice Install GitHub → Back to Novarch
Novarch / recap-wins / User Guide
User Guide · v0.3.2

The complete walkthrough
of rw.

Install, the mental model, every command, the provider modes, configuration, and recipes for common tasks. For a quick reference in the terminal, run rw help — this page is the long-form version.

// section one

01 Install #

Homebrew (recommended)

The repo is its own tap — one command installs the rw binary:

$ brew tap yohannescodes/recap-wins https://github.com/yohannescodes/recap-wins $ brew install recap-wins

Upgrade later with brew upgrade recap-wins. The formula builds from source, so the first install compiles via Swift and takes a minute. Then skip to §2 — rw is already on your PATH.

From source

Requires Swift 6 and a recent macOS.

$ git clone https://github.com/yohannescodes/recap-wins.git $ cd recap-wins $ swift build -c release

The binary is at .build/release/rw. Put it on your PATH so you can run rw from any repo. Pick one:

# A. symlink into a system bin (may need sudo) $ sudo ln -sf "$PWD/.build/release/rw" /usr/local/bin/rw # B. symlink into a personal bin (no sudo, if ~/bin is on PATH) $ mkdir -p ~/bin && ln -sf "$PWD/.build/release/rw" ~/bin/rw # C. a shell alias $ echo "alias rw=\"$PWD/.build/release/rw\"" >> ~/.zshrc && source ~/.zshrc

Verify:

$ which rw # → the path you linked $ rw --version # → 0.1.0
Note

All three options point at .build/release/rw inside the repo. Rebuilding keeps them working; moving or deleting the repo breaks them. The Homebrew tap is a nicer experience for a personal tool.

Repo docs

The source repo ships three primary docs at its root: GUIDE.md (this page's terminal-friendly counterpart), CONTRIBUTING.md (build/test, conventional-commit expectation, the open-core scope line — single-repo work is welcome; cross-repo aggregation is the paid layer), and RELEASING.md (the two-PR release flow grounded in scripts/release.sh). The README's "Docs" section links all three.

// section two

02 The mental model #

Every rw command analyzes a change set: the diff between a head ref and a base ref, in a repo. Three independent knobs:

KnobFlagDefaultMeaning
Repo--repo <path>current directorywhich git repo to read
Base--base <ref>mainwhat you compare against
Head--head <ref>HEADwhat you compare

So rw many --base develop means: in the repo I'm in, count what my current branch added on top of develop.

--base and --head accept any git ref — a branch, tag, or SHA:

$ rw --base v1.2.0 --head v1.3.0 # what shipped between two tags $ rw --base main --head a1b9f3c # main vs a specific commit $ rw many --base origin/main # vs the remote's main (uses local data)

Offline and read-only

The deterministic commands never write to the repo, never fetch, and never call a model. origin/* refs reflect your last git fetchrw won't fetch for you.

Classification

Counts come first from conventional-commit prefixes (feat:, fix:, chore:, …) — the authoritative signal, never overridden. For commits with no recognized prefix, rw infers a bucket from the subject verbs (add/ship/implement → feature; fix/resolve → fix; bump/refactor/rename → chore) and the diff (a change set that adds new source files leans feature).

Inferred counts are flagged in the output — e.g. (2 inferred from message/diff — not conventional commits) — so you can always tell a declared count from a guessed one. The parsed type is never overwritten; --json shows both type and inferredBucket. For the richest read on non-conventional history, rw new (semantic) reads the full diff and recovers features even more precisely.

// section three

03 Your first run #

$ cd ~/path/to/any/repo $ rw

You get the vitals dashboard: feature/fix/chore counts, files changed with insertions/deletions, contributors, branches involved, the highest-churn files (hotspots), and any advisory risk flags (large diff, core files touched, source changed with no tests alongside). Risk flags are a nudge, never a gate.

Everything here is deterministic and instant — no key, no network.

$ rw --base develop # compare against develop instead of main $ rw --json # the same data as raw change_report.json
// section four

04 Commands #

CommandWhat it doesMode
rw (default)vitals dashboard (plain text only)offline
rw vitalsvitals dashboard with full --json / --htmloffline
rw manycount features / fixes / chores + breakdownoffline
rw blameattribution by contributoroffline
rw branchwhich branches contributedoffline
rw newlist the new user-facing featuressemantic
rw notes <target>write a review/release notesemantic
rw market --product <id>a marketing content pack in the product's voicesemantic
rw align --product <id>cross-port parity — matcher, HTML matrix, drafted issuessemantic
rw help [topic]the built-in guide

Global flags on every subcommand: --repo, --base, --head, --json (emit the raw change_report.json instead of the formatted view — works offline on any command, including the semantic ones), and --html (render the same report to a single self-contained, offline HTML file — see §6). --html takes two companions: --html-out <path> to override the default location and --open to launch the file in your default browser after writing.

Bare rw (no subcommand) only prints the formatted vitals dashboard as plain text. For the JSON or HTML versions of vitals, use the explicit rw vitals subcommand — those flags don't live on the root command anymore (it would shadow the same-named flags on subcommands like align). Existing scripts that piped rw --json > report.json need rw vitals --json > report.json.

rw align — cross-port parity

rw align compares two native ports of the same product (Ledgerly iOS in Swift and Ledgerly Android in Kotlin, say) and surfaces parity gaps. Unlike every other rw command, it reads two repos with no shared git history — there's nothing for git diff to compare. Instead it builds a feature ledger for each side, runs a semantic matcher that classifies each pairing, drafts tracker-agnostic issues for confirmed gaps, and (optionally) renders the whole thing as a filterable HTML parity matrix.

Input modes

$ rw align --product ledgerly # uses [product.ports] from testthese.toml $ rw align --with ../ledgerly-android # current repo is side A $ rw align --a ./ios --b ./android # both sides explicit

Output modes

$ rw align --product ledgerly # human-readable parity read (default) $ rw align --product ledgerly --html --open # filterable parity matrix in the browser $ rw align --product ledgerly --json # raw AlignReport JSON $ rw align --product ledgerly --ledger-only # skip matcher; ledgers only — offline, key-free

--json on rw align emits an AlignReport, not the change_report.json you get from the other commands. The two views answer different questions.

How the matcher classifies

Each feature pairing gets one of five labels with a confidence score:

  • paired — same capability on both sides; real parity.
  • equivalent — different but platform-native substitutes (Apple Pay ↔ Google Pay). Parity achieved, not a gap.
  • gap_on_a / gap_on_b — present on one side only; real missing work, with a drafted issue ready to paste.
  • ambiguous — the matcher isn't sure; surfaced for human confirmation, never auto-classified as a gap.

A built-in Apple↔Google equivalence table ships with rw — Apple Pay/Google Pay, StoreKit/Play Billing, Keychain/Keystore, HealthKit/Health Connect, APNs/FCM, iCloud/Drive, WidgetKit/Glance, SwiftUI/Compose, Core Data/Room, Combine/Flow, TestFlight/Play Internal Testing, Sign in with Apple/Google Sign-In. Per-product [[product.parity.equivalent]] entries extend it.

The parity matrix (--html)

--html renders the report as a single self-contained, offline HTML file — same aesthetic as the rest of rw's HTML output. Two-column layout (side A left, side B right), each feature carrying its status chip and confidence pill. Filter chips at the top let you focus on just gaps, just ambiguous, etc. Drafted issues appear below the matrix with copy buttons. --html-out <path> overrides the default .rw/align-<a>-<b>.html; --open launches it in the browser after writing.

The pre-v0.3 names --matrix, --matrix-out, --open-matrix, --page, and --emit-json still work as aliases for one release. Prefer --html and --json going forward; the aliases go away in v0.4.

Drafted issues (--issues)

The matcher's IssueDraft body is canonical Markdown. --issues adds tracker-flavored wrappers without any API call: markdown (default), linear (h1 title + Labels: line), github (Title: / Labels: prefixes that pipe cleanly into gh issue create), jira (wiki markup — h2., *bold*, {{code}}, * bullets).

Confirming equivalences (--confirm)

When the matcher flags an equivalent or ambiguous pair you agree with, rw align --product <id> --confirm <match-id> appends a [[product.parity.equivalent]] entry to testthese.toml so it stops re-flagging on every run. Idempotent: re-confirming the same pair is a no-op.

Configuration

[[product]] id = "ledgerly" name = "Ledgerly" [product.ports] a = { name = "ios", path = "../ledgerly-ios", base = "main" } b = { name = "android", path = "../ledgerly-android", base = "main" } since = "v1.0" # port-start tag; earlier features are ignored [[product.parity.equivalent]] a = "Apple Pay checkout" b = "Google Pay checkout" note = "platform-native payment"

Every AlignReport ships with a non-optional disclaimer: parity is presented as candidates to confirm, not authoritative parity. A parity tool that's confidently wrong makes you relax when you shouldn't.

// section five

05 Writing prose: new and notes #

These two turn the change set into text. They need a provider (see §7) — either an API key, or skill mode with no key.

rw new

Lists the genuinely new, user-facing features, folding out chores and refactors.

$ rw new $ rw new --base develop --provider skill

rw notes <target>

Writes a note for exactly one target. Each has its own audience, tone, and character ceiling:

FlagDestinationVoice?Cap
--prPR description (technical)nonone
--asc-reviewerApp Store Connect → App Review (private)nonone
--what-newTestFlight / Play testing notesyesplatform
--asc-updateApp Store "What's New"yes4000
--gp-updateGoogle Play release notesyes500/lang
--changelogRelease-page changelog (always HTML)nonone
$ rw notes --pr $ rw notes --asc-update --product ledgerly $ rw notes --gp-update --product ledgerly --limit 300 $ rw notes --changelog # → docs/changelogs/v<version>.html

rw notes --changelog

A dedicated target for the release-page changelog you commit per version. Always renders HTML (no terminal form) and defaults its output to docs/changelogs/v<version>.html. Generates a clean Added / Changed / Fixed page with a "Generated by rw" credit — the canonical "what changed" artifact for a release, not a paste-into-form note.

Product voice

The user-facing store targets read voice + a soft length aim from the product profile, so pass --product <id> (configure profiles in testthese.toml, §9).

Character caps are hard ceilings

rw warns if output would overflow — it never silently truncates. --limit <n> only tightens; it can't raise the ceiling past the store limit. Ceilings come from a TTL-cached limits.json manifest with a baked-in offline fallback; --refresh-limits forces a refetch.

Platform

--what-new keys off the product's platform: generous on iOS (TestFlight), tight on Android (Play, 500/lang).

// section six

06 HTML output #

Every command takes --html, which renders the same report to a single self-contained, offline HTML file — inline CSS/JS, no CDN, no server, no extra model calls beyond what the command already made. Use it when terminal scrollback is the wrong surface for what you produced: a marketing pack you'd rather eyeball before pasting, a PR description with a live cap meter, or a changelog you commit to the repo.

$ rw --html # vitals as an HTML page $ rw market --product ledgerly --html --open # store-listing proof sheet $ rw notes --asc-update --product ledgerly --html $ rw notes --changelog # changelog is always HTML

The default output path is .rw/<command>-<range>.html under the repo. Override with --html-out <path>, or pass --open to launch the file in your default browser after writing.

What renders, per command

  • rw / rw many / rw blame / rw branch — the deterministic dashboards as a static page (counts, hotspots, attribution, risk flags).
  • rw new — the feature list as a clean reading view.
  • rw notes — the note rendered in context with a live char meter against its store cap: plain at first, amber as you near the limit, red when you exceed it.
  • rw market — an App Store / Play Console proof sheet: each piece (What's New, promo text, subtitle, post, tweet) in its own block with a live cap meter and a copy button. The page you'd want before pasting into a store form.
  • rw notes --changelog — a release-page changelog (always HTML; see §5).

The .rw/ directory

The default output folder is added to .gitignore, so generated HTML doesn't end up tracked. The one exception is rw notes --changelog, which writes under docs/changelogs/ by design — that one is meant to be committed.

Skill mode

--html doesn't apply when --provider skill is in play — in skill mode the host agent is the renderer, not rw. Combining the two raises a clean validation error rather than silently doing nothing.

// section seven

07 Providers — who writes the prose #

The semantic commands run through a provider, chosen by the provider key in testthese.toml or the --provider flag (the flag wins):

ProviderNeeds a key?What it does
anthropic (default)yesCalls the Anthropic API and prints the prose
geminiyesCalls the Google Gemini API and prints the prose
skillnoEmits JSON for a host agent to complete (see §8)

API key (anthropic / gemini): set the provider's env var — ANTHROPIC_API_KEY or GEMINI_API_KEY (preferred) — or api_key in testthese.toml. The environment variable wins. With gemini selected and the model left at the Anthropic default, rw automatically uses a Gemini default model. If no key is set, the semantic commands exit with a clear message — not a crash.

Heads up

A Claude Max/Pro plan covers claude.ai and Claude Code, not the pay-as-you-go Anthropic API. If you don't have API credits, use skill mode (§8) — it routes the model work through your agent session at no extra cost.

// section eight

08 Skill mode — no API key #

In skill mode, rw does all the deterministic work and emits a JSON envelope; a host agent (Claude Code, Cowork) reads it and writes the prose. No key, no network call from rw — the agent you already pay for is the model.

From the CLI

$ rw new --provider skill # prints the envelope; hand it to your agent $ rw notes --pr --provider skill

As an installed skill (recommended)

rw ships a drop-in skill in the skill/ directory. Install it once:

$ mkdir -p ~/.claude/skills/recap-wins $ cp -R skill/ ~/.claude/skills/recap-wins/

Then, in a skill-aware agent, just ask in plain language:

  • "What did I ship on this branch?"
  • "Write a PR description for this branch."
  • "Write the App Store release notes for this update."

The agent runs rw, reads the change report, and writes the answer — grounded in your real commits and diff, within any character caps.

Reminder

Skills load at session start. After installing, start a fresh agent session so it's picked up.

The envelope

The JSON new/notes emit in skill mode contains:

  • system — the prompt (tone, audience, structure) the agent follows
  • user — the grounded change set; facts to write from, never to invent
  • softTargetChars / ceilingChars — the length aim and the hard limit (0 = uncapped)
  • report — the full deterministic change_report.json
// section nine

09 Configuration #

Per-repo, human-readable. Drop a testthese.toml in the repo root. Every key is optional — rw works with no config. Copy testthese.toml.example to start.

base = "main" model = "claude-sonnet-4-6" provider = "anthropic" # or "skill" # api_key = "sk-ant-..." # prefer the ANTHROPIC_API_KEY env var instead [limits_manifest] # store caps: fetched + cached with a TTL # url = "https://.../limits.json" ttl = "30d" [review_notes.limits] # offline fallback ceilings (chars; 0 = uncapped) pr = 0 asc_update = 4000 gp_update = 500 what_new_android = 500 [[product]] id = "ledgerly" name = "Ledgerly" voice = "clear, trustworthy, private-by-default; money without anxiety" links = ["https://novarch.lol/ledgerly"] platform = "iOS" # drives the --what-new cap # soft editorial aims (chars); must be <= the hard ceilings above targets = { asc_update = 300, gp_update = 250, what_new = 350 }

Two tiers of limit, kept distinct:

  • Ceilings ([review_notes.limits]) — hard platform caps. Never exceeded; rw warns on overflow.
  • Targets (per-product) — soft editorial aims the model shoots for, so a 4000-char ceiling doesn't yield a 4000-char wall.
Tip

The real testthese.toml is gitignored so an api_key can't be committed. The tracked file is testthese.toml.example.

// section ten

10 Recipes #

See what a branch introduced, offline

$ rw --base main $ rw many --base main # just the counts

Write a PR description without leaving the terminal (with an API key)

$ export ANTHROPIC_API_KEY=sk-ant-... $ rw notes --pr

Same, but key-free via your agent

$ rw notes --pr --provider skill | pbcopy # then paste into Claude Code # …or just ask the installed skill: "write a PR description for this branch"

App Store release notes in a product's voice, within the cap

$ rw notes --asc-update --product ledgerly

Recap a repo you're not in

$ rw --repo ~/code/mogwar --base release/2.0 --head feature/onboarding

Feed the structured report to another tool

$ rw --json > report.json

Preview a marketing pack before pasting into the store

$ rw market --product ledgerly --html --open

Commit a release-page changelog

$ rw notes --changelog # writes docs/changelogs/v<version>.html $ git add docs/changelogs && git commit -m "docs: changelog for v<version>"
// section eleven

11 Troubleshooting #

zsh: command not found: rw
The binary isn't on your PATH. Re-do the symlink/alias step in §1, then which rw.
Not a git repository
You're not inside (or --repo doesn't point at) a git work tree.
rw many says "0 features" but I clearly shipped features
Your commits don't use conventional-commit prefixes, so they bucket as chores. Use rw new (semantic), which reads the diff and recovers the real features.
No Anthropic API key / No Gemini API key
Set the provider's env var (ANTHROPIC_API_KEY or GEMINI_API_KEY), or use --provider skill for the key-free path (§8).
Output overflows a store cap
rw warns but doesn't truncate. Tighten with --limit <n>, or regenerate; the store form is the final authority.
origin/main looks stale
rw never fetches. Run git fetch first.