"/blueprint", "blueprint", "task graph", "contract derivation", "execution plan",
"plan tasks from requirements", "contract-first planning"
Turn requirements.md into an executable blueprint (plan.json + contracts.md).
Five phases: Contracts → Tasks → Journeys → Verify Plan → Commit.
Sits between /specify and /execute. Scope-adaptive (greenfield → bugfix).
Uses hoyeon-cli (plan.json only; requirements.md is read as-is via Read tool).
blueprint: Requirements → Executable Plan
Overview
Transform <spec_dir>/requirements.md (from /specify) into an executable blueprint that /execute can run without rework:
Contract Synthesis — derive cross-module agreements (types, interfaces, invariants) that keep parallel work safe
Task Graph — layered DAG (L0 Foundation → L1 Feature → L2 Integration → L3 Deploy) where every sub-requirement has ≥1 fulfilling task
Journey Detection — identify multi-sub-req user flows that need end-to-end coverage
Verify Plan — assign verification gates (1=machine, 2=agent_semantic, 3=agent_e2e, 4=human) per sub-req and per journey
Commit — run cross-ref validation and hand off to /execute
Contract-first principle: lock "how modules talk" before anyone writes code. Parallel workers can't break each other's shapes; required invariants are called out explicitly.
Ask-only-when-owned principle: prompt the user only at decisions they actually own — missing inputs (no spec_dir), conflicting signals with no safe default (meta.type conflict), or real commitments they must make (G4 playtests, flagged time/confidence ambiguities). Task-graph correctness and final summaries are not user decisions: the coverage gate (2.2) and cross-ref validator (5.1) enforce them mechanically. Print, don't ask.
Not blueprint's job: writing source code, running tests, interviewing for missing requirements. If requirements are incomplete, run /specify first.
Runtime Surface
Claude Code
Use Agent(subagent_type="...") for Hoyeon logical subagents such as
code-explorer, contract-deriver, and taskgraph-planner.
Use AskUserQuestion only for user-owned decisions described by this skill.
Claude hooks may add session context, but blueprint must still leave a valid
plan.json on disk through hoyeon-cli.
Codex
Use the same Hoyeon logical subagent names in prompts, but dispatch through
Codex adapters when installed: hoyeon-code-explorer, hoyeon-worker,
hoyeon-verifier, and hoyeon-code-reviewer.
Use Bash-first state operations. Do not use MCP in v1.
Mutate plan.json only through hoyeon-cli plan init|merge|validate.
Prefer temporary JSON payload files or fixture files over inline complex JSON.
Do not rely on hooks for session guards or stop transitions.
Input / Output
Input
<spec_dir>/requirements.md — required (produced by /specify)
Optional: existing plan.json — treated as prior state, patched additively
All plan.json operations go through hoyeon-cli (NOT legacy hoyeon-cli):
Command
Purpose
hoyeon-cli plan init <spec_dir> --type <t>
Create empty stub (if missing)
hoyeon-cli plan merge <spec_dir> --json '<payload>' [--patch|--append]
Merge JSON with schema validation
hoyeon-cli plan get <spec_dir> --path <dotted>
Read field
hoyeon-cli plan validate <spec_dir>
Schema + internal cross-ref integrity
cli never parses requirements.md. Reading the markdown is the blueprint agent's job (via Read tool). cli only validates plan.json self-consistency. Coverage against requirements.md is enforced semantically by the LLM (Phase 2 / Phase 4 of this skill).
Scope Adaptation (meta.type)
meta.type
Contract artifact shape (when written)
Task graph
greenfield
Full surface: types + interfaces + invariants (~50-200 lines)
L0-L3, parallel L1
feature
Delta only: new types/interfaces this feature adds (~10-50 lines)
L0-L2, parallel if multi-module
refactor
Pin-style: ## Frozen Public API + ## Allowed Churn + ## Invariants
Flat list with invariant guards
bugfix
Minimal: typically just an ## Invariants section
Single chain (1-3 tasks)
Approval is not meta.type-scaled anymore — the ask-only-when-owned rule governs every type uniformly (see top-level principle).
contracts.md is content-driven, not type-driven. Write it whenever contract-deriver finds any cross-module content (≥1 invariant or interface) — regardless of meta.type. Skip the file (return artifact: null) only when the agent genuinely has nothing to pin. A bugfix with 3 load-bearing invariants gets a file; a feature that only adds a config flag may not. meta.type decides the template shape, not the file's existence.
meta.type normally comes from /specify (written into requirements.md frontmatter). If the field is missing — manual authoring, legacy spec, etc. — infer it using this priority (stop at the first matching rule):
Keywords in goal (highest signal, author's stated intent)
Repo state (hard physical signal — either empty or not)
spec_dir's parent repo has no source files (empty / fresh scaffold) → greenfield
Size (weakest heuristic — only when 1 and 2 are silent)
< 5 sub-reqs → bugfix
< 15 sub-reqs → feature
≥ 15 sub-reqs → greenfield
On conflict, stop and ask. If signals point to different types (e.g., keyword says refactor but repo is empty → greenfield), do NOT silently pick one. Emit AskUserQuestion with the top 2 candidates and let the user decide. Do not proceed to Phase 1 until confirmed.
Phase 0: Init
Step 0.1: Resolve spec_dir
If user passed a path, use it. Otherwise ask: "Which spec_dir? (e.g., .hoyeon/specs/my-thing/)"
Error if <spec_dir>/requirements.md does not exist — tell user to run /specify first.
Step 0.2: Read requirements.md
Use Read tool directly. Do not shell out to cli for parsing — cli has no such command.
Extract (you, the main agent, parse this from the markdown):
Frontmatter: type, goal, non_goals (YAML between --- delimiters)
Sub-requirements: every ## R-X<num>: parent + each #### R-X<num>.Y: child with given/when/then fields
Skip if meta.type == greenfield. For feature, refactor, and bugfix, scan the existing codebase so that contract derivation and task planning are grounded in real code structure — not just requirements text.
Step 0.5.1: Dispatch code-explorer (parallel)
Agent(subagent_type="code-explorer",
prompt="Goal: {meta.goal}. Find: project structure, modules, existing interfaces/types
relevant to this change. Report as file:line with brief summary.",
run_in_background=true)
Agent(subagent_type="code-explorer",
prompt="Goal: {meta.goal}. Find: existing test infrastructure (test runner, test dirs,
fixture patterns) and build/lint commands. Report as file:line.",
run_in_background=true)
Agent(subagent_type="code-explorer",
prompt="Goal: {meta.goal}. Blast radius analysis:
1. Find all callers/consumers of the modules being changed
2. Find existing tests that cover these modules (test files + test names)
3. Identify existing user-facing or API flows that pass through these modules
4. Flag flows that have NO existing test coverage
Report as: affected_flows (name + file:line entry), existing_tests (file:line),
untested_flows (name + why no test found).",
run_in_background=true)
Step 0.5.2: Build code context summary
Consolidate agent results into a short context block (keep in memory, not a file):
Pass code_context to Phase 1 (contract-deriver), Phase 2 (taskgraph-planner), and Phase 3 (journey detection) agent prompts alongside requirements.md content. This helps agents ground their output in actual file structure rather than inventing module names.
Phase 1: Contract Synthesis
Goal: produce the minimal cross-module surface area.
Step 1.1: Dispatch contract-deriver agent
Pass:
Full requirements.md content (you already read it in 0.2 — inline into agent prompt)
Detected meta.type
spec_dir absolute path
code_context summary from Phase 0.5 (if non-greenfield; omit for greenfield)
The agent writes <spec_dir>/contracts.md (markdown) and returns:
File existence is content-driven (all types). If the agent produces any invariants[] or interfaces[], it writes contracts.md. If there is genuinely nothing cross-module to pin, it returns "artifact": null and the invariants (if any) live in plan.contracts.invariants. This rule is the same for every meta.type; the type only decides the file's internal shape.
Goal: every sub-requirement is fulfilled by ≥1 task; parallelism is explicit.
Step 2.1: Dispatch taskgraph-planner agent
Pass:
Full requirements.md content
Phase 1 contracts summary (artifact name + interfaces + invariants)
meta.type
code_context summary from Phase 0.5 (if non-greenfield; omit for greenfield)
Expected output:
{"tasks":[{"id":"T1","layer":"L0","action":"write contracts.md + storage sig util","fulfills":["R-T2.1","R-T7.1"],"depends_on":[],"parallel_safe":false},
...
],"ambiguities":[]}
Tasks carry WHAT, not HOW. The action string is the only description field; it must capture intent, not file paths / function names / estimated time. Workers decide implementation detail — locking HOW into plan.json causes drift when the worker discovers the real shape mid-implementation.
Step 2.2: Coverage gate (semantic, by you)
cli does NOT verify coverage against requirements.md. You must ensure:
EveryR-X.Y sub-requirement appears in at least one tasks[].fulfills. Build a set diff:
uncovered = { all sub_req_ids } − union(tasks[].fulfills)
If uncovered is non-empty, re-dispatch taskgraph-planner with the list as a constraint. Max 2 retries. If still uncovered, surface to user.
No task references a non-existent sub-req ID (orphan). Drop orphans before merging.
Parallel safety: for each L1 task pair with parallel_safe: true, double-check they touch different modules and share only L0 contract state. If uncertain → set parallel_safe: false (serial is safe default).
Step 2.3: Preview task graph (informational)
Print the table so the user can see what got planned — but do not ask for approval. The coverage gate in 2.2 already verifies the graph is well-formed; asking "is this right?" forces the user to re-judge agent output they can't usefully correct without seeing code.
Only prompt when taskgraph-planner.ambiguities[] contains items with user_impact of time or confidence (see "Ambiguity Handling"). Otherwise proceed to 2.4 silently.
Use --append on first write. Use --patch later if you need to update individual task fields by id.
Phase 3: Journey Detection
Goal: identify multi-sub-req user flows that need E2E coverage.
A journey composes ≥2 sub-requirements into a single linear user flow, with its own given/when/then. Example: "user signs up → confirms email → sees dashboard" might compose R-U1.1 (signup form) + R-U1.2 (email confirm) + R-U2.1 (dashboard initial render).
Step 3.1: Heuristic detection (inline by you)
Scan the sub-req list for clusters where:
2+ sub-reqs share a common actor (user, admin, API client)
Their when clauses chain naturally (next action follows prior outcome)
There is a meaningful top-level outcome only visible after running them together
Not every spec has journeys. Bugfix specs usually have 0. Greenfield user-facing specs usually have 2-5.
Skip if meta.type == greenfield or code_context.blast_radius is empty.
Scan code_context.blast_radius.affected_flows for existing flows that pass through modules being changed. For each affected flow, generate a regression journey with [regression] prefix in the name:
Heuristic: an affected flow becomes a regression journey when:
It passes through ≥1 module that a task in the task graph modifies
It represents a user-visible or API-facing behavior (not internal-only)
Link to tasks: identify which tasks (T1, T2, ...) touch the affected modules, and list them in the journey's composes field alongside any related new sub-req IDs.
Prioritize untested flows: flows from blast_radius.untested_flows are higher priority — they have no safety net and MUST become regression journeys if they are user-facing.
Regression journeys use the same schema as regular journeys — no schema change needed.
Step 3.2: Emit journey entries
For each detected journey:
{"id":"J1","name":"new user onboarding","composes":["R-U1.1","R-U1.2","R-U2.1"],"given":"no prior account","when":"user completes signup → confirms email → lands on dashboard","then":"dashboard shows welcome state with 0 items"}
Regression journey example (from Step 3.1b):
{"id":"J3","name":"[regression] checkout flow preserved after payment module change","composes":["R-T1.1","R-T1.2"],"given":"existing checkout flow works with valid payment","when":"user completes purchase after code changes from T3/T5","then":"checkout succeeds identically to pre-change behavior"}
Constraints (enforced by schema):
id matches ^J\d+$
composes has ≥2 items, each is a valid R-X.Y id
given, when, then all non-empty strings
Regression journeys use [regression] prefix in name — same schema, no special type field
Every sub-req and journey gets Gate 1 + Gate 2 as minimum.
Journeys additionally get Gate 3 by default (journeys exist precisely because E2E flow matters).
Regression journeys (name starts with [regression]) get Gate 1 (run existing tests from blast_radius.existing_tests) + Gate 3 (E2E confirmation). Gate 1 is especially important here: if existing tests exist for the affected flow, running them IS the regression check. Gate 2 is optional for regression journeys (semantic review adds less value when the behavior should be identical to pre-change).
Every sub-req id from requirements.md appears exactly once as a type: sub_req target.
Every journey id appears exactly once as a type: journey target.
Every entry has gates containing at least [1, 2].
gates is a sorted unique integer array, each element in [1..4].
If mismatch, re-dispatch verify-planner with the gap list. Max 2 retries.
Step 4.3: Preview verify plan for user
Translate gate counts into user-facing consequences. The user should not have to decode G1/G2/G3/G4 labels — only understand what the plan will cost them and where their attention is actually required.
[blueprint] Verify Plan
{N_all} checks will run automatically (code review + agent semantic)
{N_e2e} of those also run in the browser/sandbox (visible UI, interaction, external calls)
{N_human} items require YOU (playtest, sampled metrics, aesthetic review)
What you need to do: {none | <bullet list of G4 items with their GWT>}
Example with no G4:
[blueprint] Verify Plan
46 checks will run automatically
26 of those also run in the browser sandbox
0 items require you
What you need to do: nothing — fully machine/agent-verifiable.
Example with G4:
[blueprint] Verify Plan
18 checks will run automatically
7 of those also run in the browser sandbox
1 item requires you:
• R-B4.1 "retry rate averages 3+ per session" — needs a playtest with 3+ users
What you need to do: 1 playtest session.
Default = silent. Print the preview block and proceed to Step 4.4 unless one of the following user-owned decisions exists:
verify_plan contains any G4 target — the user has to commit real time (playtest, sampled metrics, aesthetic review)
ambiguities[] (collected across phases, after filtering — see "Ambiguity Handling") contains any item with user_impact of time or confidence
If neither applies, log auto-approving (no user-owned commitments) and continue. No generic "proceed?" prompt.
When to actually ask: only when the user has something real to decide. Build the question from the filtered ambiguities queue (see "Ambiguity Handling" section) and/or G4 confirmations:
AskUserQuestion(
# one question per user-impact ambiguity, phrased in user terms, NOT gate labels
# Example (time-impact):
question: "R-B4.1 needs real-user data ('retry rate averages 3+') — commit to a 3-user playtest, or relax this requirement to code-review only?",
options: [
{ label: "Commit to playtest", description: "Add human verification — you run a session with 3+ users before ship" },
{ label: "Relax the bar", description: "Drop the sampled-user requirement, rely on code review of the difficulty curve formula" }
]
)
Never expose "G1/G2/G3/G4", "gates: [1,2,3]", or "drop redundant G3" to the user. If the planner flagged an ambiguity that way, restate it: what real thing does the user gain/lose by each option?
If the user chooses to revise: apply the chosen option to verify_plan (add/drop gates as implied), re-preview once. Max 2 rounds, then proceed with the last-confirmed plan.
This runs schema validation AND these internal cross-ref checks:
tasks[].fulfills ⊆ verify_plan sub_req targets
journeys[].composes ⊆ verify_plan sub_req targets
Every journeys[].id has a verify_plan entry of type: journey
Every verify_plantype: journey target matches a declared journey id
tasks[].depends_on ⊆ tasks[].id
If validation fails, diagnose the specific rule violation and re-merge corrected JSON. Never ignore a validation failure.
Step 5.2: Summary (informational)
Print a compact summary and proceed. No approval prompt — Phase 4.3 already handled every user-owned decision (G4 commitments, time/confidence ambiguities). A final "y/n" here adds nothing the user can act on without reading code.
Rule: surface ambiguities to the user only when they own the decision — i.e., the outcome changes what they must do, pay for, or commit to. Planner-internal optimizations (redundant gates, CSS-vs-measurement, pure-logic gate sufficiency) are NOT user decisions; apply the agent's recommendation silently and log it.
All three agents return ambiguities[] with this shape:
user_impact semantics (see verify-planner.md for the canonical definition):
time — forces human work (playtest, sampled metrics, aesthetic review). Always prompt.
confidence — meaningfully swings verification confidence with no safe default. Prompt unless --auto.
none — planner-internal call. Never prompt; apply recommendation and log.
Sources collected across phases:
requirements.md## Open Decisions section (OD-N blocks) — include if still unresolved (treat as user_impact: confidence by default)
contract-deriver return field ambiguities[]
taskgraph-planner return field ambiguities[]
verify-planner return field ambiguities[]
Agents that do not yet emit user_impact (older contract-deriver / taskgraph-planner outputs) default to confidence unless the concern is obviously planner-internal.
Protocol
Collect — after each agent returns, extract its ambiguities[] into a single queue.
Filter — drop every item with user_impact: none. Apply its recommendation to the in-progress artifact and record one line in the run log: auto-resolved: <concern> → <recommendation>.
Dedupe — merge semantically overlapping items (e.g., OD-2 + a contract-deriver concern about the same decision). Prefer requirements.md wording as canonical.
Translate — rewrite each remaining item in user-impact language: what does the user gain/lose from each option? Do NOT expose internal labels (G1-G4, gate ids, dispatch types) in the question or options.
Prompt — emit AskUserQuestion for the translated queue. AskUserQuestion tops out at ~5 questions per call; batch across multiple calls in order. Each option must include the agent's recommendation marked (recommended).
Apply answers — patch the in-progress plan.json (or regenerate the affected section) before proceeding to the next phase.
Trust the filter. If the agent labeled something user_impact: none, do not second-guess and promote it to a prompt. The agents are instructed to be conservative; items that reach the queue with time or confidence already passed a "does the user own this?" test.
Flags
--auto → skip all prompts, apply every recommendation silently (including time / confidence items), log applied decisions in the final summary
Default (no flag): prompt only for user_impact in (time, confidence); always auto-resolve none
Agents are globally registered at plugin-root /agents/{name}.md. Dispatch via the Agent tool with subagent_type: "<name>".
Command Reference (blueprint-only subset)
All state changes go through cli with one --json per merge. Never hand-write plan.json.
# Init (idempotent — skip if exists)
hoyeon-cli plan init <spec_dir> --type greenfield
# Patch meta (replace field values, keep unchanged fields)
hoyeon-cli plan merge <spec_dir> --patch --json '{"meta":{...}}'# Append to arrays (tasks/journeys/verify_plan)
hoyeon-cli plan merge <spec_dir> --append --json '{"tasks":[...]}'# Patch array items by id (update single task field)
hoyeon-cli plan merge <spec_dir> --patch --json '{"tasks":[{"id":"T3","status":"in_progress"}]}'# Final sanity
hoyeon-cli plan validate <spec_dir>
JSON passing: always write to /tmp/bp-<step>.json via heredoc first, then pass with --json "$(cat ...)". Direct inlining breaks on zsh glob expansion ([, {, $).
Failure Modes
Failure
Recovery
requirements.md missing
Tell user to run /specify; abort
plan validate schema error
Diagnose (cli prints specific path + message), re-merge corrected JSON
plan validate cross-ref error (e.g., task fulfills missing from verify_plan)
Re-dispatch verify-planner with the missing ids
Uncovered sub-req after taskgraph-planner
Re-dispatch with uncovered list (max 2 retries), then surface to user
User rejects after blueprint commits
Do NOT revert files. User can edit requirements.md and re-run /blueprint, or edit plan.json directly via cli.
Mode B: Inline call from /execute
When /execute is invoked without a plan.json, it may call this skill inline with --auto --no-summary. Same phases, no approval prompts. This is a flag combination, not a separate code path.
Non-Goals
Re-interviewing requirements (that's /specify)
Implementation work in src/ (contracts.md at spec_dir/ is the only artifact blueprint produces)
Running verifications (that's /execute)
Parsing requirements.md inside cli (LLM reads directly via Read tool)
Rendering a human-readable view file (read plan.json directly — it's structured and small)