Skip to content

Design: write spec.md

The design stage turns an accepted intent.md into a committed spec.md in one agent session. The agent writes requirements, acceptance criteria, and architecture while versioned policy skills for security, brand, UX, and architecture constrain it, and it lists every policy conflict under Flagged concerns. A named human accepts the spec before any implementation plan starts.

This page is for the developer or product owner who runs that session and the tech lead who owns the gate. A product owner accepts an intent for comment notifications on Monday. By Thursday the pull request stores email addresses in a new table, ignores the retention policy, and ships a settings screen that breaks the design system. Every one of those rules was written down somewhere, and none of them was applied until review.

Traditional: analysts write requirements, architects and designers translate them into a design, and security and compliance see the result weeks later. Each handoff costs days and loses part of the original intent.

AI-native: one session reads intent.md, applies the organization’s policies as skills while it writes, and puts every conflict in front of its owner before engineering sees the spec.

What you get from a policy-constrained design stage

Section titled “What you get from a policy-constrained design stage”
  • A spec.md with a testable acceptance criterion for each requirement, the list of skills that shaped it, and a Flagged concerns section with an owner per concern.
  • A policy skill you can copy, and the places Claude Code, Codex, and Cursor load it from.
  • Three copy-paste prompts: write the spec, attack it with a fresh session, and turn a flagged concern into a decision.
  • A shell check that fails when a spec skips a required section or a requirement has no criterion, so nobody has to proofread the structure.
  • The recovery for each way this stage usually breaks.
  • An intent.md that the product owner has accepted. Plan: capture intent.md produces it.
  • At least one policy written as an agent skill. Start with the policy that most often sends pull requests back, usually security or the design system.
  • The spec.md template from the artifact chain. This page uses its sections and its paths: intent/FEATURE/intent.md and intent/FEATURE/spec.md.
  • A named owner for each policy. A flagged concern with nobody to decide it stalls the change.

The session needs no engineering skill to run. A product owner can drive it, provided an engineer or tech lead accepts the architecture section on higher-risk work.

What does a policy skill for spec writing look like?

Section titled “What does a policy skill for spec writing look like?”

A policy skill is a folder with a SKILL.md file. Its description tells the agent when to load it, so write it around the task (“writing or reviewing a spec”) and not around the topic. The Agent Skills format limits name to 64 characters and description to 1,024 (agentskills.io specification, checked 2026-09-26).

---
name: security-policy
description: Apply when writing or reviewing a spec, plan, or change that touches authentication, personal data, payments, file uploads, or input from outside the service.
---
# Security policy for specs
When you write a spec, check each requirement against these rules and name
the rule you applied in "Skills and policies applied".
1. Personal data (email, name, IP address) is stored only in tables listed
in docs/data-inventory.md. A new table holding personal data is a
flagged concern for the data protection owner.
2. Every new endpoint states its authentication and authorization rule.
3. Retention: user-generated content and delivery logs are deleted after
90 days unless the spec names a legal basis for longer.
4. Never resolve a conflict between this policy and another one yourself.
Record it under "Flagged concerns" with both rules quoted.
Owner: security-team. Changes to this file need their review.

Write one skill per policy owner: security, brand and UX (design tokens, components, accessibility level), and architecture (API conventions, which service owns which data). One owner per file keeps each policy changeable through its owner’s review, and CODEOWNERS can enforce that.

Where each tool loads project skills:

ToolProject skill folderNotes
Claude Code.claude/skills/NAME/SKILL.mdAlso loaded from parent directories up to the repository root (Claude Code docs, v2.1.283)
Codex.agents/skills/NAME/SKILL.mdMention a skill by name with $NAME to make sure it loads (codex-cli 0.157.1)
Cursor.agents/skills/NAME/SKILL.mdThe folder that npx skills add writes for Cursor (skills CLI 1.7.0)

