OpenSpec Spec Generator field notes
Home Spec Kit notes Kiro notes Skills notes
Spec-driven development · field notes

Spec-driven development: what it is, and what breaks when you actually run it

The short answer: spec-driven development (SDD) means you write down what the system should do, including the acceptance criteria, before the coding agent writes a single line. The agent then implements against that document, and you check the result against the document rather than against your memory of what you meant.

The honest answer: it works, and it fails, in ways you can predict in advance. On one ten day build of an enterprise admin console it produced 25,546 net lines through 2,754 tool calls with a reported 36% speed gain. On the same project, a single CI only build failure ate 4 hours, 7 sessions, 15+ attempted fixes and 59 instructions, because the failure lived in the CI environment and the agent could not see it.

The method is not the variable. Whether the machine you run it on is reproducible is the variable.

Works best when

  • the change spans several files and layers
  • dependencies are pinned and the lockfile is stable
  • local and CI behave the same way
  • someone will maintain the code after this week

Falls apart when

  • the bug only reproduces on a server you cannot log into
  • the fix depends on config nobody documented
  • the change is smaller than the spec you would write for it
  • the spec is updated and the code is not

What the workflow actually is

Four documents, in this order. Everything else is tooling detail.

  1. proposal.md answers: what is this and why now. If the agent skips the "why", it will quietly invent a goal for you.
  2. spec.md answers: what exactly must be true when this ships. Written as scenarios with a given, a when and a then, so the acceptance test is already half written.
  3. design.md answers: how it will be built, and what was rejected.
  4. tasks.md answers: what happens first, and what can run in parallel.

Two habits separate teams that get value from this and teams that generate paperwork. First, keep the three documents out of the chat window. A new session is a new context, so anything agreed in chat is gone; anything in a file survives. Second, commit immediately and often. The rollback ladder is short and worth memorising:

git restore .              # changes not staged yet
git restore --staged .     # staged but not committed, then run the line above
git reset --hard HEAD^     # already committed, back up one commit

One team that ran this for ten days recorded 738 file reads, 550 code edits, 662 terminal commands and 208 progress markers across that period, and kept 205 files consistent in naming purely because the convention lived in a rules file instead of in someone's head.

Four failure modes, with the exact text they produce

Roughly two thirds of the time lost to spec-driven tooling is lost here, before any spec is written. These are the failures people hit, in the order they hit them.

Failure 1

The command is installed and still will not run

Windows 11, Windows PowerShell, package installed globally with npm.

npm writes a .ps1 shim next to the executable. PowerShell refuses to run it under the default policy, and the error looks like the install failed:

openspec-cn : 无法加载文件 C:\Users\evan\AppData\Roaming\npm\openspec-cn.ps1,因为在此系统上禁止运行脚本。
有关详细信息,请参阅 https://go.microsoft.com/fwlink/?LinkID=135170 中的 about_Execution_Policies。
所在位置 行:1 字符:1
+ openspec-cn init
+ ~~~~~~~~~~~
    + CategoryInfo          : SecurityError: (:) [], PSSecurityException
    + FullyQualifiedErrorId : UnauthorizedAccess

The fix is scoped to your user, needs no admin rights, and is one line:

Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned -Force
openspec-cn --version

If that prints 1.4.1, the shim problem is gone. If Get-ExecutionPolicy -List shows MachinePolicy   Restricted, group policy outranks you: call the .cmd shim from cmd.exe, run from Git Bash, or invoke the entry point through npx or node.

Failure 2

Init runs, then refuses to continue

Same machine, immediately after failure 1 is fixed.

The first error is a Windows problem. The second one is not, and mixing them up is what makes people think the fix above did not work:

错误:未检测到工具且未提供 --tools 参数。有效工具:
amazon-q
antigravity
auggie
bob
claude
cline
codex
...

请使用 --tools all、--tools none 或 --tools claude,cursor,...

The tool will not guess which coding agent to generate slash commands for. Name it:

openspec-cn init --tools codex
正在创建 OpenSpec 结构...
OpenSpec 结构已创建

OpenSpec 设置完成

已创建:Codex
5 个技能和 5 个命令在 .codex/ 中
配置:已跳过 (非交互模式)

开始使用:
  开始您的第一个变更:/opsx:propose "您的想法"

重启您的 IDE 以使斜杠命令生效。

