Make build-proposal converge on a saved prompt authored by write-prompt

High completed
2026-07-27 agentics refactor High effort

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.

At a glance

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.

Implement 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
Pursue as goal — optimize for the outcome, in parallel
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 as workflow — launch parallel subagents
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.
File refactor-build-proposal-to-emit-prompt.html
Path docs/plans/refactor-build-proposal-to-emit-prompt.html
Spec docs/plans/refactor-build-proposal-to-emit-prompt.md
Definition of done 15 / 15 done

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.

agentics/
  • kit/plugins/plan-agent/commands/write-prompt.md new thin Skill wrapper that unblocks programmatic invocation
  • kit/plugins/plan-agent/skills/write-prompt/references/proposal-prompt-template.md new fifth template with proposal-shaped slots
  • kit/plugins/plan-agent/skills/write-prompt/SKILL.md modified fifth type across Phases 1, 2, 3, 4, 7
  • kit/plugins/plan-agent/skills/build-proposal/SKILL.md modified dual-write in Step 6, prompt handoff in Step 8, --dir retarget
  • kit/plugins/plan-agent/skills/build-proposal/references/artifact-shape.md modified slot mapping and the bare-.md handoff fix at line 102
  • kit/plugins/plan-agent/skills/build/SKILL.md modified Step 1b prompt path, dirty-tree exclusion, abandonment contract
  • kit/plugins/artifact-tools/skills/prompt-artifact/SKILL.md modified fifth filter chip and tolerance for status/modified frontmatter
  • tests/plugins/
    • test-proposal-prompt-pipeline.sh new objective-verification test for the whole wiring
    • test-write-prompt-proposal-type.sh new unit coverage for the fifth type
    • test-build-proposal.sh modified checks 10, 11, 14, 15 rewritten to the dual-write contract
    • test-build-skill.sh modified Step 1b assertions updated; runs in CI
  • kit/plugins/plan-agent/README.md modified build-proposal and write-prompt sections, Plugin Structure tree
  • kit/plugins/artifact-tools/README.md modified prompt-artifact type list
  • kit/plugins/plan-agent/CHANGELOG.md modified 6.0.0 entry
  • kit/plugins/artifact-tools/CHANGELOG.md modified fifth-chip entry
  • .claude-plugin/marketplace.json modified plan-agent 5.0.0 to 6.0.0, artifact-tools minor bump
  • CLAUDE.md modified plan-agent and artifact-tools table rows
  • README.md modified 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.