Keep one source copy and copy or symlink it into the other folder, so the three tools apply the same policy version. Building custom skills covers testing and publishing them.

  1. Start a read-only session with the policy skills available.

    Use plan mode, so the agent can read the codebase but cannot change application code. The tabs below give the command for each tool.

  2. Run the spec prompt against the accepted intent.

    The first instruction is a cheap check: if the agent lists no policy skills, stop. A spec written without them is written from general knowledge, which is the failure this stage exists to prevent.

  3. Attack the draft with a fresh session.

    A new session has not seen the reasoning that produced the draft, so it reads the spec the way the build stage will. Run it before any human spends time on the spec.

    Fix blocking findings in spec.md itself, not in the chat. A decision that lives only in a conversation is gone when the session ends.

  4. Resolve flagged concerns with their owners.

    These are the points an analyst would once have escalated. Send each one to its policy owner as a decision to make, not a paragraph to interpret.

    Record each decision in the Flagged concerns section with the owner’s name and the date. A concern nobody can decide yet stays as an open question with a date by which it must be decided.

  5. Commit spec.md next to intent.md.

    Terminal window
    git add intent/notifications/spec.md
    git commit -m "docs(spec): notifications spec from accepted intent"

    The pair records what was asked for and what was decided. The spec’s header names the intent it came from and the date it was accepted.

  6. Accept the spec, or send it back.

    The product owner accepts the requirements. A tech lead or architect also accepts the architecture section when the change touches a higher-risk class: authentication, money, personal data, migrations, or a shared interface. Acceptance starts the build stage, which begins with plan.md in plan mode.

How do you run the design session in Claude Code, Codex, and Cursor?

Section titled “How do you run the design session in Claude Code, Codex, and Cursor?”

The prompts are the same in all three tools. What differs is how you keep the session read-only and where project skills live.

  • Enter plan mode with Shift+Tab, /plan, or claude --permission-mode plan. From v2.1.283 (the latest channel), interactive sessions start in auto mode on supported models, so switch to plan mode deliberately.
  • Plan mode blocks every edit, including spec.md. When the draft is ready, choose Yes, manually approve edits and approve only the write to spec.md.
  • Project skills live in .claude/skills/. The bundled /design skill (research preview) is a different feature; this stage needs only your own policy skills.

For UI work, the spec also needs visual evidence. Produce a mock from intent.md with an approved design tool or the Figma MCP server, record who accepted it, and link it from the spec. The four-stage design pipeline covers the design artifacts, visual acceptance before frontend implementation covers accepting the mock, and Figma MCP and design-to-code covers the connection. On Starter plans and on View and Collab seats, Figma limits MCP to a handful of tool calls a month (up to 6, ecosystem dossier, 2026-09-26), so run the mock step from a Dev or Full seat. The remote server is https://mcp.figma.com/mcp:

Terminal window
# Claude Code (then run /mcp in a session to log in)
claude mcp add --transport http figma https://mcp.figma.com/mcp
# Codex (codex-cli 0.157.1)
codex mcp add figma --url https://mcp.figma.com/mcp
codex mcp login figma

Run the design pass automatically when intent is accepted

Section titled “Run the design pass automatically when intent is accepted”

Run the prompt by hand until its output is consistently good. Then encode the spec prompt as a skill (for example write-spec) and make the merge of an accepted intent.md the trigger: a CI job runs the pass headless and opens spec.md as a pull request. The product owner’s first involvement is then the review.

Terminal window
# Runs in CI after intent/$FEATURE/intent.md is merged.
# FEATURE comes from the job input: the folder of the merged intent.md.
FEATURE=notifications
# Claude Code: headless, edits accepted, no interactive prompts
claude -p "Use the write-spec skill on intent/$FEATURE/intent.md." \
--permission-mode acceptEdits
# or Codex: headless, sandbox limited to the workspace
codex exec -s workspace-write "Use the write-spec skill on intent/$FEATURE/intent.md."
# Run the shape check below with SPEC_JOB=1, then open the pull request.
git switch -c "spec/$FEATURE"
git add "intent/$FEATURE/spec.md"
git commit -m "docs(spec): $FEATURE spec from accepted intent"
git push -u origin "spec/$FEATURE"
gh pr create --fill

Cursor can run the same pass from its CLI’s print mode or from a Cloud Agent Automation with a source-control trigger. Check the current CLI command name in Cursor’s own docs before you script it.

In GitHub Actions the workflow needs only permissions: contents: write and pull-requests: write, plus the agent’s API key from a repository secret. Headless runs cannot ask you for permission, so the permission mode or sandbox limits what the job can touch. The check in the next section is what proves the job touched only the spec.

How do you verify a spec without reading every line?

Section titled “How do you verify a spec without reading every line?”

Nobody should proofread a spec for structure. Split the checking three ways: a script checks the shape, a fresh agent session checks the substance against the policies, and humans decide only the flagged concerns and the final acceptance.

The shape check. Run this in CI on every pull request that adds or changes a spec.md, and with SPEC_JOB=1 in the headless job right after the agent finishes. In the job, FEATURE comes from the job input. On a pull request the working tree is clean, so the script compares the branch with origin/main and checks every spec.md the branch changes:

