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

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

`/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

opened this

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

  • commands/forge.md: plan calls asks and takes the first row whose kind is proposal. If there is none, it says no wave waits. Otherwise it shows the wave in three lines: title, what it holds, and why now. For approve, redirect <words> or later, it asks the person to confirm. Only after a yes does it call asks_proposal with that row's id, the verb, and words for a redirect. The argument-hint and description are updated to match.
  • plugin.json and README.md: both name plan, and the README says how to use it.
  • test/plugins.test.ts: a new case checks that the command names asks and asks_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 covers plan. 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 plan against a running forge, so the "three lines" display is untested.

To try it: set FORGE_URL and FORGE_TOKEN, run claude --plugin-dir plugins/claude-code, then /forge plan.

Closes #5321

Changes requested — before the latest push

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.

commented

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: the plan line now says to pass id = the row's proposal.id and 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 the asks tool through MCP and finds the proposal row. It confirms the row's id is proposal:<id>. Passing that id to asks_proposal is an error, and passing proposal.id succeeds and moves the wave to later. 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's mcp helper for this.
  • test/plugins.test.ts: the plan case now also requires the command text to name proposal.id.

I kept asks_proposal strict instead of stripping the proposal: prefix in mine(), so the server is unchanged.

Approved

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:

  1. Make asks_proposal also accept the proposal: row id. A model reading the asks output will often reach for the row id first, so a tolerant tool removes that failure.
  2. Have /forge plan print the exact asks_proposal call it is about to make when it asks for confirmation. The person then sees which wave and which verb they are approving.
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs