commit 0111afc6efcb95887e47a2dca6fedac0371c75e2 equwal <13551856+equwal@users.noreply.github.com> 2026-09-21 22:47:22 -0700 ideamine 0.8.0: build an idea through the agent pipeline /ideas-pipeline [id | text] sends one idea through the seven agents: research, storyboard, plan, parallel engineers in git worktrees, integration, and adversarial test and validation. The user approves the storyboard and the plan. After each phase and each gate, Claude adds a note to the idea, so the dashboard shows the phase. The idea is done only when the tester and the validator pass. The orchestration text is the pipeline skill of map588/agents with three changes: the plugin names of the agents, the target project of the idea, and the note after each phase. /ideas-go does not change.
.claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 2 +- README.md | 32 +++++++++- package-lock.json | 4 +- package.json | 3 +- skills/ideas-pipeline/SKILL.md | 136 ++++++++++++++++++++++++++++++++++++++++ src/hook.js | 1 + src/mcp.js | 1 + tests/hook.test.js | 5 +- tests/mcp.test.js | 4 +- tests/pipeline.test.js | 38 +++++++++++ 11 files changed, 220 insertions(+), 8 deletions(-)
diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index fd622de..4ddd6cc 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.7.0", + "version": "0.8.0", "author": { "name": "equwal" }, diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 44b2ad8..798f583 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "ideamine", - "version": "0.7.0", + "version": "0.8.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 722861e..2595e31 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ Marketplaces you add yourself do not auto-update. To upgrade, run `claude plugin | `/ideas-web` · `off` | Start the dashboard with a button for each command on this PC, and show its address. See [Buttons](#buttons). | **No** | | `/ideas-sync <url>` · `off` | Share one archive between all your machines through an ideamine server. See [One archive for every machine](#one-archive-for-every-machine). | **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-pipeline [12]` | Build the next idea, #12, or new text through seven agents, with two approval gates. See [Agent pipeline](#agent-pipeline). | 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-watch [off]` | Turn on the watcher: Haiku triages each new idea and pairs it with its project, in the background. With no argument, it also shows what the watcher did. | Haiku, only for new ideas | @@ -60,6 +61,34 @@ You save ideas from any session, so the folder that ideamine records is only the 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. +## Agent pipeline + +``` +> /ideas-pipeline 12 +``` + +`/ideas-pipeline` sends one idea through a pipeline of seven agents. Give it an idea number, or new text: then ideamine saves the text as an idea first. With no argument, it takes the next idea in the queue. The pipeline is slower than `/ideas-go`, it costs more, and it asks you to approve two times. Use it for the ideas where a wrong build costs more than the tokens. + +``` +researcher ──► story-writer ──► GATE 1: you approve the stories ──► project-manager ──► GATE 2: you approve the plan + ──► engineers (in parallel, one git worktree each) ──► integrator ──► e2e-tester + validator + ──► pass: done · fail: the project manager plans again (3 rounds at most), then Claude asks you +``` + +| Agent | Model | Job | +|---|---|---| +| `researcher` | Opus | Maps the project from evidence | +| `story-writer` | Sonnet | Turns the idea into stories with acceptance criteria | +| `project-manager` | your session's model | Checks that the stories are possible, and splits the work into tasks and waves | +| `engineer` | Opus | Builds one task in its own git worktree, and proves each claim with command output | +| `integrator` | Opus | Merges the task branches, and proves that the build and the tests pass | +| `e2e-tester` | Opus | Runs the product to check each claim of the engineers | +| `validator` | Opus | Compares the result with the stories, and finds work that no story asked for | + +The agents keep their own models. The pipeline does not use the recommended model of the idea. The artifacts are in `.pipeline/` in the project, and the pipeline adds that folder to `.git/info/exclude`. After each phase, Claude adds a note to the idea, for example `pipeline: plan approved (3 tasks, 2 waves)`, so the ticket on the dashboard shows the phase. When the tests and the validation pass, the idea is done. When the third round fails, the idea stays in `doing`, and the note tells what failed. Claude does not reset your branch without your permission. Each gate ends a turn, so after a gate Claude Code can ask you to allow the ideamine tools. + +The pipeline and its agents come from agent-pipeline by [map588](https://github.com/map588). + ## The watcher ``` @@ -289,6 +318,7 @@ Tools: `idea_add`, `idea_list`, `idea_triage`, `idea_update`, `idea_next`, `idea /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-pipeline ──► Claude ──► MCP idea_next ──► seven agents, two gates ──► idea_update: a note after each phase, then done /ideas-all ──► Claude ──► MCP idea_list (full) ──► idea_remove for the ideas that fit this chat ──► builds them watcher on: any prompt ──► hook ──► background pass ──► claude -p (Haiku) ──► verdicts + a project for each idea @@ -302,7 +332,7 @@ sync on: each change ──► outbox ──► ideamine server ──► the ar prompt log on: any prompt ──► hook ──► prompt outbox ──► background sync ──► the Prompts tab ``` -The plugin contains a Node MCP server with no dependencies, fifteen skills (the slash commands), and one hook. The hook answers `/idea`, `/ideas`, and the local `/ideas-*` commands before any API call, and it lets every other prompt through. 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, sixteen skills (the slash commands), one hook, and the seven agents of the agent pipeline. The hook answers `/idea`, `/ideas`, and the local `/ideas-*` commands before any API call, and it lets every other prompt through. 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-lock.json b/package-lock.json index f7af566..8e06bb7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ideamine", - "version": "0.7.0", + "version": "0.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ideamine", - "version": "0.7.0", + "version": "0.8.0", "license": "MIT", "bin": { "ideamine": "bin/ideamine.js" diff --git a/package.json b/package.json index 293fa32..1a7dfa5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ideamine", - "version": "0.7.0", + "version": "0.8.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": { @@ -11,6 +11,7 @@ "src", "dashboard", "skills", + "agents", "hooks", ".claude-plugin", ".mcp.json", diff --git a/skills/ideas-pipeline/SKILL.md b/skills/ideas-pipeline/SKILL.md new file mode 100644 index 0000000..06a1581 --- /dev/null +++ b/skills/ideas-pipeline/SKILL.md @@ -0,0 +1,136 @@ +--- +name: ideas-pipeline +description: Build an ideamine idea through the agent pipeline. Seven agents research the project, write a storyboard, plan tasks, build them in parallel git worktrees, merge them, and test and validate the result. The user approves the storyboard and the plan. Each phase adds a note to the idea. +argument-hint: "[id | request]" +disable-model-invocation: true +allowed-tools: mcp__plugin_ideamine_ideamine__idea_next, mcp__plugin_ideamine_ideamine__idea_update, mcp__plugin_ideamine_ideamine__idea_add, mcp__plugin_ideamine_ideamine__idea_triage +--- + +Build one idea through the agent pipeline. Requested: $ARGUMENTS. Empty means the next idea in the queue. A number is the id of an idea. Other text is a new request. + +## The idea + +1. If the request is text and not a number, save it with `idea_add` (the text verbatim), and use the id of the new idea. +2. Call `idea_next` with `triage: true`, and pass `id` if you have one. 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. +3. Choose the target project. 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 and the target project in one line. Then call `idea_update` with status "doing" and `project` set to the target project. +5. The request for the pipeline is the brief of the idea, then its original note. +6. The idea is the record of this run. After each phase and each gate, add one note to it with `idea_update` (`id`, `note`). Use these notes, so that the dashboard shows the phase on the ticket: `pipeline: research done`, `pipeline: storyboard approved`, `pipeline: plan approved (<tasks> tasks, <waves> waves)`, `pipeline: wave <n> integrated`, `pipeline: round <n> failed: <one line>`, `pipeline: tests and validation pass`. +7. When both reports pass, call `idea_update` with status "done" and a one-line note that names the report paths. When the cap is reached or you escalate, keep the status "doing", and add a note that says what failed and where the reports are. Never mark the idea done on a failure. + +# Agent Pipeline Orchestration + +You (the main session) are the orchestrator. The project-manager agent plans but cannot +spawn agents — you dispatch every agent, pass artifacts between phases by file path, and +enforce the iteration cap. Do not do the phases' work yourself; delegate each to its agent. + +The agents are `ideamine:researcher`, `ideamine:story-writer`, `ideamine:project-manager`, +`ideamine:engineer`, `ideamine:integrator`, `ideamine:e2e-tester`, and `ideamine:validator`. +Use these names as the `subagent_type` when you spawn them. + +## Setup + +1. The target project is the one from step 3 above. Run each command, and each git + command, in the target project. +2. Create `.pipeline/` and `.pipeline/reports/` in the target project. If the project is + a git repo and `.pipeline/` is not ignored, add it to `.git/info/exclude`. +3. Constants for this run: `MAX_ROUNDS = 3`. Artifacts: + - `.pipeline/research.md`, `.pipeline/storyboard.md`, `.pipeline/plan.md` + - `.pipeline/reports/engineer-<task>.md`, `.pipeline/reports/integration-<round>.md`, + `.pipeline/test-report.md`, `.pipeline/validation-report.md` +4. Parallel engineering uses git worktrees, which require a git repo. If the target is + not one, ask the user whether to `git init`; if declined, engineers run sequentially + in place, there is no integrator step, and checkpoints/rollback are unavailable. + +## Phase 1 — Research + +Spawn `researcher` (run_in_background: false) with: project root, the user's request as +focus hint, and the research.md output path. On return, skim the report's Open Questions — +if any block understanding the user's request, ask the user now, before storyboarding. + +## Phase 2 — Storyboard + +Spawn `story-writer` with: mode=write, the user's request verbatim, research.md path, +storyboard.md output path. + +**Gate 1:** present the storyboard's Intent section (including every chosen +interpretation) and a one-line-per-story list to the user, and get explicit approval +before Phase 3. If the user corrects an interpretation, send the story-writer back +(SendMessage) with the correction and re-run this gate. + +## Phase 3 — Plan + +Spawn `project-manager` with: mode=plan, both artifact paths, plan.md output path, +iteration 1 of MAX_ROUNDS. On return, read plan.md yourself and check: + +- Escalations section empty? If not, resolve with the user before continuing. +- Do same-wave tasks declare heavy scope overlap (same core files)? Send the PM back + once to merge those tasks or sequence them with a dependency — light overlap is fine, + the integrator resolves it. + +**Gate 2:** present the plan summary (tasks, file scopes, waves, escalations) and get +explicit approval. This is the last checkpoint before any code changes. + +## Phase 4 — Engineering (worktrees) + +Round setup (git repos only): require a clean working tree — if dirty, ask the user +(commit / stash / proceed without checkpoints). On round 1, record +`BASELINE_SHA = git rev-parse HEAD` and keep it for the whole run. + +For each wave in order: + +1. Per task: `git worktree add .pipeline/worktrees/<task> -b pipeline/<task>` (off + current HEAD). +2. Spawn one `engineer` per task — all in a single message so they run in parallel. + Each gets: task_id, its worktree path and branch, plan.md and research.md paths, and + its report path (in the main checkout's `.pipeline/reports/`). +3. Wait for the wave. If an engineer reports `blocked`, do not improvise a fix — + collect it for the replan, and skip any later-wave task that depends on it + (collect those too). +4. Spawn `integrator` with: the wave's branch list (plan order), the working branch + name, plan.md path, engineer report paths, and the integration report path. It + merges each branch into the working branch, resolves conflicts, repairs cross-task + API breaks, proves build/tests green, and removes merged worktrees and branches. +5. If the integrator escalates (conflict or API break needing redesign), stop + dispatching waves and go to Phase 6 with its report among the failures. +6. The next wave branches off the integrated HEAD. + +Non-git fallback: run the wave's engineers sequentially in the project directory and +skip the integrator. + +## Phase 5 — Test + Validate (parallel) + +Spawn both in a single message: + +- `e2e-tester`: plan, storyboard, engineer + integration report paths, test-report.md + output path. +- `validator`: storyboard, research, plan, engineer report paths, BASELINE_SHA, + validation-report.md output path. + +## Phase 6 — Verdict loop + +Read both reports (plus the integration report if it escalated). + +- **Both pass:** report to the user: what was built (from validator's Intent Coverage), + test verdict, artifact locations, checkpoint commits. Done. +- **Any failure and rounds remain:** spawn `project-manager` with mode=replan, the + failure reports, and iteration N+1. Its Failure Classification section routes the work: + - **Spec defects:** spawn `story-writer` with mode=revise (prior storyboard + failure + report paths), re-run Gate 1 for the changed stories, then have the PM plan against + the revised storyboard. Spec detours share MAX_ROUNDS — no extra counter. + - **Code defects:** repeat Phases 4–5 for the new plan (failed work only). +- **Failures at MAX_ROUNDS, or PM escalates:** stop. Present the user: what passed, what + failed (with the tester's reproductions), the PM's escalations, and options — including + rollback to a checkpoint (`git reset --hard`), which requires the user's explicit + consent and is never automatic. + +## Rules + +- Every agent gets file paths, not pasted content — artifacts are the shared memory. +- Never skip a phase because it "seems unnecessary"; the user opted into the pipeline. +- No code changes before Gate 2 approval. +- Git ownership: engineers commit only on their own `pipeline/<task>` branch; the + integrator owns merges into the working branch; you make no commits beyond what this + skill describes. Plain commit messages, no co-author lines. +- Between phases, give the user a one-line status (phase done, headline finding), and + add the phase note to the idea (step 6 above). diff --git a/src/hook.js b/src/hook.js index cc0cfe6..4fbd622 100644 --- a/src/hook.js +++ b/src/hook.js @@ -32,6 +32,7 @@ const USAGE = `Usage: /idea <text> add an idea (a bulleted list a /ideas-sync [url|off] share the archive of every machine through an ideamine server These call the model: /ideas-go [N] build the next idea, or #N, on its model. New ideas are triaged first. + /ideas-pipeline [N|text] build an idea through the agent pipeline. You approve the storyboard and the plan. /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-watch [off] Haiku triages new ideas and pairs them with projects, in the background`; diff --git a/src/mcp.js b/src/mcp.js index 2941830..2401ac3 100644 --- a/src/mcp.js +++ b/src/mcp.js @@ -154,6 +154,7 @@ const PROMPTS = [ { 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-pipeline', description: 'Build an idea through the agent pipeline, with two approval gates', 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: [] }, { name: 'ideas-watch', description: 'Turn the background watcher on or off', arguments: [{ name: 'off', required: false }] }, diff --git a/tests/hook.test.js b/tests/hook.test.js index cfd5b4c..2ae9cce 100644 --- a/tests/hook.test.js +++ b/tests/hook.test.js @@ -53,6 +53,9 @@ test('ordinary prompts, model commands, and questions pass straight through', () 'an /idea in the middle', '/ideas-go', '/ideamine:ideas-go 3', + '/ideas-pipeline', + '/ideamine:ideas-pipeline 3', + '/ideas-pipeline dark mode for the popup', '/ideas-all', '/ideas-sort', '/ideas-cat', @@ -78,7 +81,7 @@ test('/ideas and the dashed commands list, show, and edit ideas', () => { 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/); + assert.match(runHook('/idea').reason, /^Usage[\s\S]*\/ideas-rm N[\s\S]*\/ideas-pipeline/); }); test('/ideas-rm deletes ideas for good, without a model call', () => { diff --git a/tests/mcp.test.js b/tests/mcp.test.js index 216423e..a70ede9 100644 --- a/tests/mcp.test.js +++ b/tests/mcp.test.js @@ -109,11 +109,13 @@ test('tool errors come back as isError results, not protocol errors', async () = test('prompts mirror the skills', async () => { const list = await request('prompts/list'); const names = list.result.prompts.map((p) => p.name); - assert.deepEqual(names, ['idea', 'ideas', 'ideas-ls', 'ideas-cat', 'ideas-rm', 'ideas-find', 'ideas-groups', 'ideas-done', 'ideas-reopen', 'ideas-go', 'ideas-all', 'ideas-sort', 'ideas-watch', 'ideas-web', 'ideas-sync']); + assert.deepEqual(names, ['idea', 'ideas', 'ideas-ls', 'ideas-cat', 'ideas-rm', 'ideas-find', 'ideas-groups', 'ideas-done', 'ideas-reopen', 'ideas-go', 'ideas-pipeline', 'ideas-all', 'ideas-sort', 'ideas-watch', 'ideas-web', 'ideas-sync']); // 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 pipeline = await request('prompts/get', { name: 'ideas-pipeline', arguments: { id: 'dark mode for the popup' } }); + assert.match(pipeline.result.messages[0].content.text, /^Build one idea through the agent pipeline\. Requested: dark mode for the popup\.[\s\S]*idea_add[\s\S]*Gate 2/); 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/); diff --git a/tests/pipeline.test.js b/tests/pipeline.test.js new file mode 100644 index 0000000..462f632 --- /dev/null +++ b/tests/pipeline.test.js @@ -0,0 +1,38 @@ +// Checks that /ideas-pipeline and the seven pipeline agents fit together. The pipeline itself runs in a +// Claude Code session, so these tests do not run it. +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import test from 'node:test'; + +const read = (file) => fs.readFileSync(new URL(`../${file}`, import.meta.url), 'utf8'); +const plugin = JSON.parse(read('.claude-plugin/plugin.json')).name; +const skill = read('skills/ideas-pipeline/SKILL.md'); +const agentFiles = fs.readdirSync(new URL('../agents', import.meta.url)).filter((f) => f.endsWith('.md')); +const frontmatter = (text) => + Object.fromEntries([...text.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/)[1].matchAll(/^([\w-]+): (.*?)\r?$/gm)].map((m) => [m[1], m[2]])); + +test('the pipeline skill names each agent of the plugin, and each name is an agent', () => { + const named = new Set([...skill.matchAll(new RegExp(`\`${plugin}:([\\w-]+)\``, 'g'))].map((m) => m[1])); + const agents = agentFiles.map((f) => frontmatter(read(`agents/${f}`)).name); + assert.equal(agents.length, 7); + assert.deepEqual([...named].sort(), agents.sort()); +}); + +test('each agent has a name that matches its file, a description, and a model', () => { + for (const f of agentFiles) { + const fm = frontmatter(read(`agents/${f}`)); + assert.equal(fm.name, f.replace(/\.md$/, ''), f); + assert.ok(fm.description && fm.model, f); + } +}); + +test('the pipeline skill may call each ideamine tool that it tells Claude to call', () => { + const allowed = skill.match(/^allowed-tools: (.*?)\r?$/m)[1].split(/,\s*/); + const told = new Set([...skill.matchAll(/`(idea_[a-z]+)`/g)].map((m) => m[1])); + assert.ok(told.size >= 3); + for (const tool of told) assert.ok(allowed.includes(`mcp__plugin_${plugin}_${plugin}__${tool}`), tool); +}); + +test('the package ships the agents', () => { + assert.ok(JSON.parse(read('package.json')).files.includes('agents')); +});