That last line is the one people miss. The commands were written to disk correctly and your editor has not reloaded them, so the slash commands appear to be missing until you restart it.

Failure 3

The package manager is broken, and so is ping

Windows, install attempted with pnpm.

This one presents as a pnpm problem and is not one. The tell is that an unrelated system command has also stopped working:

ERR_PNPM_NO_GLOBAL_BIN_DIR
Unable to find the global bin directory
Command failed with exit code 1: chcp

chcp lives in C:\Windows\system32. If pnpm cannot find it, that directory has fallen out of the system Path, which is why ping is dead too. Repair in this order:

  1. Add C:\Windows\system32, C:\Windows and C:\Windows\System32\Wbem back to the system Path.
  2. Create a user variable PNPM_HOME with the value C:\Users\<your name>\AppData\Local\pnpm, then add %PNPM_HOME% to the user Path.
  3. Open a brand new terminal. Run chcp. If it returns 活动代码页: 936, the environment is back.

Then reinstall. If the download itself dies with ERR_BAD_REQUEST, that is your proxy, not the package.

Failure 4

It builds locally and fails only on the server

Cloud CI runner. Takes four hours. Does not reproduce on a laptop.

This is the failure that spec-driven development does not protect you from, and it is worth understanding why. Each verification round required pushing code and waiting about ten minutes for CI, and the agent was analysing log screenshots rather than the live environment. Every single analysis was correct. Each one was correct about a different layer, because fixing one layer exposed the next.

Three causes were stacked:

  1. omit=optional in .npmrc. It was added earlier to stop @next/swc-darwin-arm64 from downloading on Linux. It also skipped @tailwindcss/oxide-linux-x64-gnu, the Tailwind v4 native binding, so postinstall waited for something that would never arrive.
  2. Prisma v6 engine download. No error, no timeout, just a wait. The behaviour is not documented; finding it required reading node_modules/@prisma/fetch-engine/dist/index.js line 2319.
  3. Cross platform lockfile. A lockfile generated on macOS arm64 does not contain the Linux x64 native packages, and switching to npm ignores the lockfile, so every install produced a different tree.

The fix that finally held, after 15+ attempts:

pin tailwindcss@4.1.11
PRISMA_ENGINES_MIRROR=https://registry.npmmirror.com/-/binary/prisma
standardise on pnpm, update the CI commands to match
delete omit=optional from .npmrc

Nothing about that fix is a specification problem. It is a reproducibility problem, and no amount of spec quality will compensate for it.

The failure matrix, as a file you can keep

Everything above, plus the cases that did not fit in prose, in one table. Symptom, the exact text, the environment, the cause, the fix, and when it was first written up.

Download the matrix (CSV)

The file itself lives at https://openspec-mvp.pages.dev/sdd-failure-matrix.csv (4.2 KB, 10 rows plus a header, served as text/csv). It opens in Excel, Numbers or any text editor. The print button strips the navigation and prints the tables and code blocks cleanly.

SymptomEnvironmentCauseFixWritten up
Installed, but every run dies at startup Windows 11, PowerShell, npm global .ps1 shim blocked by execution policy Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned -Force 2026-06-27
Init stops and lists agent names openspec-cn 1.4.1 target agent not specified openspec-cn init --tools codex 2026-06-27
Slash commands missing after init any editor, non-interactive init editor has not reloaded the new config restart the IDE 2026-06-27
Global install fails, ping also dead Windows, pnpm system32 missing from system Path restore three Path entries, set PNPM_HOME, verify with chcp 2026-03-16
Download dies midway behind a proxy proxy rejects the registry request fix the proxy configuration 2026-03-16
Green locally, red on CI, no error at all cloud CI runner omit=optional plus Prisma v6 engine fetch plus cross platform lockfile pin tailwindcss@4.1.11, set PRISMA_ENGINES_MIRROR, one package manager, drop omit=optional 2026-03-12
Code runs but ignores house style TypeScript admin console rules say what not to do, no reference implementation add a worked-example layer beside the rules layer 2026-03-12
Agent picks a deprecated API antd v5 model defaults to the most common API in training data list the deprecations in the rules file 2026-03-12
Terminal shows mojibake Windows PowerShell console code page mismatch, file is fine Get-Content .\log.md -Raw -Encoding UTF8 2026-06-27
Execution policy will not change domain joined Windows group policy outranks the user scope use the .cmd shim, another shell, or npx 2026-06-27

