Recently Written · git

ideamine

An idea inbox for Claude Code: /idea saves ideas at zero tokens; Claude triages them and routes each to the cheapest model that can build it.

git clone https://github.com/equwal/ideamine

Log | Files | Refs


commit af3a5361733fc9c5901a9139ce2ba72917c5a95e
equwal <13551856+equwal@users.noreply.github.com>
2026-09-21 02:30:45 -0700

ideamine 0.3.0: a dashed name for each command, for the slash menu

The slash menu shows skills, not the verbs after /ideas. Thus each
verb is now a skill with its own name:

- /ideas-ls, /ideas-cat, /ideas-rm, /ideas-done, /ideas-reopen: the
  hook answers these with no model call.
- /ideas-go, /ideas-all, /ideas-sort: these call the model, as
  /ideas go, all, and sort did before.
- /ideas alone shows the queue. /ideas with words is a question.

The start and drop verbs are no longer slash commands. The CLI still
has them. The MCP prompts mirror the skills, and a test checks that
each skill has a prompt.

 .claude-plugin/marketplace.json |  2 +-
 .claude-plugin/plugin.json      |  2 +-
 README.md                       | 39 ++++++++++++------------
 package.json                    |  2 +-
 skills/ideas-all/SKILL.md       | 15 ++++++++++
 skills/ideas-cat/SKILL.md       |  9 ++++++
 skills/ideas-done/SKILL.md      | 11 +++++++
 skills/ideas-go/SKILL.md        | 18 +++++++++++
 skills/ideas-ls/SKILL.md        | 11 +++++++
 skills/ideas-reopen/SKILL.md    | 11 +++++++
 skills/ideas-rm/SKILL.md        |  9 ++++++
 skills/ideas-sort/SKILL.md      |  8 +++++
 skills/ideas/SKILL.md           | 28 +++--------------
 src/claude.js                   |  4 +--
 src/hook.js                     | 66 +++++++++++++++++++----------------------
 src/mcp.js                      | 17 ++++++++---
 src/render.js                   |  4 +--
 src/rubric.js                   |  2 +-
 src/store.js                    |  2 +-
 tests/hook.test.js              | 56 +++++++++++++++++-----------------
 tests/mcp.test.js               |  9 ++++--
 21 files changed, 204 insertions(+), 121 deletions(-)
diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json
index 9f41517..4fe57ed 100644
--- a/.claude-plugin/marketplace.json
+++ b/.claude-plugin/marketplace.json
@@ -10,7 +10,7 @@
     {
       "name": "ideamine",
       "description": "An idea inbox for Claude Code. /idea saves an idea without a model call. Claude triages the ideas and picks the cheapest model that can build each one.",
-      "version": "0.2.0",
+      "version": "0.3.0",
       "author": {
         "name": "equwal"
       },
diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json
index 82ae732..ef674bf 100644
--- a/.claude-plugin/plugin.json
+++ b/.claude-plugin/plugin.json
@@ -1,6 +1,6 @@
 {
   "name": "ideamine",
-  "version": "0.2.0",
+  "version": "0.3.0",
   "description": "An idea inbox for Claude Code. /idea saves an idea without a model call, so it works at any time, even at your usage limit. Claude triages the ideas and picks the cheapest model that can build each one.",
   "author": {
     "name": "equwal",
diff --git a/README.md b/README.md
index 09dbde7..41085b3 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@ That line did not call the model. A hook answers `/idea` on your machine before
 - **does not interrupt the current task.** Claude never sees the idea, so it cannot get distracted by it.
 - **works on any model, in any session, and when you are out of usage.** Nothing is sent, so there is nothing to rate-limit.
 
-Every session writes to one archive, `~/.ideamine/ideas.json`, whatever project or model it uses. The archive is a queue. Claude triages new ideas to put the queue in order: it decides which ideas are worth doing and picks the **cheapest model that can build each one**: Haiku for a typo, Sonnet for a feature, Opus for a redesign. Fable is only for the hardest problems. `/ideas go` takes the idea that fits your chat, else the first one, and builds it.
+Every session writes to one archive, `~/.ideamine/ideas.json`, whatever project or model it uses. The archive is a queue. Claude triages new ideas to put the queue in order: it decides which ideas are worth doing and picks the **cheapest model that can build each one**: Haiku for a typo, Sonnet for a feature, Opus for a redesign. Fable is only for the hardest problems. `/ideas-go` takes the idea that fits your chat, else the first one, and builds it.
 
 ## Install
 
@@ -36,21 +36,22 @@ Marketplaces you add yourself do not auto-update. To upgrade, run `claude plugin
 | Command | What it does | Calls the model? |
 |---|---|---|
 | `/idea <text>` | Add an idea. A pasted bulleted list adds one idea per bullet. `#tags` are recorded. | **No** |
-| `/ideas` or `/ideas ls` | List the queue: doing, do (best first), maybe, inbox | **No** |
-| `/ideas ls done` · `ls -a` · `ls here` | List one lane, every lane, or only this project | **No** |
-| `/ideas cat 12` | Show idea #12 in full: brief, model, notes | **No** |
-| `/ideas rm 12 14` | Delete ideas for good | **No** |
-| `/ideas done 12 shipped it` | Mark an idea done, with a note. Also `start`, `reopen`, `drop` | **No** |
-| `/ideas go [12]` | Build the idea that fits this chat, else the first in the queue, or #12, on its recommended model. New ideas are triaged first. | Yes, this is the build |
-| `/ideas all` | Claude reads every idea, takes the ones that fit this chat out of the queue, and does them. The others stay in the queue. | Yes, this is the build |
-| `/ideas sort` | Triage the inbox now and show the queue. You do not have to: `go` triages when it must. | Yes, briefly |
+| `/ideas` | Show the queue: doing, do (best first), maybe, inbox | **No** |
+| `/ideas-ls done` · `-a` · `here` | List one lane, every lane, or only this project | **No** |
+| `/ideas-cat 12` | Show idea #12 in full: brief, model, notes | **No** |
+| `/ideas-rm 12 14` | Delete ideas for good | **No** |
+| `/ideas-done 12 shipped it` | Mark an idea done, with a note | **No** |
+| `/ideas-reopen 12` | Put an idea back in the queue, for example one that the triage skipped | **No** |
+| `/ideas-go [12]` | Build the idea that fits this chat, else the first in the queue, or #12, on its recommended model. New ideas are triaged first. | Yes, this is the build |
+| `/ideas-all` | Claude reads every idea, takes the ones that fit this chat out of the queue, and does them. The others stay in the queue. | Yes, this is the build |
+| `/ideas-sort` | Triage the inbox now and show the queue. You do not have to: `/ideas-go` triages when it must. | Yes, briefly |
 | `/ideas <question>` | Ask about your ideas, e.g. "which ones fit in an hour?" | Yes, briefly |
 
-The verb comes first, as in a shell. `/ideas all` is a command, not a view: to see every lane, use `/ideas ls -a`. `/ideas help` shows the usage. In the plugin menu the commands also appear as `/ideamine:idea` and `/ideamine:ideas`. Both forms work.
+Each command has its own name, so the slash menu shows all of them when you type `/idea`. The plugin menu also shows them as `/ideamine:ideas-go` and so on. Both forms work.
 
-`go` does not ask questions. The queue puts the best `do` ideas first, then the best `maybe` ideas. Ideas that the triage marks `skip` stay out of the queue until you `reopen` or `rm` them.
+`/ideas-go` does not ask questions. The queue puts the best `do` ideas first, then the best `maybe` ideas. Ideas that the triage marks `skip` stay out of the queue until you reopen or delete them.
 
-You save ideas from any session, so the project that ideamine records is only the folder you were in. That can be a scratch folder that is gone. For this reason, `go` and `all` let Claude judge by the text which ideas fit the current chat, and where to build each one. `go` builds in the current project when the idea fits it. Else it uses the recorded folder if that folder exists, or finds the project that the idea is about. If it cannot find the project, it stops and tells you.
+You save ideas from any session, so the project that ideamine records is only the folder you were in. That can be a scratch folder that is gone. For this reason, `/ideas-go` and `/ideas-all` let Claude judge by the text which ideas fit the current chat, and where to build each one. `/ideas-go` builds in the current project when the idea fits it. Else it uses the recorded folder if that folder exists, or finds the project that the idea is about. If it cannot find the project, it stops and tells you.
 
 Claude can also save ideas by itself. If you write "idea: dark mode for the popup" or "save that for later", it calls the `idea_add` tool and continues the current task.
 
@@ -65,9 +66,9 @@ The triage gives each idea a verdict (`do`, `maybe`, `skip`), an impact from 1 t
 | `opus` (Opus 5) | $5 / $25 | ambiguous or cross-cutting work: architecture, hard debugging, performance, security |
 | `fable` (Fable 5.1) | $10 / $50 | only the hardest long-horizon or research-grade problems |
 
-The triage does not run on your session's model. `/ideas go` and `/ideas sort` make one tool call, and the MCP server hands the work to a separate, minimal `claude -p` run on Sonnet (see below). Only new ideas are triaged, once each. A session on Opus or Fable therefore pays the same few cents as a session on Haiku. If that CLI is not available, Claude does the triage itself.
+The triage does not run on your session's model. `/ideas-go` and `/ideas-sort` make one tool call, and the MCP server hands the work to a separate, minimal `claude -p` run on Sonnet (see below). Only new ideas are triaged, once each. A session on Opus or Fable therefore pays the same few cents as a session on Haiku. If that CLI is not available, Claude does the triage itself.
 
-Recommendations are stored as aliases, so they stay valid when a newer model ships under the same name. `/ideas go` gives the build to a subagent on the recommended model. The subagent starts with a clean context, so the build does not also re-read your whole conversation. To override a recommendation, run `ideamine model 12 opus` or ask Claude.
+Recommendations are stored as aliases, so they stay valid when a newer model ships under the same name. `/ideas-go` gives the build to a subagent on the recommended model. The subagent starts with a clean context, so the build does not also re-read your whole conversation. To override a recommendation, run `ideamine model 12 opus` or ask Claude.
 
 ## Headless triage
 
@@ -75,7 +76,7 @@ Recommendations are stored as aliases, so they stay valid when a newer model shi
 ideamine sort
 ```
 
-`/ideas go` and `/ideas sort` use this same engine. It is one `claude -p` call on your normal Claude Code login, with no tools, no MCP servers, no settings, a two-line system prompt, and a JSON schema for the output. Four ideas take about 1,700 input tokens, roughly two cents on Sonnet. A normal model turn in a setup with a few MCP servers can re-read tens of thousands of tokens. To keep the inbox sorted while you sleep, schedule the command with cron or Task Scheduler. `--model haiku` makes it cheaper, and `--dry-run` shows the exact prompt.
+`/ideas-go` and `/ideas-sort` use this same engine. It is one `claude -p` call on your normal Claude Code login, with no tools, no MCP servers, no settings, a two-line system prompt, and a JSON schema for the output. Four ideas take about 1,700 input tokens, roughly two cents on Sonnet. A normal model turn in a setup with a few MCP servers can re-read tens of thousands of tokens. To keep the inbox sorted while you sleep, schedule the command with cron or Task Scheduler. `--model haiku` makes it cheaper, and `--dry-run` shows the exact prompt.
 
 ## Command line
 
@@ -124,15 +125,15 @@ Tools: `idea_add`, `idea_list`, `idea_triage`, `idea_update`, `idea_next`, `idea
 
 ```
 /idea …          ──► UserPromptSubmit hook ──► ~/.ideamine/ideas.json ──► "💡 Saved #43"   (model never called)
-/ideas ls|cat|rm ──► same hook, answers locally
+/ideas, /ideas-ls, -cat, -rm, -done ──► same hook, answers locally
 
-/ideas go        ──► Claude ──► MCP idea_next ──► claude -p (Sonnet, minimal context), for new ideas only
+/ideas-go        ──► Claude ──► MCP idea_next ──► claude -p (Sonnet, minimal context), for new ideas only
                                               ──► verdict · impact · size · cheapest capable model · brief
                             ──► subagent on that model ──► builds it ──► idea_update: done
-/ideas all       ──► Claude ──► MCP idea_list (full) ──► idea_remove for the ideas that fit this chat ──► builds them
+/ideas-all       ──► Claude ──► MCP idea_list (full) ──► idea_remove for the ideas that fit this chat ──► builds them
 ```
 
-The plugin contains a Node MCP server with no dependencies, two skills (the slash commands), and one hook. The hook ignores every prompt except `/idea` and the local `/ideas` commands, and those end before any API call. The hook runs directly, not through a shell, and takes about 130 ms per prompt on Windows. The skills are user-only, so their descriptions add no tokens to your sessions. If the archive cannot be read, the hook lets the prompt through, so the `/idea` skill can still save it with the MCP tool. Your text is never dropped.
+The plugin contains a Node MCP server with no dependencies, ten skills (the slash commands), and one hook. The hook ignores every prompt except `/idea`, `/ideas`, and the local `/ideas-*` commands, and those end before any API call. The hook runs directly, not through a shell, and takes about 130 ms per prompt on Windows. The skills are user-only, so their descriptions add no tokens to your sessions. If the archive cannot be read, the hook lets the prompt through, so the `/idea` skill can still save it with the MCP tool. Your text is never dropped.
 
 ## Development
 
diff --git a/package.json b/package.json
index ecb6cd4..47b8b97 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "ideamine",
-  "version": "0.2.0",
+  "version": "0.3.0",
   "description": "An idea inbox for Claude Code. /idea saves an idea without calling the model; Claude later triages each idea and picks the cheapest model that can build it.",
   "type": "module",
   "bin": {
diff --git a/skills/ideas-all/SKILL.md b/skills/ideas-all/SKILL.md
new file mode 100644
index 0000000..620330e
--- /dev/null
+++ b/skills/ideas-all/SKILL.md
@@ -0,0 +1,15 @@
+---
+name: ideas-all
+description: Do every ideamine idea that fits this chat. Claude takes those ideas out of the queue. The other ideas stay in the queue.
+disable-model-invocation: true
+allowed-tools: mcp__plugin_ideamine_ideamine__idea_list, mcp__plugin_ideamine_ideamine__idea_remove
+---
+
+Do every idea from the queue that fits this chat. Do not ask the user anything.
+
+The user saves ideas from any session. Thus the project that an idea records is only the folder the user was in, and that folder can be wrong or gone. Judge by the text of each idea whether it fits this chat.
+
+1. Call `idea_list` with `full: true`. It returns every idea in the queue, in full.
+2. Choose the ideas that fit this chat: the current project or this conversation. If no idea fits, say so in one line and stop.
+3. Call `idea_remove` with the `ids` of the ideas that fit. This takes them out of the queue. The other ideas stay in the queue for a chat that they fit.
+4. Show the user the ideas that you took, one line each, and how many stay in the queue. Then do the ideas that you took, one at a time, as the user's request.
diff --git a/skills/ideas-cat/SKILL.md b/skills/ideas-cat/SKILL.md
new file mode 100644
index 0000000..28f31e5
--- /dev/null
+++ b/skills/ideas-cat/SKILL.md
@@ -0,0 +1,9 @@
+---
+name: ideas-cat
+description: Show ideamine ideas in full. The ideamine hook answers this with no model call.
+argument-hint: "<id...>"
+disable-model-invocation: true
+allowed-tools: mcp__plugin_ideamine_ideamine__idea_list
+---
+
+For each id in "$ARGUMENTS", call `idea_list` with that `id`. Show each result exactly as returned.
diff --git a/skills/ideas-done/SKILL.md b/skills/ideas-done/SKILL.md
new file mode 100644
index 0000000..c720227
--- /dev/null
+++ b/skills/ideas-done/SKILL.md
@@ -0,0 +1,11 @@
+---
+name: ideas-done
+description: Mark an ideamine idea done, with an optional note. The ideamine hook answers this with no model call.
+argument-hint: "<id> [note]"
+disable-model-invocation: true
+allowed-tools: mcp__plugin_ideamine_ideamine__idea_update
+---
+
+Request: $ARGUMENTS
+
+Call `idea_update` with status "done". The first word of the request is the `id`. The other words, if there are any, are the `note`. Reply with the one-line result.
diff --git a/skills/ideas-go/SKILL.md b/skills/ideas-go/SKILL.md
new file mode 100644
index 0000000..d9593a0
--- /dev/null
+++ b/skills/ideas-go/SKILL.md
@@ -0,0 +1,18 @@
+---
+name: ideas-go
+description: Build the next ideamine idea, or a given id, on the cheapest Claude model that can do it. New ideas are triaged first. It does not ask questions.
+argument-hint: "[id]"
+disable-model-invocation: true
+allowed-tools: mcp__plugin_ideamine_ideamine__idea_next, mcp__plugin_ideamine_ideamine__idea_update, mcp__plugin_ideamine_ideamine__idea_triage
+---
+
+Build one idea with the model recommended for it. Requested idea: $ARGUMENTS (empty means the next idea in the queue). Do not ask the user anything.
+
+The user saves ideas from any session. Thus the project that an idea records is only the folder the user was in, and that folder can be wrong or gone. Judge by the text of the idea which idea fits this chat, and where it belongs.
+
+1. Call `idea_next` with `triage: true`. Pass `id` if an id is given. The tool triages new ideas first. If it says that the queue is empty, say so in one line and stop. If the triage failed and no idea came back, call `idea_triage` with no verdicts, judge those ideas, save all verdicts in ONE `idea_triage` call with your model name as `by`, and then call `idea_next` again.
+2. If no id is given, the tool also returns the whole queue. If an idea in the queue clearly fits this chat (the current project or this conversation), call `idea_next` with its `id`, and build that idea. Else build the first idea.
+3. Choose the directory for the build. Use the current project if the idea fits it. Else use the project directory of the idea if it exists and fits the idea (the tool tells if it exists). Else find the project that the idea is about. If you cannot find it, say so in one line and stop.
+4. Tell the user the title, the recommended model, and the directory in one line. Then call `idea_update` with status "doing" and `project` set to that directory.
+5. Give the build to ONE subagent (Agent tool, general-purpose). Set its `model` to the recommended model (haiku, sonnet, opus, or fable). This sends each idea to the cheapest model that can do it. Set `run_in_background` to false. Step 6 must run in this turn, because the permission to use the ideamine tools ends with the turn. In the subagent prompt, include the brief, the original note, and the directory. Tell the subagent to work only in that directory, and to report what it changed and what is left to do.
+6. When the subagent returns, call `idea_update`. Use status "done" with a one-line note. If work remains, keep status "doing" and write a note that says what remains. Then summarize in 2-3 lines.
diff --git a/skills/ideas-ls/SKILL.md b/skills/ideas-ls/SKILL.md
new file mode 100644
index 0000000..f90cf40
--- /dev/null
+++ b/skills/ideas-ls/SKILL.md
@@ -0,0 +1,11 @@
+---
+name: ideas-ls
+description: List the ideamine queue, one lane, or every lane (-a). The ideamine hook answers this with no model call.
+argument-hint: "[inbox|do|maybe|skip|doing|done|dropped|-a] [here]"
+disable-model-invocation: true
+allowed-tools: mcp__plugin_ideamine_ideamine__idea_list
+---
+
+Call `idea_list` for this request: $ARGUMENTS
+
+A lane name is the `filter`, `-a` is `filter: "all"`, and `here` is `here: true`. Pass other words as `query`. Show the board exactly as returned.
diff --git a/skills/ideas-reopen/SKILL.md b/skills/ideas-reopen/SKILL.md
new file mode 100644
index 0000000..f1a7088
--- /dev/null
+++ b/skills/ideas-reopen/SKILL.md
@@ -0,0 +1,11 @@
+---
+name: ideas-reopen
+description: Put an ideamine idea back in the queue, for example one that the triage skipped. The ideamine hook answers this with no model call.
+argument-hint: "<id> [note]"
+disable-model-invocation: true
+allowed-tools: mcp__plugin_ideamine_ideamine__idea_update
+---
+
+Request: $ARGUMENTS
+
+Call `idea_update` with status "reopen". The first word of the request is the `id`. The other words, if there are any, are the `note`. Reply with the one-line result.
diff --git a/skills/ideas-rm/SKILL.md b/skills/ideas-rm/SKILL.md
new file mode 100644
index 0000000..dff981b
--- /dev/null
+++ b/skills/ideas-rm/SKILL.md
@@ -0,0 +1,9 @@
+---
+name: ideas-rm
+description: Delete ideamine ideas for good. The ideamine hook answers this with no model call.
+argument-hint: "<id...>"
+disable-model-invocation: true
+allowed-tools: mcp__plugin_ideamine_ideamine__idea_remove
+---
+
+Call `idea_remove` with the ids in "$ARGUMENTS" as `ids`. Reply with the first line of the result.
diff --git a/skills/ideas-sort/SKILL.md b/skills/ideas-sort/SKILL.md
new file mode 100644
index 0000000..b8b8834
--- /dev/null
+++ b/skills/ideas-sort/SKILL.md
@@ -0,0 +1,8 @@
+---
+name: ideas-sort
+description: Triage the ideamine inbox now and show the queue. You do not have to, because /ideas-go triages when it must.
+disable-model-invocation: true
+allowed-tools: mcp__plugin_ideamine_ideamine__idea_triage
+---
+
+Call `idea_triage` with `headless: true`. If that call fails, call `idea_triage` with no verdicts, judge every idea, and save all verdicts in ONE `idea_triage` call with your model name as `by`. Show the result exactly as returned: the verdicts, then the queue.
diff --git a/skills/ideas/SKILL.md b/skills/ideas/SKILL.md
index dae9e9b..a4b54df 100644
--- a/skills/ideas/SKILL.md
+++ b/skills/ideas/SKILL.md
@@ -1,31 +1,11 @@
 ---
 name: ideas
-description: The ideamine queue. ls, cat, rm, and done run on your machine with no model call. go builds the next idea on the cheapest model that can do it, all does every idea that fits this chat, and sort triages the inbox.
-argument-hint: "ls [lane|-a|here] | cat N | rm N | go [N] | all | sort | done|start|reopen N | a question"
+description: Show the ideamine queue, or ask a question about your ideas. With no question, the ideamine hook shows the queue with no model call.
+argument-hint: "[a question]"
 disable-model-invocation: true
-allowed-tools: mcp__plugin_ideamine_ideamine__idea_list, mcp__plugin_ideamine_ideamine__idea_update, mcp__plugin_ideamine_ideamine__idea_next, mcp__plugin_ideamine_ideamine__idea_triage, mcp__plugin_ideamine_ideamine__idea_remove
+allowed-tools: mcp__plugin_ideamine_ideamine__idea_list, mcp__plugin_ideamine_ideamine__idea_update, mcp__plugin_ideamine_ideamine__idea_remove
 ---
 
 Request: $ARGUMENTS
 
-The first word of the request is the command. Do not ask the user anything. Do the command.
-
-The user saves ideas from any session. Thus the project that an idea records is only the folder the user was in, and that folder can be wrong or gone. Judge by the text of the idea which ideas fit this chat, and where each idea belongs.
-
-**go [N]**: build one idea with the model recommended for it.
-1. Call `idea_next` with `triage: true`. Pass `id` if N is given. The tool triages new ideas first. If it says that the queue is empty, say so in one line and stop. If the triage failed and no idea came back, call `idea_triage` with no verdicts, judge those ideas, save all verdicts in ONE `idea_triage` call with your model name as `by`, and then call `idea_next` again.
-2. If N is not given, the tool also returns the whole queue. If an idea in the queue clearly fits this chat (the current project or this conversation), call `idea_next` with its `id`, and build that idea. Else build the first idea.
-3. Choose the directory for the build. Use the current project if the idea fits it. Else use the project directory of the idea if it exists and fits the idea (the tool tells if it exists). Else find the project that the idea is about. If you cannot find it, say so in one line and stop.
-4. Tell the user the title, the recommended model, and the directory in one line. Then call `idea_update` with status "doing" and `project` set to that directory.
-5. Give the build to ONE subagent (Agent tool, general-purpose). Set its `model` to the recommended model (haiku, sonnet, opus, or fable). This sends each idea to the cheapest model that can do it. Set `run_in_background` to false. Step 6 must run in this turn, because the permission to use the ideamine tools ends with the turn. In the subagent prompt, include the brief, the original note, and the directory. Tell the subagent to work only in that directory, and to report what it changed and what is left to do.
-6. When the subagent returns, call `idea_update`. Use status "done" with a one-line note. If work remains, keep status "doing" and write a note that says what remains. Then summarize in 2-3 lines.
-
-**all**: do every idea that fits this chat.
-1. Call `idea_list` with `full: true`. It returns every idea in the queue, in full.
-2. Choose the ideas that fit this chat: the current project or this conversation. If no idea fits, say so in one line and stop.
-3. Call `idea_remove` with the `ids` of the ideas that fit. This takes them out of the queue. The other ideas stay in the queue for a chat that they fit.
-4. Show the user the ideas that you took, one line each, and how many stay in the queue. Then do the ideas that you took, one at a time, as the user's request.
-
-**sort**: call `idea_triage` with `headless: true`. If that call fails, call `idea_triage` with no verdicts, judge every idea, and save all verdicts in ONE `idea_triage` call with your model name as `by`. Show the result exactly as returned: the verdicts, then the queue.
-
-**Anything else** (ls, cat, rm, done, start, reopen, drop, or a question): answer from `idea_list`. Pass `filter`, `id`, `query`, or `here` when the request asks for them. Show a board exactly as returned. To change a status, use `idea_update`. To delete ideas, use `idea_remove`. Keep the answer short. Do not start to build an idea.
+Answer from `idea_list`. Pass `filter`, `id`, `query`, `here`, or `full` when the request asks for them. If the request is empty, show the board exactly as returned. To change a status, use `idea_update`. To delete ideas, use `idea_remove`. Keep the answer short. Do not start to build an idea: /ideas-go and /ideas-all do that.
diff --git a/src/claude.js b/src/claude.js
index b662934..bcf30ce 100644
--- a/src/claude.js
+++ b/src/claude.js
@@ -119,8 +119,8 @@ export async function headlessTriage({ model = process.env.IDEAMINE_TRIAGE_MODEL
 }
 
 /**
- * The triage that `go` needs before it picks: the idea `id` when it has no verdict, else the whole
- * inbox. Returns the headlessTriage result, or null when every candidate has a verdict already.
+ * The triage that /ideas-go needs before it picks: the idea `id` when it has no verdict, else the
+ * whole inbox. Returns the headlessTriage result, or null when every candidate has a verdict already.
  */
 export async function triageFirst({ id = null, model } = {}) {
   const db = load();
diff --git a/src/hook.js b/src/hook.js
index bd8e5d2..6fb1efc 100644
--- a/src/hook.js
+++ b/src/hook.js
@@ -1,26 +1,28 @@
-// UserPromptSubmit hook: answers /idea and the local /ideas commands (ls, cat, rm, done, ...) and
-// blocks the prompt, so the model is never called. That makes capture free, instant, and possible
-// even when the session is out of usage. Every other prompt passes through untouched, including
-// /ideas go, all, sort, and questions, which the /ideas skill answers.
+// UserPromptSubmit hook: answers /idea, /ideas, and the local /ideas-* commands (ls, cat, rm, done,
+// reopen) and blocks the prompt, so the model is never called. That makes capture free, instant,
+// and possible even when the session is out of usage. Every other prompt passes through untouched,
+// including /ideas-go, /ideas-all, /ideas-sort, and questions, which their skills answer.
 
 import { renderAdded, renderBoard, renderIdea } from './render.js';
 import { addIdeas, FILTERS, findIdea, lane, load, removeIdeas, updateIdea } from './store.js';
 import { clip, splitIdeas } from './text.js';
 
-// `/idea ...`, `/ideas ...`, and the plugin-qualified `/ideamine:idea ...` forms.
-const COMMAND = /^\s*\/(?:ideamine:)?(ideas?)(?=\s|$)([\s\S]*)$/i;
-const EDIT_VERBS = { done: 'done', finish: 'done', drop: 'dropped', doing: 'doing', start: 'doing', reopen: 'reopen' };
+// `/idea ...`, `/ideas ...`, `/ideas-<verb> ...`, and the plugin-qualified `/ideamine:...` forms.
+// Each verb is a separate skill with a dash, so that the slash menu shows it.
+const COMMAND = /^\s*\/(?:ideamine:)?(idea|ideas(?:-[a-z]+)?)(?=\s|$)([\s\S]*)$/i;
+const STATUS_COMMANDS = { 'ideas-done': 'done', 'ideas-reopen': 'reopen' };
 const ID = /^#?\d+$/;
 
-const USAGE = `Usage: /idea <text>                  add an idea (a bulleted list adds one idea per bullet)
-       /ideas [ls [lane|-a] [here]]   list the queue. Lanes: inbox do maybe skip doing done dropped
-       /ideas cat N...                show ideas in full
-       /ideas rm N...                 delete ideas for good
-       /ideas done|start|reopen|drop N [note]
+const USAGE = `Usage: /idea <text>                add an idea (a bulleted list adds one idea per bullet)
+       /ideas [question]            show the queue, or ask about your ideas
+       /ideas-ls [lane|-a] [here]   list. Lanes: inbox do maybe skip doing done dropped
+       /ideas-cat N...              show ideas in full
+       /ideas-rm N...               delete ideas for good
+       /ideas-done N [note] · /ideas-reopen N
 These call the model:
-       /ideas go [N]                  build the next idea, or #N, on its model. New ideas are triaged first.
-       /ideas all                     do every idea that fits this chat. The others stay in the queue.
-       /ideas sort                    triage the inbox now and show the queue`;
+       /ideas-go [N]                build the next idea, or #N, on its model. New ideas are triaged first.
+       /ideas-all                   do every idea that fits this chat. The others stay in the queue.
+       /ideas-sort                  triage the inbox now and show the queue`;
 
 const noIdea = (ids) => `No idea ${ids.map((id) => `#${String(id).replace(/^#/, '')}`).join(', ')}.`;
 
@@ -48,15 +50,14 @@ export function handlePrompt(prompt, { cwd = process.cwd(), session = null } = {
     return renderAdded(results, load());
   }
 
-  // /ideas <verb> [args]. The verb comes first, as in a shell, so "all" is never a view.
-  const [first = 'ls', ...rest] = arg.split(/\s+/).filter(Boolean);
-  const verb = first.toLowerCase();
-  const ids = ID.test(first) ? [first, ...rest] : rest;
-  const allIds = ids.length > 0 && ids.every((w) => ID.test(w));
+  const words = arg.split(/\s+/).filter(Boolean);
+  const ids = words.length > 0 && words.every((w) => ID.test(w)) ? words : null;
   const db = load();
 
-  if (verb === 'ls') {
-    const lower = rest.map((w) => (w === '-a' ? 'all' : w.toLowerCase()));
+  if (command === 'ideas' && !words.length) return renderBoard(db, { cwd, hints: true });
+
+  if (command === 'ideas-ls') {
+    const lower = words.map((w) => (w === '-a' ? 'all' : w.toLowerCase()));
     const filters = lower.filter((w) => FILTERS.includes(w));
     const here = lower.includes('here');
     if (filters.length + (here ? 1 : 0) === lower.length && filters.length <= 1) {
@@ -64,27 +65,22 @@ export function handlePrompt(prompt, { cwd = process.cwd(), session = null } = {
     }
   }
 
-  if ((verb === 'cat' || ID.test(first)) && allIds) {
-    const missing = ids.filter((id) => !findIdea(db, id));
-    return missing.length ? noIdea(missing) : ids.map((id) => renderIdea(findIdea(db, id))).join('\n\n');
-  }
-
-  if (verb === 'rm' && allIds) {
+  if ((command === 'ideas-cat' || command === 'ideas-rm') && ids) {
     const missing = ids.filter((id) => !findIdea(db, id));
     if (missing.length) return noIdea(missing);
+    if (command === 'ideas-cat') return ids.map((id) => renderIdea(findIdea(db, id))).join('\n\n');
     return removeIdeas(ids).map((i) => `✗ Removed #${i.id} · ${clip(i.title, 60)}`).join('\n');
   }
 
-  const status = EDIT_VERBS[verb];
-  if (status && rest[0] && ID.test(rest[0])) {
-    const note = rest.slice(1).join(' ');
-    const idea = updateIdea(rest[0], { status, note: note || undefined });
+  const status = STATUS_COMMANDS[command];
+  if (status && words[0] && ID.test(words[0])) {
+    if (!findIdea(db, words[0])) return noIdea([words[0]]);
+    const note = words.slice(1).join(' ');
+    const idea = updateIdea(words[0], { status, note: note || undefined });
     return `✓ #${idea.id} ${clip(idea.title, 60)} → ${lane(idea)}${note ? ' (note added)' : ''}`;
   }
 
-  if (verb === 'help') return USAGE;
-
-  return null; // go, all, sort, or a question: the /ideas skill answers it with the model
+  return null; // a question, /ideas-go, -all, -sort, or other words: the skill answers with the model
 }
 
 export async function runHook() {
diff --git a/src/mcp.js b/src/mcp.js
index 5a5a4ee..c93e969 100644
--- a/src/mcp.js
+++ b/src/mcp.js
@@ -149,8 +149,17 @@ const TOOLS = [
 // Slash commands for clients that load this server without the plugin (the plugin ships skills instead).
 // The prompt text is the matching SKILL.md body, so both stay in sync.
 const PROMPTS = [
-  { name: 'idea', skill: 'idea', description: 'Save an idea for later', arguments: [{ name: 'text', required: true }] },
-  { name: 'ideas', skill: 'ideas', description: 'The idea queue: ls, cat, rm, go, all, sort', arguments: [{ name: 'request', required: false }] },];
+  { name: 'idea', description: 'Save an idea for later', arguments: [{ name: 'text', required: true }] },
+  { name: 'ideas', description: 'Show the queue, or ask about your ideas', arguments: [{ name: 'question', required: false }] },
+  { name: 'ideas-ls', description: 'List the queue, one lane, or every lane (-a)', arguments: [{ name: 'lane', required: false }] },
+  { name: 'ideas-cat', description: 'Show ideas in full', arguments: [{ name: 'ids', required: true }] },
+  { name: 'ideas-rm', description: 'Delete ideas for good', arguments: [{ name: 'ids', required: true }] },
+  { name: 'ideas-done', description: 'Mark an idea done', arguments: [{ name: 'id', required: true }, { name: 'note', required: false }] },
+  { name: 'ideas-reopen', description: 'Put an idea back in the queue', arguments: [{ name: 'id', required: true }] },
+  { name: 'ideas-go', description: 'Build the next idea on its recommended model', arguments: [{ name: 'id', required: false }] },
+  { name: 'ideas-all', description: 'Do every idea that fits this chat', arguments: [] },
+  { name: 'ideas-sort', description: 'Triage the inbox now and show the queue', arguments: [] },
+];
 
 function skillBody(name, args) {
   const file = new URL(`skills/${name}/SKILL.md`, ROOT);
@@ -315,14 +324,14 @@ async function handleRequest(msg, { prompts }) {
       }
     }
     case 'prompts/list':
-      return result(id, { prompts: prompts ? PROMPTS.map(({ skill, ...p }) => p) : [] });
+      return result(id, { prompts: prompts ? PROMPTS : [] });
     case 'prompts/get': {
       const p = prompts && PROMPTS.find((x) => x.name === params.name);
       if (!p) return error(id, -32602, `unknown prompt: ${params.name}`);
       const args = Object.values(params.arguments || {}).filter(Boolean).join(' ');
       return result(id, {
         description: p.description,
-        messages: [{ role: 'user', content: { type: 'text', text: skillBody(p.skill, args) } }],
+        messages: [{ role: 'user', content: { type: 'text', text: skillBody(p.name, args) } }],
       });
     }
     case 'resources/list':
diff --git a/src/render.js b/src/render.js
index 23279d6..816a101 100644
--- a/src/render.js
+++ b/src/render.js
@@ -63,8 +63,8 @@ export function renderBoard(db, { filter = 'open', project = null, query = '', c
   }
   if (hints) {
     const tips = ['/idea <text> add'];
-    if (counts(db).open) tips.push('/ideas go build next', '/ideas all do all that fit here');
-    if (db.ideas.length) tips.push('/ideas cat N show', '/ideas rm N delete', '/ideas help');
+    if (counts(db).open) tips.push('/ideas-go build next', '/ideas-all do all that fit here');
+    if (db.ideas.length) tips.push('/ideas-cat N show', '/ideas-rm N delete');
     out.push('', tips.join(' · '));
   }
   return out.join('\n');
diff --git a/src/rubric.js b/src/rubric.js
index 1de0e18..d370a42 100644
--- a/src/rubric.js
+++ b/src/rubric.js
@@ -1,5 +1,5 @@
 // How ideas are judged and which model builds them. Both triage paths (the MCP tool used by
-// /ideas go and /ideas sort, and the headless `ideamine sort`) read this file.
+// /ideas-go and /ideas-sort, and the headless `ideamine sort`) read this file.
 
 import path from 'node:path';
 import { lane, listIdeas } from './store.js';
diff --git a/src/store.js b/src/store.js
index d863332..c8116bc 100644
--- a/src/store.js
+++ b/src/store.js
@@ -206,7 +206,7 @@ const READY_LANES = ['do', 'maybe'];
 
 /**
  * Best idea to build next: verdict "do" before "maybe", not started, current project first, then
- * value per effort. A "maybe" idea is still in the queue, so /ideas go builds it when no "do" is left.
+ * value per effort. A "maybe" idea is still in the queue, so /ideas-go builds it when no "do" is left.
  */
 export function pickNext(db, { project = null, only = false } = {}) {
   let ready = db.ideas.filter((i) => READY_LANES.includes(lane(i)));
diff --git a/tests/hook.test.js b/tests/hook.test.js
index df9690f..a97af7b 100644
--- a/tests/hook.test.js
+++ b/tests/hook.test.js
@@ -43,57 +43,59 @@ test('ordinary prompts, model commands, and questions pass straight through', ()
   const prompts = [
     'fix the tests',
     '/ideasx',
+    '/idea-go 3', // a name from before 0.3.0: no command now
     'an /idea in the middle',
-    '/ideas go',
-    '/ideamine:ideas go 3',
-    '/ideas all', // a command, never a view: a view is "ls all"
-    '/ideas sort',
-    '/ideas inbox', // the verb comes first, so a bare lane is a request for the model
-    '/ideas cat',
-    '/ideas ls ideas about scrolling',
-    '/ideas rm the vague one',
+    '/ideas-go',
+    '/ideamine:ideas-go 3',
+    '/ideas-all',
+    '/ideas-sort',
+    '/ideas-cat',
+    '/ideas-ls ideas about scrolling', // not lanes: the skill searches with these words
+    '/ideas-rm the vague one',
+    '/ideas all', // with a space it is a question for the model, never a command
     '/ideas what should I build today?',
   ];
   for (const p of prompts) assert.equal(runHook(p), null, p);
   assert.equal(store.load().ideas.length, 0);
 });
 
-test('/ideas lists the queue, shows ideas, and applies quick edits', () => {
+test('/ideas and the dashed commands list, show, and edit ideas', () => {
   store.addIdeas(['alpha', 'beta'], { project: '/work/app' });
   assert.match(runHook('/ideas').reason, /2 open[\s\S]*#2 {3}beta[\s\S]*#1 {3}alpha/);
-  assert.equal(runHook('/ideas ls').reason, runHook('/ideas').reason);
-  assert.match(runHook('/ideas #1').reason, /^#1 alpha/);
-  assert.match(runHook('/ideas cat 1 2').reason, /^#1 alpha\n[\s\S]*\n\n#2 beta\n/);
-  assert.match(runHook('/ideas done 1 shipped it').reason, /#1 alpha → done/);
-  assert.match(runHook('/ideas ls done').reason, /DONE[\s\S]*#1/);
-  assert.match(runHook('/ideas ls -a').reason, /\[showing: all\][\s\S]*#2[\s\S]*#1/);
-  assert.match(runHook('/ideas 9').reason, /No idea #9/);
-  assert.match(runHook('/ideas cat 1 8 9').reason, /^No idea #8, #9\.$/);
-  assert.match(runHook('/ideas help').reason, /^Usage[\s\S]*\/ideas rm N/);
-  assert.match(runHook('/idea').reason, /^Usage/);
+  assert.equal(runHook('/ideas-ls').reason, runHook('/ideas').reason);
+  assert.match(runHook('/ideas-cat #1').reason, /^#1 alpha/);
+  assert.match(runHook('/ideas-cat 1 2').reason, /^#1 alpha\n[\s\S]*\n\n#2 beta\n/);
+  assert.match(runHook('/ideas-done 1 shipped it').reason, /#1 alpha → done \(note added\)/);
+  assert.match(runHook('/ideas-ls done').reason, /DONE[\s\S]*#1/);
+  assert.match(runHook('/ideas-ls -a').reason, /\[showing: all\][\s\S]*#2[\s\S]*#1/);
+  assert.match(runHook('/ideamine:ideas-reopen 1').reason, /#1 alpha → inbox/);
+  assert.equal(runHook('/ideas-cat 9').reason, 'No idea #9.');
+  assert.equal(runHook('/ideas-cat 1 8 9').reason, 'No idea #8, #9.');
+  assert.equal(runHook('/ideas-done 9').reason, 'No idea #9.');
+  assert.match(runHook('/idea').reason, /^Usage[\s\S]*\/ideas-rm N/);
 });
 
-test('/ideas rm deletes ideas for good, without a model call', () => {
+test('/ideas-rm deletes ideas for good, without a model call', () => {
   store.addIdeas(['alpha', 'beta', 'gamma', 'delta']);
-  const out = runHook('/ideas rm 1 #3');
+  const out = runHook('/ideas-rm 1 #3');
   assert.equal(out.decision, 'block');
   assert.match(out.reason, /Removed #1 · alpha\n.*Removed #3 · gamma$/);
-  assert.equal(runHook('/ideas rm 2 9').reason, 'No idea #9.'); // an unknown id deletes nothing
-  assert.match(runHook('/ideamine:ideas rm 4').reason, /Removed #4 · delta/);
+  assert.equal(runHook('/ideas-rm 2 9').reason, 'No idea #9.'); // an unknown id deletes nothing
+  assert.match(runHook('/ideamine:ideas-rm 4').reason, /Removed #4 · delta/);
   assert.deepEqual(store.load().ideas.map((i) => i.text), ['beta']);
 });
 
-test('the board tells how to build, take, and remove ideas, and does not send you to a triage', () => {
+test('the board names the dashed commands, and does not send you to a triage', () => {
   store.addIdeas(['alpha']);
   const board = runHook('/ideas').reason;
-  for (const tip of [/\/ideas go/, /\/ideas all/, /\/ideas rm N/]) assert.match(board, tip);
-  assert.doesNotMatch(board, /\/idea-triage|\/ideas sort/);
+  for (const tip of [/\/ideas-go/, /\/ideas-all/, /\/ideas-rm N/]) assert.match(board, tip);
+  assert.doesNotMatch(board, /\/idea-triage|\/ideas-sort/);
 });
 
 test('handlePrompt filters by project with "here"', () => {
   store.addIdeas(['mine'], { project: '/a' });
   store.addIdeas(['theirs'], { project: '/b' });
-  const board = handlePrompt('/ideas ls here', { cwd: '/a' });
+  const board = handlePrompt('/ideas-ls here', { cwd: '/a' });
   assert.match(board, /mine/);
   assert.doesNotMatch(board, /theirs/);
 });
diff --git a/tests/mcp.test.js b/tests/mcp.test.js
index 147081d..abadfe8 100644
--- a/tests/mcp.test.js
+++ b/tests/mcp.test.js
@@ -107,9 +107,12 @@ test('tool errors come back as isError results, not protocol errors', async () =
 
 test('prompts mirror the skills', async () => {
   const list = await request('prompts/list');
-  assert.deepEqual(list.result.prompts.map((p) => p.name), ['idea', 'ideas']);
-  const ideas = await request('prompts/get', { name: 'ideas', arguments: { request: 'go 12' } });
-  assert.match(ideas.result.messages[0].content.text, /^Request: go 12\n[\s\S]*idea_next[\s\S]*idea_remove/);
+  const names = list.result.prompts.map((p) => p.name);
+  assert.deepEqual(names, ['idea', 'ideas', 'ideas-ls', 'ideas-cat', 'ideas-rm', 'ideas-done', 'ideas-reopen', 'ideas-go', 'ideas-all', 'ideas-sort']);
+  // One prompt for each skill, so that other MCP clients get the same commands as the plugin.
+  assert.deepEqual([...names].sort(), fs.readdirSync(new URL('../skills', import.meta.url)).sort());
+  const go = await request('prompts/get', { name: 'ideas-go', arguments: { id: '12' } });
+  assert.match(go.result.messages[0].content.text, /Requested idea: 12\b[\s\S]*idea_next[\s\S]*run_in_background/);
   const got = await request('prompts/get', { name: 'idea', arguments: { text: 'teleport the cat' } });
   const text = got.result.messages[0].content.text;
   assert.match(text, /teleport the cat/);