Decisions

August 25, 2026 · View on GitHub

Technical decisions that are not obvious from the code, recorded so a contributor does not have to re-litigate them. Newest first. Each entry says what was decided and why; if the reasoning stops holding, change the code and add a new entry rather than editing an old one.

2026-08-17 - plugin.json stays at 0.1.0 while the binary is at 0.5.0

The plugin's version is not bumped to match the release it ships alongside.

The two numbers describe different artifacts. 0.5.0 is the binary; 0.1.0 is five files that shipped once, yesterday. Coupling them means bumping the plugin on every binary release including the ones where no skill changed, and the first time that is forgotten the number lies in the other direction - worse than being honestly behind.

The version is also a path component of the install directory: a remote install lands the plugin in ~/.claude/plugins/cache/skillreaper/skillreaper/<version>/, verified today by installing from GitHub. Bumping it therefore moves that directory and forces a refresh for anyone already installed, in exchange for a cosmetic match. That is what settled it: the change is not free, and what it buys is only that two numbers on a page agree when they are not measuring the same thing.

Bump it when the skills actually change.

2026-08-17 - --agent prints no price; every other format still does

RenderAgent omits the ~$X.XX/month segment. --json, --md and the interactive report keep it.

A dollar figure is not a measurement. It is dead tokens × sessions × a price per token that we do not control and that moves whenever a provider updates its list - the same stack has been reported at $24, $1.54, $1.84, $1.96 and $2.32 in two months without its context growing meaningfully. Item counts, token counts and utilization are measured facts; the price is a multiplication by an arbitrary constant.

That distinction matters more in this format than in any other, because --agent's entire contract is that its bytes are pasted verbatim into a conversation and then read by a person as the tool's own claim. The other formats have a reader who knows what they are looking at: --json is parsed by a program that can decide what to trust, and --md and the interactive report both carry the "figures are estimates" framing on screen. Pasted prose carries none of that, so a stale price there reads as a stated fact rather than an estimate.

MoneyPerMonth stays on Report and is still computed - dropping it from one renderer is a presentation decision, not a reason to lose the number. The guard is TestRenderAgentOmitsMoney, which renders a fixture that has a non-zero MoneyPerMonth and fails if a $ reaches either agent format.

2026-08-17 - The gofmt cleanup waits for PR #22, not the other way round

internal/cost/cost_test.go and internal/platform/platform.go drifted from gofmt under go1.26. The fix is trivial and is held back anyway, in this order: #22 merges → chore/repo-debt rebases → chore/repo-debt merges → only then the gofmt -l check enters CI.

gofmt realigns the struct block in TestTokens, at cost_test.go:29-38. PR #22 inserts TestTokensFor at @@ -29,6 +29,32 @@ - the same lines. Landing the cleanup first would hand a first-time contributor a rebase conflict created entirely by our own formatting debt, immediately after telling them in review that the gofmt noise was not theirs to fix. Whose branch absorbs a conflict is a social decision, not a mechanical one, and it belongs to the person who owns the debt.

The CI check goes last for a duller reason: enabling it while the tree is still unformatted turns the next push red.

2026-08-17 - reap --agent renders for agents; the skills stop rendering

Supersedes "Plugin skills call reap --json, not reap" below. That entry stays as written: the reasoning was sound and the outcome was still wrong, and a log that quietly edits itself is not a log.

RenderAgent and RenderGapAgent in internal/report/agent.go emit compact plain text with no ANSI, no bars, no box drawing and no terminal-width padding. The two SKILL.md files now say "run it, paste it verbatim" and carry no rendering rules at all.

What changed our mind: a prose render spec grows a rule for every edge case somebody hits - column widths, truncation, zero denominators - and every line of it is context loaded in every session, which is the dead weight this tool exists to measure. A skill that must gain weight to stay deterministic has lost its own argument. The delegated rendering also disagreed with the binary on the numbers, not just the layout: utilPct truncates, the spec said "round", so the same data was reported as 1% by reap gap and 2% by the skill. In the binary that is one pctOf and a table test; in prose it was a paragraph nobody could verify.

