Refactor build-proposal so its decision-complete output is a saved, copy-pasteable prompt under docs/prompts/, authored by delegating to write-prompt, while dual-writing the legacy docs/proposals/ document for one deprecation release. Ship the whole change as plan-agent 6.0.0.
build-proposal currently ends by hand-writing a one-line handoff string, and the skill that exists to author prompts properly cannot be called at all. This wires the two together so a proposal converges on a real saved prompt, and we will know it worked when a single pipeline test confirms the command wrapper, the fifth prompt type, the dual-write, and the gallery chip all line up.
Read and implement all steps in the plan at docs/plans/refactor-build-proposal-to-emit-prompt.md — Make build-proposal converge on a saved prompt authored by write-prompt. Verify against the plan's Tests, Verification, and Acceptance Criteria before reporting done. If everything passed, mark completion in docs/plans/refactor-build-proposal-to-emit-prompt.md — tick each step's [x] marker and each criterion's - [x], set status: completed — and re-render the HTML from the spec. If any check failed, leave status: in-progress and say which.
More ways to run this plan — goal & workflow prompts, file path
Achieve this goal: Make build-proposal converge on a saved prompt authored by write-prompt. The plan at docs/plans/refactor-build-proposal-to-emit-prompt.md describes one approach — use it as reference, but optimize for the outcome. Fan out across parallel subagents where that serves the outcome. Verify against the plan's Tests, Verification, and Acceptance Criteria before reporting done. If everything passed, mark completion in docs/plans/refactor-build-proposal-to-emit-prompt.md — tick each step's [x] marker and each criterion's - [x], set status: completed — and re-render the HTML from the spec. If any check failed, leave status: in-progress and say which.
Run a workflow to implement the plan at docs/plans/refactor-build-proposal-to-emit-prompt.md — Make build-proposal converge on a saved prompt authored by write-prompt. Brief subagents with the plan file at docs/plans/refactor-build-proposal-to-emit-prompt.md. Reserve a final verification phase for the lead agent, not a subagent. Verify against the plan's Tests, Verification, and Acceptance Criteria before reporting done. If everything passed, mark completion in docs/plans/refactor-build-proposal-to-emit-prompt.md — tick each step's [x] marker and each criterion's - [x], set status: completed — and re-render the HTML from the spec. If any check failed, leave status: in-progress and say which.
refactor-build-proposal-to-emit-prompt.html
docs/plans/refactor-build-proposal-to-emit-prompt.html
docs/plans/refactor-build-proposal-to-emit-prompt.md
Context
The story behind this plan — what prompted the work and why it matters now.
build-proposal already ends by emitting a prompt — a hand-built one-line invocation string for implementation-plan. The skill spends an entire paragraph (build-proposal/SKILL.md:207-214), duplicated in references/operating-principles.md:49 and build/SKILL.md:185-188, warning that getting that string's grammar wrong drops implementation-plan into conversion mode and yields a plan whose steps restate proposal headings. That is prompt authoring being done by hand, in prose, in triplicate — and write-prompt is the skill that exists to do it properly.
The blocker found during proposal research is mechanical, not conceptual. disable-model-invocation: true blocks programmatic Skill invocation, not just ambient auto-activation. Four plan-agent skills carry the flag; the two with thin commands/*.md wrappers (deep-grill, documenting-plans) are invocable and appear in the session skill registry, and the two without (write-prompt, finalize-plan) are not. The wrapper is the fix, and it is 15 lines.
Four decisions were locked in the 2026-07-27 proposal review and are not reopened here: keep disable-model-invocation and add the wrapper; make the saved prompt file the living document; add a fifth proposal prompt type rather than compressing into task; dual-write both artifacts for 6.0.0 with the prompt authoritative. Four follow-on decisions were settled while drafting this plan: status: uses the proposal-native vocabulary gathering/converged; --dir follows the authoritative artifact and now names the prompts directory; appendices map to a single catch-all {{APPENDICES}} slot; and this plan covers the full 6.0.0 release rather than the de-risking spike alone. Four more came out of the plan interview: Tier 0 keeps writing nothing while Tier 1 emits a short-subset prompt; the prompt path is derived rather than read back; an in-place rewrite diffs for hand edits before overwriting; and long proposal bodies rely on the gallery's existing <details> collapse rather than type-specific CSS. Code review then caught a defect in the derivation itself: including the date would break the living document across a calendar boundary, so the filename is proposal-{slug}.md with no date.
Known risk carried into implementation: Skill() has no documented return value. The interview resolved this by deriving the prompt path deterministically from the slug instead of parsing anything out of the transcript, which removes the dependency rather than working around it; the date is deliberately excluded from the filename so a multi-day loop keeps resolving to the same file. What remains unverified is whether Skill() can reach write-prompt at all once the wrapper lands, whether three-level nesting works, and whether the claude-fable-5 to opus pin boundary changes behavior. Step 2 exists to prove those empirically before anything is built on them, and is a hard stop if they fail.
See docs/proposals/replace-proposal-doc-with-prompt.md for the full decision record, risk register, and the section-to-slot mapping table.
Files that change
Every file this plan touches, and what happens to each one.
kit/plugins/plan-agent/commands/write-prompt.mdnew thin Skill wrapper that unblocks programmatic invocationkit/plugins/plan-agent/skills/write-prompt/references/proposal-prompt-template.mdnew fifth template with proposal-shaped slotskit/plugins/plan-agent/skills/write-prompt/SKILL.mdmodified fifth type across Phases 1, 2, 3, 4, 7kit/plugins/plan-agent/skills/build-proposal/SKILL.mdmodified dual-write in Step 6, prompt handoff in Step 8, --dir retargetkit/plugins/plan-agent/skills/build-proposal/references/artifact-shape.mdmodified slot mapping and the bare-.md handoff fix at line 102kit/plugins/plan-agent/skills/build/SKILL.mdmodified Step 1b prompt path, dirty-tree exclusion, abandonment contractkit/plugins/artifact-tools/skills/prompt-artifact/SKILL.mdmodified fifth filter chip and tolerance for status/modified frontmatter- tests/plugins/
test-proposal-prompt-pipeline.shnew objective-verification test for the whole wiringtest-write-prompt-proposal-type.shnew unit coverage for the fifth typetest-build-proposal.shmodified checks 10, 11, 14, 15 rewritten to the dual-write contracttest-build-skill.shmodified Step 1b assertions updated; runs in CI
kit/plugins/plan-agent/README.mdmodified build-proposal and write-prompt sections, Plugin Structure treekit/plugins/artifact-tools/README.mdmodified prompt-artifact type listkit/plugins/plan-agent/CHANGELOG.mdmodified 6.0.0 entrykit/plugins/artifact-tools/CHANGELOG.mdmodified fifth-chip entry.claude-plugin/marketplace.jsonmodified plan-agent 5.0.0 to 6.0.0, artifact-tools minor bumpCLAUDE.mdmodified plan-agent and artifact-tools table rowsREADME.mdmodified build-proposal artifact path
Steps
The step-by-step work, in order — each step says what to do, why it matters, and how to check it worked.
kit/plugins/plan-agent/commands/write-prompt.md mirroring commands/deep-grill.md exactly — frontmatter with description, allowed-tools: Skill, and argument-hint, a two-sentence body, and one fenced Skill(skill: "plan-agent:write-prompt", args: "$ARGUMENTS") call.
disable-model-invocation: true blocks programmatic invocation, and the wrapper is the established in-repo workaround that keeps ambient triggering on the word "prompt" suppressed.grep -qF 'Skill(skill: "plan-agent:write-prompt"' kit/plugins/plan-agent/commands/write-prompt.md exits 0, and write-prompt/SKILL.md:5 still carries disable-model-invocation: true./plan-agent:write-prompt, confirm it runs and appears in the session skill registry, then confirm a nested build to build-proposal to write-prompt call completes and note whether the claude-fable-5 caller invoking an opus callee changes behavior.
Skill() return value, three-level nesting, and cross-model-pin behavior) are all unverified, and every later step assumes they hold.write-prompt is invocable, a prompt file is written to docs/prompts/, and a three-level nested invocation completes without a depth or permission error.kit/plugins/plan-agent/skills/write-prompt/references/proposal-prompt-template.md following the shape of the existing four templates — a ## Template fenced block, a ## Placeholder Guide table, and a ## Assembled Example — carrying {{TLDR}}, {{CONTEXT}}, {{CORE_FINDING}}, {{COMPARISON_TABLE}}, {{LOCKED_DECISIONS}}, {{WORKSTREAMS}}, {{RISKS}}, {{OPEN_QUESTIONS}}, {{ROADMAP}}, {{APPENDICES}}, and {{CORE_INSTRUCTION}}.
task template's 10 slots loses appendices, roadmap phasing, and risk tables, and the proposal's Next-step section maps exactly onto a prompt's core instruction.{{ tokens.proposal type through kit/plugins/plan-agent/skills/write-prompt/SKILL.md — add the fifth row to the Phase 1 type table and technique matrix, the Phase 3 XML layer mapping, the Phase 4 template-selection entry, and extend Phase 7 with three things: a caller-supplied output path contract (--out <path>) that, when present, overrides Phase 7's own directory resolution and its 3-5-word intent slug entirely; status: (gathering or converged), modified:, and generated-sha: frontmatter keys; and an in-place rewrite rule replacing the -2/-3 uniqueness guard for this type, gated by comparing the current body's hash against the recorded generated-sha: and asking via AskUserQuestion when they differ.
generated-sha: is what makes the drift check real: without a durable record of what the skill last wrote, an uncommitted previous round is indistinguishable from a hand edit, so the check would either warn on every rewrite or silently clobber.--out round-trips to the exact path given, a round-two run overwrites the same file rather than creating a -2 variant, and hand-editing the body then re-running triggers the confirmation prompt while an untouched file does not.write-prompt Phase 2 so a caller that has already interviewed the user skips the interview batch.
build-proposal Step 5 resolves decisions with the human already, and re-running Phase 2 would double-interview; the Claude Code docs are silent on any official pattern, so this is a documented repo-local $ARGUMENTS convention.AskUserQuestion calls.kit/plugins/artifact-tools/skills/prompt-artifact/SKILL.md — add proposal as a fifth filter chip at lines 148-149, make the frontmatter reader tolerate the new status: and modified: keys without breaking card rendering, and confirm the existing <details> collapse plus a horizontally scrolling <pre> handle a 300-line body without forcing page-level overflow.
$PROMPTS_DIR/*.md and hard-codes four chip values, so a fifth type ships a gallery where proposal prompts are invisible to every filter, and proposal prompts are roughly 3x longer than any prompt the gallery has rendered so far.type: proposal prompt renders a card reachable by its own chip, and the page body does not scroll horizontally at mobile width.build-proposal's Step 6 to dual-write — invoke write-prompt with the proposal content and the Step 5 bypass token, compute the target path once as {resolved-prompts-dir}/proposal-{verb-target-slug}.md — date-free, slug is the identity — and pass it to write-prompt via the Step 4 --out contract so the caller dictates the path rather than both sides deriving one independently, and additionally write the legacy docs/proposals/<slug>.md carrying a deprecation banner naming the prompt as authoritative — then rewrite Step 8 to hand off that same path, and retarget --dir to the prompts directory.
Skill() has no documented return value, and write-prompt's own Phase 7 would otherwise resolve a different directory and a different 3-5-word intent slug, so an independently derived path would name a file that was never written — passing it explicitly makes the two agree by construction. The date is omitted because a living document spanning two calendar days would otherwise resolve to a different filename on round two; created: and modified: carry the dates instead.build-proposal reports is byte-identical to the file write-prompt wrote, a loop spanning two dates resolves to one file, and ls {prompts-dir}/proposal-* shows exactly one file per slug.build-proposal/SKILL.md — Tier 0 continues to answer directly and write no artifact of either kind, and Tier 1 emits a prompt populated only from the short subset of slots ({{CONTEXT}}, {{CORE_FINDING}}, {{OPEN_QUESTIONS}}, {{CORE_INSTRUCTION}}) with the remaining slots omitted rather than emitted empty.
build/SKILL.md:189-193 documents a "No proposal written" fall-through that fires precisely when Tier 0 produces nothing, so making every tier write a file would silently break the chain, and the skill's own rule forbids emitting empty sections.build's fall-through still triggers, and a Tier 1 run produces a prompt with no empty slot headings.kit/plugins/plan-agent/skills/build-proposal/references/artifact-shape.md — add the section-to-slot mapping table from the proposal's Appendix B, and fix line 102's /plan-agent:implementation-plan docs/proposals/<slug>.md to lead with objective text.
.md handoff that SKILL.md:207-214 forbids and test check 15 greps for, but the check only scans SKILL.md, so the reference has been teaching the conversion-mode trap unnoticed.grep -qE 'implementation-plan +[^ ]+\.md' kit/plugins/plan-agent/skills/build-proposal/references/artifact-shape.md exits non-zero.kit/plugins/plan-agent/skills/build/SKILL.md Step 1b so the returned artifact is a prompt path — the Skill(skill: "plan-agent:build-proposal") call site at L179, the implementation-plan handoff string at L181-185, the dirty-tree exclusion at L100, and the abandonment contract at L216-218.
build-proposal reports without parsing it, so a changed artifact silently propagates unless every reference is updated together.--dir forwarding asymmetry documented at L180-185 still holds.tests/plugins/test-build-proposal.sh checks 10, 11, 14, and 15 to the dual-write contract, update tests/plugins/test-build-skill.sh's Step 1b assertions at L178-187, and add tests/plugins/test-write-prompt-proposal-type.sh plus the objective test tests/plugins/test-proposal-prompt-pipeline.sh.
test-build-skill.sh runs in CI via check-plugin-versions.yml:30 and publish-dist.yml:44, so leaving its assertions stale turns the release red.bash tests/plugins/test-build-skill.sh and bash tests/plugins/test-proposal-prompt-pipeline.sh both exit 0.kit/plugins/plan-agent/README.md (the build-proposal and write-prompt sections, and add the missing write-prompt/ entry to the Plugin Structure tree), kit/plugins/artifact-tools/README.md, CLAUDE.md:38,81, root README.md:451,470,473, both CHANGELOGs, and .claude-plugin/marketplace.json moving plan-agent from 5.0.0 to 6.0.0 with an artifact-tools minor bump.
test-build-proposal.sh check 12 asserts the marketplace version rises above origin/main.BASE_REF=main node scripts/check-plugin-versions.mjs exits 0 and no README or CLAUDE.md line still describes docs/proposals/<slug>.md as the sole deliverable.Tests
The tests that prove the change does what it promises.
Definition of done
The plan counts as done when every statement below is true — check each one off as you verify it.
Final check
One last pass to confirm the whole change works end to end.
Run the four test scripts and BASE_REF=main node scripts/check-plugin-versions.mjs; all five must exit 0. bash tests/plugins/test-proposal-prompt-pipeline.sh is the end-to-end gate — it asserts every seam of the refactor in one run.
Then exercise the real pipeline rather than trusting static assertions. From a session with the plugin loaded, run /plan-agent:build-proposal <a small test idea> --dir /tmp/proposal-check and confirm: a prompt file appears under the resolved prompts directory with type: proposal, status:, and modified: frontmatter; a legacy copy appears under docs/proposals/ carrying the deprecation banner; the Step 8 handoff line names the prompt path and leads with objective text. Run the same idea a second time against the same slug and confirm the prompt file is overwritten in place rather than a -2 variant appearing.
Finally publish the prompt library via prompt-artifact --library and confirm the rendered gallery shows five filter chips and that the proposal card is hidden and shown by the proposal chip.
Step 2 is a hard gate: if write-prompt does not become invocable after the wrapper lands, or the three-level nesting or cross-model-pin behavior fails, stop and report rather than continuing to Step 3 — every later step assumes that seam holds.
Wrapping up
Three gates that must all pass before this plan is marked completed.
Completion Report
- Step 1 deviates from "mirror
commands/deep-grill.mdexactly" - a command shadows a skill of the same name in the
Skillnamespace, so a wrapper whose body isSkill(skill: "plan-agent:write-prompt", ...)returns itself andSKILL.mdnever loads; measured with a headless probe against the worktree plugin, that shape put 0## Phaseheadings in context while reading${CLAUDE_PLUGIN_ROOT}/skills/write-prompt/SKILL.mdby path put all 7 there, so the wrapper loads the skill file by path (with aGlobfallback) and carries the reason inline - The first pass shipped the self-delegating wrapper and a chained run silently worked around it
build→build-proposal→write-promptproduced correct output by readingSKILL.mditself, through a seam that was not actually connected; that is the failure grep-based tests cannot see, and is why Step 2 exists as a hard gate- Both new test files assert the by-path contract
- check 1 in each now requires the wrapper to name
skills/write-prompt/SKILL.mdand to explain the shadowing, rather than requiring the presence of aSkill()call, which the broken wrapper also satisfied commands/deep-grill.mdandcommands/documenting-plans.mdcarry the same shape and are presumed to have the same defect- left untouched as out of scope, with a follow-up task filed
- The prompt library gallery was verified structurally, not visually
- five chips present,
data-type="proposal"on both proposal cards, exact-match filter JS (card.dataset.type === t), andoverflow-x: autoon the<pre>; it was not published to claude.ai (an outward-facing action needing explicit approval) and not confirmed at mobile width, as the browser pane timed out