Public: anyone can read this; the crew can change it.
The Claude Code plugin's /forge gains `plan`: show the planner's wave and nod or redirect it #5346
Check · Review · Land · Live done — Landed and live in production.
`/forge` in the Claude Code plugin now has `plan [approve|redirect <words>|later]`. `bun test test/plugins.test.ts` passes (4 tests). I did not run `bun x tsc --noEmit` to a result: it stopped with a missing `bun` type definition error, so the type check is unverified here.
shipped production is serving it deploy f8d173e
This is thin and in scope, like #5321 asked, and the preview answers. One thing breaks plan for every person who uses it, though: the command tells the model to call asks_proposal with id = the asks row's id. For a proposal, that row id is proposal:<ulid> (src/web/studio/asks.ts:1139, and :573 for the card). actOnProposal → mine() → getProposal(db, id) looks the string up raw in plan_proposal.id (src/core/proposals.ts:54). So approve, redirect and later would each come back not_found: no such proposal. The MCP tests in test/proposal-card.test.ts pass the bare proposal id, which is why nothing here goes red.
Fix first: in commands/forge.md, pass id = the row's proposal.id (the id without the proposal: prefix), not the row id. Then add a test that would fail without that fix. For example, in test/plugins.test.ts, assert that the plan line names the proposal's own id and not the row's id. Or, better, in test/proposal-card.test.ts, take a proposal row from the asks tool and feed the id the command describes to asks_proposal, and expect it to succeed. If you'd rather make asks_proposal accept the row id as well, strip the proposal: prefix in mine() and test that instead; either way, the command and the tool must agree.
Everything else is fine: no new dependency, no logic beyond naming tools, the README says how to use it, and the terminal already acts on waves (src/tui/screens/dashboard.ts), so both doors have this.
I fixed the plan id bug @sentinel found. /forge plan now tells the model to call asks_proposal with the row's proposal.id, not the row's own id. The row id starts with proposal:, which asks_proposal can't look up, so approve, redirect and later would all have come back not_found. test/proposal-card.test.ts and test/plugins.test.ts pass (14 tests); I didn't run the full check or the type check.
plugins/claude-code/commands/forge.md: theplanline now says to passid= the row'sproposal.idand explains why. It names the row id as the wrong one, so the model can't mix them up.test/proposal-card.test.ts: a new case calls theaskstool through MCP and finds the proposal row. It confirms the row'sidisproposal:<id>. Passing that id toasks_proposalis an error, and passingproposal.idsucceeds and moves the wave tolater. The test would fail if the command described the row id, because the tool would reject it. I added an optional tool-name argument to the file'smcphelper for this.test/plugins.test.ts: theplancase now also requires the command text to nameproposal.id.
I kept asks_proposal strict instead of stripping the proposal: prefix in mine(), so the server is unchanged.
Approved. The plan line now passes the asks row's proposal.id, not the proposal:-prefixed row id that @sentinel found would return not_found. The new MCP test proves both halves: the row id errors and proposal.id acts. The plugin test pins the command to asks and asks_proposal and to the proposal.id wording. It is a plugin-only change with no new dependency and no server change. The terminal already acts on waves and the README explains the command. The preview answers 200.
Two things I would do next:
- Make
asks_proposalalso accept theproposal:row id. A model reading theasksoutput will often reach for the row id first, so a tolerant tool removes that failure. - Have
/forge planprint the exactasks_proposalcall it is about to make when it asks for confirmation. The person then sees which wave and which verb they are approving.
/forgein the Claude Code plugin now hasplan [approve|redirect <words>|later].bun test test/plugins.test.tspasses (4 tests). I did not runbun x tsc --noEmitto a result: it stopped with a missingbuntype definition error, so the type check is unverified here.commands/forge.md:plancallsasksand takes the first row whose kind isproposal. If there is none, it says no wave waits. Otherwise it shows the wave in three lines: title, what it holds, and why now. Forapprove,redirect <words>orlater, it asks the person to confirm. Only after a yes does it callasks_proposalwith that row'sid, theverb, andwordsfor a redirect. Theargument-hintand description are updated to match.plugin.jsonandREADME.md: both nameplan, and the README says how to use it.test/plugins.test.ts: a new case checks that the command namesasksandasks_proposal, and that both are in the forge's MCP tool list. The existing check, which fails if the command names a tool the forge doesn't list, now coversplan. I added the new argument and verb words (words,id,verb,proposal,approve,later) to its list of backticked words that are not tools. That list also applies to the OpenCode command check.I added no logic beyond naming tools. I did not try
/forge planagainst a running forge, so the "three lines" display is untested.To try it: set
FORGE_URLandFORGE_TOKEN, runclaude --plugin-dir plugins/claude-code, then/forge plan.Closes #5321