The verbatim rule has now been broken twice, and only the first break was a model rendering a number: the second was an agent retyping an output block into a message instead of reading it from a file, adding two words (you can) that are in no version of the source - inside the session that was verifying this format prevents exactly that. The cause is not the actor, it is copying by hand instead of generating. Every check that held today went through a file and diff.

New functions rather than a flag threaded through the render path. RenderText already takes a color bool, and color=false does not produce this format - the box drawing, the ▰▱ bars, the 60-dash rules and the 44-char name clip all survive it. A second bool would have to reach six functions, and the two outputs would then share branches that can drift apart in silence.

2026-08-17 - --agent carries a signature; --json and --md still do not

RenderAgent writes its attribution line unconditionally: no colour flag, no TTY check, no cooldown, and it never touches NudgeState or RenderFooter.

This is a deliberate exception to the rule one entry below, and the difference is who reads the bytes. --json and --md are piped into other programs, where a signature is a parse error waiting to happen. --agent is prose an agent hands to a person, so the attribution belongs in the payload. Putting it in the SKILL.md instead was the obvious alternative and was rejected: it would leave exactly one formatting instruction in the prompt, and that is the crack the whole render spec grew back through last time.

2026-08-17 - TokenRatios is a config table, not control flow

Per-model character-per-token ratios live in an exported TokenRatios map in internal/cost, parallel to the existing ModelPricing map. Models absent from the map fall back to the default 3.7 ratio.

Adding a model means adding a row, never touching tokensWithRatio or any branch. That is the same extension point ModelPricing already established, so the two maps stay learnable as one idea.

RenderFooter prints a permanent attribution line: no cooldown, no token threshold, no opt-out, and it never touches NudgeState. The star-CTA remains separate and stays throttled.

It lives in cmdReport's default branch, which is what keeps it out of --json, --md and --quiet without a hand-written gate - machine-readable output must stay parseable. The colour flag decides only how the line is painted, never whether it is printed, so redirecting to a file still carries attribution.

2026-08-17 - Plugin commands are namespaced /skillreaper:reap

The Claude Code plugin exposes /skillreaper:reap and /skillreaper:gap. The namespace is not abbreviated.

The verbosity is deliberate: the tool's name is repeated at every invocation, and a short alias would collide with unrelated commands in a user's install.

2026-08-17 - The plugin does not bundle the binary

The plugin assumes reap is on PATH and prints a runnable install block when it is not, leading with npx skillreaper because that needs no install at all.

reap already ships through Homebrew, npm/npx and go install. Bundling it would mean committing six GOOS/GOARCH builds; downloading it from the plugin would duplicate the checksum-verified fetch npm/lib/release.js already performs. The skills resolve availability with command -v reap only - never an absolute path, never a filesystem search, because a copy found off PATH may be an older build and silently using it hides a broken install.

2026-08-17 - Plugin skills call reap --json, not reap

Both skills run the JSON mode and render the result themselves, following an explicit render spec written into each SKILL.md.

The human report is a ~30-line ANSI table. Injecting it into an agent's context only to have the model repeat it back is precisely the dead weight this tool exists to find. Known cost: rendering delegated to a model is not deterministic - the same spec has produced different column widths across runs. The render specs pin column order, row limits, rounding and truncation to narrow that, and the trade-off is revisited if the drift proves not to be containable.

2026-08-17 - The plugin ships no hooks/hooks.json

The plugin deliberately contains no hooks.

reap install-hook writes a SessionStart nudge into the user's own settings.json, keyed by the marker comment skillreaper-weekly-nudge. A hook shipped in the plugin would be a second, independent copy: anyone who already ran install-hook would be nudged twice, and reap uninstall-hook can only remove the settings.json copy, never the plugin's. If hooks are ever wanted here, the plugin must detect that marker and supersede the manual hook rather than coexist with it.