idea CLI Overview

August 17, 2026 · View on GitHub

The idea command manages a per-repo backlog stored in fab/backlog.md. It is a lightweight CRUD tool for capturing, triaging, and tracking ideas — small enough to live alongside hand-edited markdown, structured enough to be queryable from the command line.

Binary & Installation

Binary: src/cmd/idea/ (Go binary, distributed as idea).

Install via Homebrew tap:

brew install sahil87/tap/idea

Or install manually from a clean checkout:

./scripts/install.sh

The manual installer builds the binary via ./scripts/build.sh and copies it to ~/.local/bin/idea.

Worktree Behavior

By default, idea operates on the current worktree's fab/backlog.md (resolved via git rev-parse --show-toplevel). Pass --main to target the main worktree's backlog instead; internally, idea resolves the main worktree root by running git rev-parse --path-format=absolute --git-common-dir and taking its parent directory. In the main worktree, both behave identically. This ensures that users in a linked worktree get predictable local behavior unless they explicitly opt into the shared backlog.

The backlog file path can also be overridden globally by --file <path> or by setting the IDEAS_FILE environment variable.

System backlog and out-of-git operation

idea also works outside any git repository and offers a system-level backlog for cross-repo idea capture. The system backlog lives at $XDG_CONFIG_HOME/idea/backlog.md when XDG_CONFIG_HOME is set, and ~/.config/idea/backlog.md otherwise (resolved via Go's os.UserConfigDir). Its parent directory is created on demand on the first mutating write. The file format and all command semantics are identical to a repo backlog — only the path differs.

The backlog path is resolved by this precedence (first match wins). Note that --main and --file/IDEAS_FILE are not independent alternatives: --main selects which root is used, and --file/IDEAS_FILE (when set) are applied within that selected root.

  1. --system — the system backlog, skipping git entirely (reachable from inside a repo too).
  2. --main — root = the main worktree root. Git-only: it still errors with not in a git repository outside a repo. A --file/IDEAS_FILE value, if set, is rooted here.
  3. In a git repo (no --main) — root = the current worktree root. A --file/IDEAS_FILE value, if set, is rooted here; otherwise {worktree-root}/fab/backlog.md (the default).
  4. Outside a git repo (no --main) — root = the system config dir ($XDG_CONFIG_HOME/idea/, else ~/.config/idea/). A --file/IDEAS_FILE value, if set, is rooted here; otherwise the system backlog (the graceful fallback; commands no longer fail with not in a git repository).

In all rooted cases an absolute --file/IDEAS_FILE value is used verbatim.

--system and --main are mutually exclusive — passing both is a user error and exits non-zero.

Commands

CommandDescription
idea "text"Add a new idea (shorthand for idea add)
idea add "text"Add a new idea to the backlog
idea listList open (uncompleted) ideas
idea show <query>Show a single idea matching the query
idea done <query>Mark an idea as done
idea reopen <query>Reopen a completed idea
idea edit <query>Edit an idea's text in your editor ($VISUAL, then $EDITOR, then vi) on the decoded text
idea edit <query> "text"Replace an idea's text inline
idea rm <query> --yesDelete an idea (requires --yes/-y or the equivalent --force to confirm; --dry-run previews the match without deleting)
idea prune [--yes]Bulk-remove all done ideas (dry run by default; --yes/-y or --force to delete)
idea promote <query>Move an idea from the current worktree's backlog to the main worktree's backlog
idea fmtRewrite the backlog into canonical form, adopting bare checkbox lines (--check reports without writing)

Promote semantics (idea promote <query>): the move preserves the idea's ID, date, and open/done status verbatim; the destination (main worktree) is written before the source (current worktree), so a crash mid-move duplicates the idea rather than losing it. An ID collision in the destination refuses the move with an operational error (exit 1) naming the ID — the ID is never re-minted — and leaves both files untouched. Run from the main worktree (or whenever source and destination resolve to the same file), promote is a no-op: nothing is written, a note: advisory goes to stderr, and the exit code is 0. --main and --system are usage errors with promote (exit 2) — promote defines its own source and destination; --file/IDEAS_FILE apply within each root. Outside a git repository promote fails operationally (not in a git repository, exit 1).

Editor form contract (idea edit <query>, no text argument): an unchanged buffer is a no-op — the backlog is untouched, a note: text unchanged — nothing to do advisory goes to stderr, and the exit code is 0. An emptied buffer is refused: no change, non-zero exit. A non-zero editor exit aborts: the backlog is untouched, non-zero exit. Passing --id/--date with the no-text form still opens the editor, applies the metadata at save, and suppresses the unchanged no-op — a metadata-only change lands without mutating the text.

Exit codes

idea follows the toolkit exit-code convention so a caller (or agent) can branch on why a command failed:

CodeClassExamples
0Successany command that completed its operation
1Operational failureno idea matched a query, ambiguous match, declined consent (rm/prune without --yes/--force), fmt --check on a non-canonical file, file I/O / editor / git-resolution errors
2Usage errorunknown flag, wrong argument count, --system + --main together, shell-init with a missing/unsupported shell

The distinction is between a malformed invocation (fix the command line → 2) and a well-formed invocation whose operation failed (a different remedy → 1). Error message wording is unchanged by this convention; only the exit code distinguishes the two failure classes.

ID Format & Query Semantics

Each idea gets a short 4-character lowercase alphanumeric ID (e.g., [a7k2]) and an ISO date (YYYY-MM-DD). IDs are unique within a single backlog file.

Queries (the <query> argument on show, done, reopen, edit, rm) match against either the ID or the description text. Matching is substring, case-insensitive. If a query matches more than one idea, the command refuses to act and lists the matches. An exact (case-insensitive) ID match takes precedence over substring matches: when the query is exactly an idea's ID, that idea is selected outright, so passing the canonical ID always resolves it — even when that ID string also appears as a substring inside another idea's text.

Parse & Format Behavior (lenient read, canonical write)

idea is liberal in what it accepts and strict in what it emits:

  • Lenient on read. The YYYY-MM-DD: date segment is optional on input, and idea also accepts */+ bullets (in addition to -), arbitrary leading whitespace, and CRLF or LF line endings. A line is recognized as an idea by its [ ]/[x] checkbox plus 4-char [id] anchors. This means a hand-edited or externally-authored backlog of dateless - [ ] [id] text lines is read correctly rather than silently ignored.
  • Canonical on write. Every idea line idea writes uses one canonical form — - bullet, no indentation, LF endings, and a date that is always present (today's date is backfilled when the input had none). A mutating command (done/reopen/edit/rm, or prune --force when it removes items) normalizes all recognized idea lines in the file at once; non-mutating commands (list/show) never rewrite the file.
  • Backfill notice. When a mutating save stamps today's date on one or more previously-dateless items, idea prints a brief advisory notice to stderr (note: stamped today's date on N previously-dateless item(s)), keeping stdout machine-parseable. The notice is suppressed when nothing was backfilled.
  • Explicit canonicalizer. idea fmt rewrites the whole file into canonical form on demand — no semantic change required — and additionally adopts bare checkbox lines lacking the [id] anchor (fresh unique ID, today's date, checked state preserved), turning an existing markdown task list into a managed backlog in one command. It is idempotent (a second run is byte-stable and skips the write), reports to stderr while stdout stays empty, and idea fmt --check writes nothing, prints the would-be report, and exits non-zero when the file is not canonical.

For the full backlog line format — accepted input variants, the canonical output form, date backfill, Shape B pass-through, and the format-contract change note — see backlog-format.md.

External-Consumer Integration

The backlog file is plain Markdown with a stable line format. Any tool that reads fab/backlog.md can discover backlog IDs and descriptions without coupling to idea's internals — this is the contract.

One example consumer is fab-kit's /fab-new, which can accept a backlog ID and pull the description directly from the file when starting a new change. That integration is illustrative, not defining: the file format is the API, and idea is one (canonical) writer of that format.