1
done Create 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.
Why
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.
Verify
the file is 15 lines or fewer, 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.
2
done Prove the invocation seam before building on it — from a session with the plugin loaded, invoke /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.
Why
risks 1 through 3 in the proposal (no documented Skill() return value, three-level nesting, and cross-model-pin behavior) are all unverified, and every later step assumes they hold.
Verify
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.
3
done Create 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}}.
Why
a 326-line proposal compressed into the 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.
Verify
all 11 placeholder tokens appear in both the template block and the Placeholder Guide table, and the Assembled Example contains no unsubstituted {{ tokens.
4
done Wire the 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.
Why
Phase 7 today picks its own directory and derives its own slug, so any caller that independently computes the path will disagree with it and name a file that was never written — the caller must dictate the path rather than guess it. And 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.
Verify
the type appears in all five phases, --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.
5
done Add a pre-gathered-answers bypass to write-prompt Phase 2 so a caller that has already interviewed the user skips the interview batch.
Why
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.
Verify
Phase 2 documents the bypass token and its skip condition, and an invocation carrying the token produces a prompt with zero AskUserQuestion calls.
6
done Update 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.
Why
the skill globs $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.
Verify
the chip list names all five types, publishing a library containing a type: proposal prompt renders a card reachable by its own chip, and the page body does not scroll horizontally at mobile width.
7
done Rewrite 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.
Why
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.
Verify
the path 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.
8
done Preserve tier behavior across the refactor in 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.
Why
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.
Verify
a Tier 0 idea produces zero files in both directories and build's fall-through still triggers, and a Tier 1 run produces a prompt with no empty slot headings.
9
done Update 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.
Why
line 102 currently advertises the bare-.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.
Verify
grep -qE 'implementation-plan +[^ ]+\.md' kit/plugins/plan-agent/skills/build-proposal/references/artifact-shape.md exits non-zero.
10
done Update 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.
Why
the chain interpolates whatever build-proposal reports without parsing it, so a changed artifact silently propagates unless every reference is updated together.
Verify
no line in Step 1b refers to a proposal path as the chained artifact, and the --dir forwarding asymmetry documented at L180-185 still holds.
11
done Update the three test files — rewrite 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.
Why
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.
Verify
bash tests/plugins/test-build-skill.sh and bash tests/plugins/test-proposal-prompt-pipeline.sh both exit 0.
12
done Update the documentation and bump versions — 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.
Why
the change removes an artifact contract, which is a major bump under the repo's semver rules, and test-build-proposal.sh check 12 asserts the marketplace version rises above origin/main.
Verify
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.

Tier 1 — This plan creates and modifies executable test scripts, plugin skill definitions, and marketplace configuration
Objective: build-proposal converges on a saved prompt authored by write-prompt. File: tests/plugins/test-proposal-prompt-pipeline.sh; Type: smoke; Asserts: the command wrapper exists and calls Skill with the write-prompt target, write-prompt declares the proposal type and its template file exists with all 11 placeholders, build-proposal Step 6 dual-writes and derives a date-free proposal-{slug}.md path, the legacy copy carries a deprecation banner, Step 8 hands off a docs/prompts path, Tier 0 is documented as writing no artifact, prompt-artifact lists five filter chips, and no build-proposal file advertises a bare-.md handoff; Run: bash tests/plugins/test-proposal-prompt-pipeline.sh
Unit: the fifth prompt type is wired through every write-prompt phase. File: tests/plugins/test-write-prompt-proposal-type.sh; Targets: write-prompt/SKILL.md Phases 1, 2, 3, 4, 7 and references/proposal-prompt-template.md; Key cases: type table row, technique matrix row, XML layer mapping, template selection path, --out contract overrides directory resolution and intent slug, status/modified/generated-sha frontmatter keys, in-place rewrite rule, drift check fires on a hand edit and stays silent on an untouched uncommitted file, bypass token documented, all 11 placeholders present in both template and guide
Integration: the build chain still resolves end-to-end with the new artifact. File: tests/plugins/test-build-skill.sh; Targets: build/SKILL.md Step 1b; Key cases: the build-proposal Skill call site, the proposal-versus-direct gate, the no-artifact fall-through, and the --dir forwarding asymmetry
Integration: build-proposal's own contract under dual-write. File: tests/plugins/test-build-proposal.sh; Targets: build-proposal/SKILL.md and references/; Key cases: rewritten checks 10, 11, 14, 15 plus the existing marketplace-version check 12

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.

Required

Completion Report

Step 1 deviates from "mirror commands/deep-grill.md exactly"
a command shadows a skill of the same name in the Skill namespace, so a wrapper whose body is Skill(skill: "plan-agent:write-prompt", ...) returns itself and SKILL.md never loads; measured with a headless probe against the worktree plugin, that shape put 0 ## Phase headings in context while reading ${CLAUDE_PLUGIN_ROOT}/skills/write-prompt/SKILL.md by path put all 7 there, so the wrapper loads the skill file by path (with a Glob fallback) 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-prompt produced correct output by reading SKILL.md itself, 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.md and to explain the shadowing, rather than requiring the presence of a Skill() call, which the broken wrapper also satisfied
commands/deep-grill.md and commands/documenting-plans.md carry 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), and overflow-x: auto on 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

Next steps

Follow-up ideas that came up along the way — none of them are required to finish this plan.

Remove the deprecated proposals path in 6.1.0

Completes the migration once the deprecation window closes.

Paste this prompt into Claude to execute this follow-up:

In the agentics repo (github.com/shawn-sandy/agentics), remove the deprecated
dual-write proposal path from the build-proposal skill. Delete the
docs/proposals/ write branch and its deprecation banner from
kit/plugins/plan-agent/skills/build-proposal/SKILL.md Step 6, remove the
docs/proposals/ directory and its .gitkeep, and fix the now-dangling relative
link at docs/plans/merge-plan-interview-into-plan-agent.md:26 to point at the
prompt artifact instead. Update tests/plugins/test-build-proposal.sh to drop
the dual-write assertions, update kit/plugins/plan-agent/README.md and
CLAUDE.md, bump plan-agent to 6.1.0 in .claude-plugin/marketplace.json, and
add a kit/plugins/plan-agent/CHANGELOG.md entry. Verify by running
bash tests/plugins/test-build-proposal.sh and
bash tests/plugins/test-proposal-prompt-pipeline.sh — both must exit 0 — and
confirm no file outside docs/plans/ or docs/artifacts/ still references
docs/proposals/.
Wire test-build-proposal.sh into CI

It has never run in any workflow and check 12 is red on main.

Paste this prompt into Claude to execute this follow-up:

In the agentics repo (github.com/shawn-sandy/agentics), add
tests/plugins/test-build-proposal.sh to the test matrix in
.github/workflows/check-plugin-versions.yml and
.github/workflows/publish-dist.yml, alongside the existing
tests/plugins/test-build-skill.sh invocation. Fix whatever makes check 12
(the dynamic marketplace-version comparison against origin/main) fail before
wiring it in, so the workflow does not go red on merge. Context:
docs/plans/wire-plugin-tests-into-ci.md:47 records that this test is
deliberately unwired because check 12 is failing. Verify by running
bash tests/plugins/test-build-proposal.sh locally — it must exit 0 — and by
confirming both workflow files invoke it.
Make write-prompt actually read its own best-practices reference

119 lines of guidance are currently dead weight.

Paste this prompt into Claude to execute this follow-up:

In the agentics repo (github.com/shawn-sandy/agentics), the file
kit/plugins/plan-agent/skills/write-prompt/references/best-practices-reference.md
declares at line 3 that it "is consumed by the write-prompt skill", but no
phase in kit/plugins/plan-agent/skills/write-prompt/SKILL.md instructs
reading it, so its 119 lines of Anthropic prompting guidance never load.
Either wire it into Phase 3 or Phase 4 with an explicit Read instruction, or
delete it and fold the load-bearing rules inline into the phase bodies —
decide based on which keeps SKILL.md under the 500-line authoring budget.
Bump the plan-agent patch version in .claude-plugin/marketplace.json and add
a CHANGELOG entry. Verify by confirming either that SKILL.md names the
reference path in a phase body, or that the file no longer exists and no
file references it.
Normalize the existing docs/prompts corpus

One of the four saved prompts contradicts the documented body format.

Paste this prompt into Claude to execute this follow-up:

In the agentics repo (github.com/shawn-sandy/agentics), the file
docs/prompts/task-refactor-authentication-middleware-2026-06-04.md wraps its
prompt body in a ```text fence (lines 10 and 27), contradicting
kit/plugins/plan-agent/skills/write-prompt/SKILL.md line 282, which says to
embed the prompt as plain text rather than the Phase 6 fenced display block.
The other three files in docs/prompts/ are unfenced. Remove the fence so all
four files share one body format. Verify by confirming no file in
docs/prompts/ contains a fence immediately after its H1, and that
kit/plugins/artifact-tools/skills/prompt-artifact/SKILL.md still renders the
body correctly into its <pre> block.