The numbers, by date and by who reported them

Every figure below is claimed by a specific write-up on a specific date. None of them is a benchmark, and none of them transfers automatically to your project. Read them as one team's logbook.

DateSourceWhat they claimed
2026-03-12 Post-mortem of a ten day, from scratch build of an enterprise admin console, run with an agent under a three layer rules setup 25,546 net lines added; 2,754 tool calls; 738 reads, 550 edits, 662 terminal commands, 208 progress markers; reported 36% gain. Phase split: 2 days and 20 instructions to scaffold, 4 days and 89 instructions once the spec workflow was introduced, 4 days and 108 instructions for hardening and deploy. Naming held across 205 files.
2026-03-12 Same post-mortem, troubleshooting section One CI only build failure: 4 hours, 7 sessions, 15+ attempted fixes, 59 instructions, about ten minutes per verification round. Root causes: omit=optional, Prisma v6 silent engine download, macOS arm64 lockfile on a Linux x64 runner. Fix: pin tailwindcss@4.1.11, set PRISMA_ENGINES_MIRROR, standardise on pnpm, remove omit=optional.
2026-03-12 Same post-mortem, refactor case 9 groups and 34 subtasks ran to completion with under 5 human instructions. A hook exporting 20+ methods was split into three single purpose hooks, and a component taking 17 props came down to 6 to 8.
2026-03-16 Windows setup write-up for the same tooling Two blockers on Windows: the PowerShell script policy, and ERR_PNPM_NO_GLOBAL_BIN_DIR caused by system32 leaving the Path. Verify with chcp returning 活动代码页: 936.
2026-05-16 Walkthrough running the workflow on Windows against a non default model Floor is Node.js 20.19.0. Model configured through ANTHROPIC_BASE_URL with API_TIMEOUT_MS set to 300000. Recommends stepping through /opsx:new then /opsx:continue instead of generating all artifacts in one shot, so each one can be reviewed.
2026-06-27 Windows 11 troubleshooting post for the Chinese language build openspec-cn --version reported 1.4.1. Two errors in sequence: PSSecurityException then "no tool detected, pass --tools". Successful init wrote 5 skills and 5 commands into .codex/.

Who should not do this

This is the part most write-ups leave out. Spec-driven development has a real setup cost, and below a certain change size that cost is larger than the thing you are changing.

  • Changes smaller than the spec. Copy edits, a visibility toggle, CSS spacing. Saying it in chat costs less than writing it down, and the agent's instant feedback is worth more than a document.
  • Code you will delete this week. Throwaway scripts, one off demos, a spike to see if an idea is viable. Write it, run it, throw it away.
  • Teams without a reproducible environment. If CI and your laptop disagree, the spec will be correct and the build will still fail. Fix the environment first, or the method will take the blame for something it did not cause.
  • Anyone who cannot pin dependencies. A floating version range plus an agent that regenerates code means the tree changes under you between runs. Pin first.
  • Exploratory work with no stable requirement. If you do not yet know what you want, a spec freezes a guess. Go investigate, then write the spec.
  • Locked down machines where you cannot change shell policy. You can work around it, but every workaround is friction on every run, and friction is where habits die.
  • Parallel agents on parallel branches without a dependency graph. Two agents editing the same spec area will conflict, and the natural failure mode is a deadlock where each waits on the other. Run them serially on one branch, or draw the graph first.

The one rule that decides whether it works

Specs rot faster than code, because code fails loudly and a stale spec fails silently. The failure mode to watch is this: the spec says errors return {"code": "USER_NOT_FOUND", "message": "..."}, two weeks later the spec adds trace_id, and the module written in week one is never updated. Nothing breaks. The next agent reads the new spec and writes code that disagrees with the old module.

The cheap defence is mechanical rather than disciplined: keep a list of dependent modules in each spec file, and add one CI step that runs the tests of any module whose spec file changed. If you cannot do that, at least make the spec the thing the agent reads at the start of every session, which is what makes it worth maintaining at all.

If you want the short version of the whole method: write the four documents, keep them in git next to the code, pin your dependencies, and treat any build that passes locally and fails on the server as an environment bug until proven otherwise.

Generate your first spec scaffold