Title: recap-wins — User Guide | Novarch LLC
URL: https://novarch.lol/rw-guide.html
Description: The long-form user guide for rw (recap-wins): install, the mental model, every command, providers, skill mode, configuration, recipes, and troubleshooting.
Publisher: Novarch LLC (https://novarch.lol)

---

[How it works](https://novarch.lol/recap-wins.html#how) [HTML](https://novarch.lol/recap-wins.html#html) [Voice](https://novarch.lol/recap-wins.html#voice) [Install](https://novarch.lol/recap-wins.html#install) [GitHub →](https://github.com/yohannescodes/recap-wins) [Back to Novarch](https://novarch.lol/index.html)

[Novarch](https://novarch.lol/index.html) / [recap-wins](https://novarch.lol/recap-wins.html) / 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.

// Contents

1. Install
2. The mental model
3. Your first run
4. Commands
5. Writing prose
6. HTML output
7. Providers
8. Skill mode — no API key
9. Configuration
10. Recipes
11. Troubleshooting

// 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:

| Knob | Flag | Default | Meaning |
|---|---|---|---|
| Repo | --repo <path> | current directory | which git repo to read |
| Base | --base <ref> | main | what you compare *against* |
| Head | --head <ref> | HEAD | what 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 fetch` — rw won't fetch for you.

### Classification

Counts come first from [conventional-commit](https://www.conventionalcommits.org) 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 #

| Command | What it does | Mode |
|---|---|---|
| rw *(default)* | vitals dashboard (plain text only) | offline |
| rw vitals | vitals dashboard with full `--json` / `--html` | offline |
| rw many | count features / fixes / chores + breakdown | offline |
| rw blame | attribution by contributor | offline |
| rw branch | which branches contributed | offline |
| rw new | list the new user-facing features | semantic |
| rw notes <target> | write a review/release note | semantic |
| rw market --product <id> | a marketing content pack in the product's voice | semantic |
| rw align --product <id> | cross-port parity — matcher, HTML matrix, drafted issues | semantic |
| 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:

| Flag | Destination | Voice? | Cap |
|---|---|---|---|
| --pr | PR description (technical) | no | none |
| --asc-reviewer | App Store Connect → App Review (private) | no | none |
| --what-new | TestFlight / Play testing notes | yes | platform |
| --asc-update | App Store "What's New" | yes | 4000 |
| --gp-update | Google Play release notes | yes | 500/lang |
| --changelog | Release-page changelog (always HTML) | no | none |

$ 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):

| Provider | Needs a key? | What it does |
|---|---|---|
| anthropic *(default)* | yes | Calls the Anthropic API and prints the prose |
| gemini | yes | Calls the Google Gemini API and prints the prose |
| skill | **no** | Emits 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.
