Decision Ledger Claude Code mod: a Claude Code decision log for a project
A /decision command that records a choice, the reason and the rejected alternative, tied to a file or symbol.
Permissions
- Reads filesCan read files and list directories on your machine ($.fs.read / list / stat / exists).
- Runs commandsCan run shell programs on your machine ($.process.run / spawn). Check the source for exactly which ones.
- Intercepts tool callsSees every tool call Claude makes (tool.call / tool.check) and could block or change it.
- Changes the UIDraws into the Claude Code interface: bands, panes, status text, toasts (ui.render / $.ui.*).
- Adds commands/toolsRegisters new slash commands or tools that you or Claude can invoke ($.command.register / $.tool.register).
- Stores dataKeeps data between sessions in the plugin's private store ($.store.*).
- tested with
- v2.1.307
- last tested
- requires
- Claude Code ≥ 2.1.287
- surfaces
- transcript
- source
- YeonwooSung (Apache-2.0)
Install inside Claude Code:
/plugin install decision-ledger --marketplace YeonwooSung/my-claude-code-modsWhat does this mod do?
Decision Ledger is a community mod by YeonwooSung (YeonwooSung), one plugin in the my-claude-code-mods collection. It is a small Claude Code decision log. When you settle a design question during a session, you write down the choice, the reason and the option you turned down, and attach it to a file or symbol such as src/cache.ts#lookup. Later you can list everything, filter by file, or remove an entry.
It adds one slash command, /decision:
/decision add <file#symbol> | <choice> | <rationale> | <rejected alternative>records a decision. The rejected alternative is optional./decision listprints every decision, and/decision list src/cache.tsprints only those whose reference contains that text./decision remove <id>deletes one entry./decision resetclears this mod's records./decision helpprints the syntax.
The reply appears as command text in the transcript, so it works in the terminal, in Desktop and in headless sessions. Each entry is printed as an id and reference, then Choice, Why and Rejected lines. Nothing is recorded unless you type it.
Install
Use any of the methods in the install block; they install straight from the author's own marketplace, my-claude-code-mods. Reload plugins afterwards (or restart Claude Code) so /decision is registered. To try it without installing, point --plugin-dir at the decision-ledger folder of a checkout:
claude --plugin-dir ./decision-ledger$ install decision-ledger
Requires Claude Code ≥ 2.1.287
Installs from the author's own marketplace: YeonwooSung/my-claude-code-mods
1.One line, inside Claude Code
/plugin install decision-ledger --marketplace YeonwooSung/my-claude-code-modsPaste into a running session (v2.1.275+). Claude Code asks to add the marketplace first.
2.From your shell
claude plugin marketplace add YeonwooSung/my-claude-code-mods claude plugin install decision-ledger@my-claude-code-modsThen run /reload-plugins in any session that is already open.
3.Try it without installing
git clone https://github.com/YeonwooSung/my-claude-code-mods claude --plugin-dir ./my-claude-code-mods/decision-ledgerLoads the mod for one session only. Nothing is added to your settings.
How it works
The plugin is one hooks module, hooks/register.ts, plus a data class in hooks/core/feature.ts and shared helpers in hooks/core/runtime.ts and hooks/core/redact.ts. On session.start it registers the command. On command.run it loads the saved state from $.store, hands the arguments to the Feature class, and writes the state back only if it changed. The add branch, from feature.ts:
if (verb === 'add') {
const [reference, choice, rationale, rejected = ''] = entries(text)
if (!reference || !choice || !rationale) return 'Usage: ' + HELP
this.data.decisions = bounded([...this.data.decisions, { id: this.data.nextId++, reference: safeText(reference, 500), choice: safeText(choice, 1000), rationale: safeText(rationale, 2000), rejected: safeText(rejected, 1000) }])
}entries splits on |, safeText strips terminal escape sequences and masks obvious secrets, and bounded keeps the last 200. Commands run one at a time through a queue, and the key includes the working directory and session id, so two folders never share a ledger. If the save fails, the command reports an error and the stored state is left as it was.
Customize it
There are no options in /config. The limits are constants in the source: 200 entries (bounded in runtime.ts), per-field length caps of 500, 1000, 2000 and 1000 characters (feature.ts), and a 256 KiB cap on saved state (register.ts). Edit them in a local checkout and load it with --plugin-dir if you want different ones.
Permissions & safety
We read every file the plugin can execute before running it: .claude-plugin/plugin.json, hooks/hooks.json, hooks/register.ts, hooks/core/feature.ts, hooks/core/runtime.ts, hooks/core/redact.ts, and the two test files. There is no package.json inside the plugin, no install script, no bin/, no bundled or minified code and no binary. Findings:
- Network: none. We searched for
fetch,httpandchild_processand found nothing. No telemetry. - Processes:
register.tsbuilds a helper object with$.process.run, but the only command,Feature.command, never calls it. The helpers that would rungit(runtime.ts) andnode -e(register.ts:24) are unused by this mod. They are shared code from the author's other plugins. - Files: no direct writes.
$.fs.read,$.fs.statand$.fs.listare wired into the same unused helper object. State goes through$.store, the plugin's own store; nothing is written to~/.claude, settings, shell files or LaunchAgents. - Daemons: none.
- Secrets: it reads no environment variables or credential files. Text you enter is passed through
redactSecrets, which masks things like--password, bearer tokens,KEY=assignments and URL passwords. The README calls this best effort, so do not type secrets into a rationale. - Prompt influence: none. It injects nothing into prompts and returns only the command's own text.
- Tool hook:
tool.callis registered, but becauseFeaturehas noobservemethod (register.ts:9) it only callsnext(e)and returns. It never blocks or rewrites a tool call. - Requirements: none beyond Claude Code with function hooks.
- License: Apache-2.0, with a
LICENSEfile at the repository root.
claude plugin validate passed. It listed two notes that the command.run and tool.call hooks have no .catch, and one non-blocking warning from its directory check that the plugin has no icon. The author's 10 tests (2 files) passed with claude plugin test on Claude Code 2.1.307.
- Reads files
- Can read files and list directories on your machine ($.fs.read / list / stat / exists).
- Runs commands
- Can run shell programs on your machine ($.process.run / spawn). Check the source for exactly which ones.
- Intercepts tool calls
- Sees every tool call Claude makes (tool.call / tool.check) and could block or change it.
- Changes the UI
- Draws into the Claude Code interface: bands, panes, status text, toasts (ui.render / $.ui.*).
- Adds commands/tools
- Registers new slash commands or tools that you or Claude can invoke ($.command.register / $.tool.register).
- Stores data
- Keeps data between sessions in the plugin's private store ($.store.*).
Compatibility & troubleshooting
Checked with Claude Code 2.1.307 on macOS (validate and tests). The author tested on 2.1.302 and says the live terminal and Desktop rendering was not checked. If /decision does not respond:
- Run
/reload-pluginsor restart Claude Code after installing. - Run
/decision helpto see the syntax. A missing choice or rationale returns the usage line instead of saving. - Use
|only as the separator; the add command cannot hold a pipe inside a field. - "Session changed; operation not applied" means the folder or session id changed while the command was running. Run it again.
- An empty ledger after a restart usually means a different folder or session id, because entries are keyed on both.
To check that it works, run /decision add a.ts#f | choice | reason, then /decision list.
FAQ
- Does Decision Ledger work out decisions from the conversation?
- No. Every entry is text you type into /decision add. The README says decisions are explicit user input, never inferred from model reasoning, and there is no model call and no automatic source editing.
- Where are the decisions stored?
- In the plugin's own store through $.store, under a key made of the working directory and the session id. Resuming the same directory and session id brings the entries back; a different folder or session starts with an empty ledger.
- How many decisions can it hold?
- The latest 200. Adding a 201st drops the oldest. Ids keep counting up and are not reused after a removal, and the saved state is capped at 256 KiB.
- Does it send anything over the network?
- Not in the code we read. There is no fetch, HTTP or socket call anywhere in the plugin, and no telemetry.
- Can I use a pipe character inside a rationale?
- No. The add command splits its arguments on the pipe character, so each of the four fields has to be free of it. Anything after the fourth pipe is dropped.
# Related mods
FeaturedPin Memo
Pin notes and clickable todo checkboxes above the Claude Code prompt with /pin, saved per session.
- above-prompt
- Changes the UI
- Adds commands/tools
- Stores data
tested with v2.1.303
FeaturedContext Inspector
Shows what fills your Claude Code context, how much is re-reads and duplicates, and what compaction kept.
- status
- pane
- toast
- Writes files
- Intercepts tool calls
- Reads your prompts
- Changes the UI
- Adds commands/tools
tested with v2.1.303
More in Timers, todos, standups, Slash commands.