Skip to content
claudemods

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

Install inside Claude Code:

/plugin install decision-ledger --marketplace YeonwooSung/my-claude-code-mods
All install options ↓

What 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 list prints every decision, and /decision list src/cache.ts prints only those whose reference contains that text.
  • /decision remove <id> deletes one entry.
  • /decision reset clears this mod's records.
  • /decision help prints 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. 1.One line, inside Claude Code

    /plugin install decision-ledger --marketplace YeonwooSung/my-claude-code-mods

    Paste into a running session (v2.1.275+). Claude Code asks to add the marketplace first.

  2. 2.From your shell

    claude plugin marketplace add YeonwooSung/my-claude-code-mods
    claude plugin install decision-ledger@my-claude-code-mods

    Then run /reload-plugins in any session that is already open.

  3. 3.Try it without installing

    git clone https://github.com/YeonwooSung/my-claude-code-mods
    claude --plugin-dir ./my-claude-code-mods/decision-ledger

    Loads 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, http and child_process and found nothing. No telemetry.
  • Processes: register.ts builds a helper object with $.process.run, but the only command, Feature.command, never calls it. The helpers that would run git (runtime.ts) and node -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.stat and $.fs.list are 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.call is registered, but because Feature has no observe method (register.ts:9) it only calls next(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 LICENSE file 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-plugins or restart Claude Code after installing.
  • Run /decision help to 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.
Pin Memo running in Claude CodeFeatured

Pin 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

Context Inspector running in Claude CodeFeatured

Context 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.