blueprint: Requirements → Executable Plan

SkillProductivity

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

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the blueprint: Requirements → Executable Plan skill

What this skill tells your AI

The instructions your AI receives, as published by team-attention/hoyeon in skills/blueprint/SKILL.md and read by ahel’s review.

Overview

Transform <spec_dir>/requirements.md (from /specify) into an executable blueprint that /execute can run without rework:

  1. Contract Synthesis — derive cross-module agreements (types, interfaces, invariants) that keep parallel work safe
  2. Task Graph — layered DAG (L0 Foundation → L1 Feature → L2 Integration → L3 Deploy) where every sub-requirement has ≥1 fulfilling task
  3. Journey Detection — identify multi-sub-req user flows that need end-to-end coverage
  4. Verify Plan — assign verification gates (1=machine, 2=agent_semantic, 3=agent_e2e, 4=human) per sub-req and per journey
  5. 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

Output

<spec_dir>/
├── requirements.md   # unchanged (input)
├── plan.json         # NEW/UPDATED: tasks + journeys + verify_plan + contracts summary
└── contracts.md      # NEW: cross-module surface (markdown). Optional for trivial bugfix.

Only those three files. No rendered view file, no language-specific stubs.

File role separation

  • requirements.md — specify owns. Human-editable markdown with sub-requirements (GWT).
  • plan.json — blueprint owns. Machine state. Schema: plan/v1 (see cli/schemas/plan.schema.json).
  • contracts.md — blueprint creates. Sibling artifact referenced by plan.contracts.artifact. Markdown, language-agnostic.

Prerequisite: cli

All plan.json operations go through hoyeon-cli (NOT legacy hoyeon-cli):

CommandPurpose
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.typeContract artifact shape (when written)Task graph
greenfieldFull surface: types + interfaces + invariants (~50-200 lines)L0-L3, parallel L1
featureDelta only: new types/interfaces this feature adds (~10-50 lines)L0-L2, parallel if multi-module
refactorPin-style: ## Frozen Public API + ## Allowed Churn + ## InvariantsFlat list with invariant guards
bugfixMinimal: typically just an ## Invariants sectionSingle 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):

  1. Keywords in goal (highest signal, author's stated intent)

    • Contains refactor / migrate / restructure / rewriterefactor
    • Contains fix / bug / regression / brokenbugfix
  2. Repo state (hard physical signal — either empty or not)

    • spec_dir's parent repo has no source files (empty / fresh scaffold) → greenfield
  3. 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
  • Open decisions (optional): any ### OD-N: blocks

Build an internal list:

reqs = [
  { parent: "R-B1", title: "...", subs: [
    { id: "R-B1.1", title: "...", given: "...", when: "...", then: "..." },
    ...
  ]},
  ...
]

Step 0.3: Init plan.json stub

hoyeon-cli plan init <spec_dir> --type <meta.type>

If plan.json already exists (re-run), skip init and treat as patch-merge mode.

Step 0.4: Patch meta with real goal/non_goals

cat > /tmp/bp-meta.json << 'EOF'
{"meta": {"type": "<t>", "goal": "<goal>", "non_goals": ["..."]}}
EOF
hoyeon-cli plan merge <spec_dir> --patch --json "$(cat /tmp/bp-meta.json)"

Phase 0.5: Codebase Reconnaissance (non-greenfield only)

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):

code_context = {
  modules: ["src/api/", "src/storage/", "src/ui/"],
  existing_interfaces: ["StorageAPI (src/storage/types.ts:12)", ...],
  test_infra: "vitest, src/__tests__/, no E2E setup",
  entry_points: ["src/main.ts", "src/api/router.ts"],
  blast_radius: {
    affected_flows: ["checkout flow (src/api/orders.ts:45 → src/payment/charge.ts:12)"],
    existing_tests: ["test/checkout.test.ts", "test/payment.integration.ts"],
    untested_flows: ["admin refund flow (no test found)"]
  }
}

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:

{
  "artifact": "contracts.md",
  "interfaces": ["InputAPI", "StorageAPI", "RendererAPI"],
  "invariants": ["INV-1: ...", "INV-2: ..."],
  "ambiguities": []
}

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.

Step 1.2: Merge contracts into plan.json

cat > /tmp/bp-contracts.json << 'EOF'
{"contracts": {"artifact": "contracts.md", "interfaces": [...], "invariants": [...]}}
EOF
hoyeon-cli plan merge <spec_dir> --patch --json "$(cat /tmp/bp-contracts.json)"

Phase 2: Task Graph

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:

  • Every R-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.

[blueprint] Task Graph (Phase 2)

| # | Layer | Action | Fulfills | Depends | Parallel |
|---|-------|--------|----------|---------|----------|
| T1 | L0 | write contracts.md + storage sig | R-T2.1, R-T7.1 | — | no |
| T2 | L1 | implement auth flow | R-U1.1, R-U1.2 | T1 | yes |
| ...

Coverage: 12/12 sub-reqs fulfilled (0 uncovered)

Only prompt when taskgraph-planner.ambiguities[] contains items with user_impact of time or confidence (see "Ambiguity Handling"). Otherwise proceed to 2.4 silently.

Step 2.4: Merge tasks into plan.json

cat > /tmp/bp-tasks.json << 'EOF'
{"tasks": [ ... ]}
EOF
hoyeon-cli plan merge <spec_dir> --append --json "$(cat /tmp/bp-tasks.json)"

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.

Step 3.1b: Regression journey detection (non-greenfield only)

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

Step 3.3: Merge journeys

cat > /tmp/bp-journeys.json << 'EOF'
{"journeys": [ ... ]}
EOF
hoyeon-cli plan merge <spec_dir> --append --json "$(cat /tmp/bp-journeys.json)"

Phase 4: Verify Plan

Goal: every sub-req AND every journey gets a gate assignment.

4-Gate model (cumulative; each gate adds, never replaces)

GateNameWhat it meansTypical cost
1machineDeterministic check: unit test, type check, file contents, shell exit codeseconds, free
2agent_semanticLLM reads code/output and judges "does this match the described intent?"~1 minute, model call
3agent_e2eReal runtime observation: browser, computer-use, CLI run, API callminutes, sandbox
4humanSubjective judgment: playtest, aesthetic review, "feels right"hours, blocking on user

Baseline rules (always apply)

  • 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).

Add Gate 3 (agent_e2e) when sub-req involves:

  • Visible UI behavior (visible, rendered, displayed, shown, animation, screen shake, transition)
  • User interaction (click, tap, swipe, drag, hover, keyboard)
  • External system calls (fetch, request, API, database query, file IO where contents matter beyond schema)
  • Platform-specific behavior (mobile, desktop, browser tab, window)

Add Gate 4 (human) when sub-req involves:

  • Subjective quality (feel, good UX, intuitive, natural, fun, pleasant)
  • Statistical/behavioral metrics requiring real users (average retry rate, time-on-task, % of users who, sample size, playtest)
  • Judgement calls no model can ground (appropriate, reasonable, tasteful)

Step 4.1: Dispatch verify-planner agent

Pass:

  • Full requirements.md content (for GWT text)
  • Full journeys[] from Phase 3
  • The 4-gate rules above

Expected output:

{
  "verify_plan": [
    { "target": "R-T2.1", "type": "sub_req", "gates": [1, 2] },
    { "target": "R-U5.1", "type": "sub_req", "gates": [1, 2, 3] },
    { "target": "R-B3.1", "type": "sub_req", "gates": [1, 2, 4] },
    { "target": "J1",     "type": "journey", "gates": [1, 2, 3] }
  ],
  "ambiguities": []
}

Step 4.2: Self-check (by you)

  • 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:

  1. verify_plan contains any G4 target — the user has to commit real time (playtest, sampled metrics, aesthetic review)
  2. 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.

Step 4.4: Merge verify_plan

cat > /tmp/bp-verify.json << 'EOF'
{"verify_plan": [ ... ]}
EOF
hoyeon-cli plan merge <spec_dir> --append --json "$(cat /tmp/bp-verify.json)"

Phase 5: Commit

Step 5.1: Full validation

hoyeon-cli plan validate <spec_dir>

This runs schema validation AND these internal cross-ref checks:

  1. tasks[].fulfillsverify_plan sub_req targets
  2. journeys[].composesverify_plan sub_req targets
  3. Every journeys[].id has a verify_plan entry of type: journey
  4. Every verify_plan type: journey target matches a declared journey id
  5. tasks[].depends_ontasks[].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.

[blueprint] Plan complete.

Summary:
  Type:      greenfield
  Tasks:     11 (L0:2, L1:5 parallel, L2:3, L3:1)
  Journeys:  2
  Verify:    18 entries (G1:18, G2:18, G3:7, G4:1)
  Contracts: 5 interfaces, 3 invariants (contracts.md)

Next: /execute <spec_dir>/

Skip the summary entirely under --no-summary (used by /execute's inline call).

Step 5.3: Handoff

✅ Blueprint committed.
   plan.json     ← 11 tasks, 2 journeys, 18 verify entries
   contracts.md  ← 5 interfaces, 3 invariants

Next: /execute <spec_dir>/

Exit skill.


Shortened here. Read the whole file on GitHub.

Signals

GitHub stars
174
Forks
24
Last commit
May 2026
Hacker News mentions
20
Advanced
Catalog kind
skill
Gateway key
blueprint-team-attention
Source
github.com/team-attention/hoyeon