/** * Role Commands Extension * * Thin wrappers over the `preset` extension for the canonical roles defined * in `packages/base/prompts/roles/` (and emitted into `packages/base/presets.json` * by `bin/build-roles`). Each command activates the matching preset and kicks * off a templated user message. * * /plan [topic] → preset planner + plan request * /spec [feature] → preset planner + spec request * /review [scope] → preset reviewer + review request * * `/spec` reuses the `planner` preset because spec'ing is read-only design * work — what diverges is the kickoff message. * * Implementation: each handler sends `/preset ` as a user message * (which pi dispatches to the preset extension's command, not to the LLM), * then sends the kickoff as a normal user message that triggers a turn. */ import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent"; type Wrapper = { preset: string; description: string; kickoff: (args: string) => string; }; const WRAPPERS: Record = { plan: { preset: "planner", description: "Switch to planner role and draft a plan [topic]", kickoff: (args) => args ? `Draft an implementation plan for: ${args}` : "Draft an implementation plan for the work we just discussed. If there is none, ask what to plan.", }, spec: { preset: "planner", description: "Switch to planner role and write a spec [feature]", kickoff: (args) => args ? `Write a short spec for: ${args}. Capture goal, non-goals, surface area, open questions, and acceptance proof.` : "Write a short spec for the feature under discussion. Capture goal, non-goals, surface area, open questions, and acceptance proof.", }, review: { preset: "reviewer", description: "Switch to reviewer role and review changes [scope]", kickoff: (args) => args ? `Review: ${args}. Follow the reviewer role's report structure.` : "Review the staged changes (fall back to the most recent commit if nothing is staged). Follow the reviewer role's report structure.", }, }; export default function roleCommands(pi: ExtensionAPI) { for (const [name, wrapper] of Object.entries(WRAPPERS)) { pi.registerCommand(name, { description: wrapper.description, handler: async (args: string | undefined, _ctx: ExtensionCommandContext) => { const kickoff = wrapper.kickoff((args ?? "").trim()); // First message is a slash command, dispatched synchronously by pi. pi.sendUserMessage(`/preset ${wrapper.preset}`); // Second message is the actual prompt; this one triggers the LLM turn. pi.sendUserMessage(kickoff); }, }); } }