Terminal window
# Headless job (SPEC_JOB=1): FEATURE comes from the job input, and the
# agent's changes are still uncommitted (tracked edits plus new files).
# Pull request: check every spec.md the branch changes (fetch origin/main first).
if [ -n "${SPEC_JOB:-}" ]; then
specs="intent/$FEATURE/spec.md"
changed=$(git diff --name-only HEAD; git ls-files --others --exclude-standard)
else
changed=$(git diff --name-only origin/main...HEAD)
specs=$(printf '%s\n' "$changed" | grep -E '^intent/[^/]+/spec\.md$')
fi
[ -n "$specs" ] || { echo "No spec.md in this change"; exit 1; }
# The design pass may change only specs.
if printf '%s\n' "$changed" | grep -v '^$' | grep -vqxF "$specs"; then
echo "The change touches files other than spec.md"; exit 1
fi
printf '%s\n' "$specs" | while IFS= read -r SPEC; do
test -s "$SPEC" || { echo "No $SPEC written"; exit 1; }
for section in '## Requirements and acceptance criteria' '## Architecture and design' \
'## Out of scope' '## Skills and policies applied' '## Flagged concerns'; do
grep -qx "$section" "$SPEC" || { echo "$SPEC: missing section: $section"; exit 1; }
done
# At least one requirement, and every requirement has a checkable criterion.
grep -qE '^- R[0-9]+:' "$SPEC" || { echo "No requirements in $SPEC"; exit 1; }
if grep -E '^- R[0-9]+:' "$SPEC" | grep -v 'Accepted when:'; then
echo "$SPEC: requirements above have no acceptance criterion"; exit 1
fi
done || exit 1

The substance check. The fresh-session review from step 3 runs on the same pull request. Attach its table to the pull request, and let it block on any finding marked blocking.

The human gates. Each policy owner signs off the concerns in their area, and the product owner accepts the spec. On higher-risk classes a tech lead also accepts the architecture section. Nobody accepts a spec whose Flagged concerns section still holds an item without an owner.

The later check. A skill is advisory: the agent can still ignore a rule. A policy that must always hold also needs a deterministic check later, as a hook in the build stage or a gate before deploy. Each acceptance criterion also becomes a test, which executable acceptance criteria walks through.

Two indicators show whether the stage is working, both defined in Metrics for an AI-native SDLC:

  • Leading: time from the intent.md commit to the accepted spec.md commit for the same change. Set a target for it (for example, same working day) and watch the trend.
  • Lagging: requirements rework after build starts, counted as spec.md commits dated after the first plan.md commit for the same change. Missing architectural decisions that engineers discover during the build show up here.

What goes wrong when agents write specs, and how to recover

Section titled “What goes wrong when agents write specs, and how to recover”

The spec is written from general knowledge. The agent listed no policy skills, or the “Skills and policies applied” section is empty. Check that the skills sit in the folder your tool reads and that each description mentions writing specs. Then rerun the spec prompt in a new session.

The agent resolves a trade-off silently. The spec picks a new table, a new dependency, or a weaker retention rule, and Flagged concerns is empty. The fresh-session review catches most of these (its item 4). Reject the spec and tell the agent to record the conflict instead of deciding it.

Code appears during the design stage. The agent scaffolds a migration or writes tests “to help”. The shape check fails the job. Delete the extra files, restate the “do not edit any file except spec.md” line, and run the session in plan mode or a read-only sandbox.

Claude Code cannot save spec.md in plan mode. Plan mode blocks all edits. Choose Yes, manually approve edits, approve the write to spec.md, and reject any other edit.

The spec turns into a plan. It lists files to change and tasks in order. Move that material out: the spec states required behaviour, and plan.md in the build stage decides how. A spec that prescribes tasks locks in choices before anyone has read the code.

Acceptance criteria cannot be tested. “Notifications feel fast” passes a human read and fails every test. The shape check only proves a criterion exists. The fresh-session review (its item 2) flags the vague ones, and each should become a number or an observable event.

A concern has no owner. It sits in the spec, the change stalls, and someone eventually deletes it. Name the owner in the policy skill itself (the Owner: line), so the agent can route the concern when it flags it.

Policies contradict each other every time. The same brand and accessibility conflict appears in every spec. That is a policy bug, not a spec bug. Send it to both owners once, change one of the skills, and the concern stops recurring.