OpenSpec: The Markdown Spec Layer That Makes AI-Generated Code Safe to Ship
AI-made changes too risky to ship? OpenSpec spec-driven development in practice
I recently read an article about "Spec programming + visual Skills / code maps". The core point struck a chord: when faced with a legacy codebase of hundreds of thousands of lines, both humans and AI lack an overview map, so nobody dares to review, let alone ship, AI-modified code. The article's answer is "use skills to generate an interactive code map".
My addition is: the map solves the "seeing" part, but the "describing clearly and verifying" part is still missing. Seeing the structure is not the same as agreeing on what counts as "done". That step requires specs. This article discusses an open-source tool that does this lightly: OpenSpec.
1. The problem isn't that AI writes poorly; it's that requirements never land
Let me lay out three typical scenarios:
- Requirements live only in chat logs. You say three sentences to AI, it changes eight files. Three days later, when you need to recall "why was this designed that way", you dig through conversation records, but those were compressed, cleared, or lost when switching sessions.
- AI output cannot be accepted. Without pre-agreed acceptance criteria, review degenerates into "it doesn't error, so it's fine". After the change, you have to read an implementation you didn't write, which is more tiring than writing it yourself, especially in an existing legacy codebase.
- The system has no single source of truth. When a newcomer asks "how many columns does this list show by default, and under what conditions does it remember the user's choice?", the answer is scattered across code, a Wiki page, some group chat, and the memory of a departed colleague.
These three together constitute spec drift: code runs forward while the shared understanding of the system stays still.
Spec-Driven Development (SDD) has a simple idea: write "what behavior the system should exhibit" as structured, reviewable, Git-committed Markdown, so humans and AI align before the first line of code is written. The spec is the contract; code is just one implementation of it.
There is a division of labor often overlooked, which matches the "code map" article I mentioned:
| Layer | Question Answered | Typical Carrier |
|---|---|---|
| Skills / Workflows | What steps does this task follow | skill packs, slash commands |
| Code map / overview | Where is the code, how do the links flow, how big is the impact | interactive HTML, flowcharts, sequence diagrams |
| Spec contracts | What counts as done, and how to verify it | openspec/specs/, changes/ |
The three complement each other; they do not replace each other. Maps and skills make AI "able to work"; specs allow humans "to make the call".
2. What is OpenSpec
OpenSpec is a lightweight specification framework open-sourced by Fission-AI (MIT license), adding a "spec layer" to AI coding assistants. Its design philosophy, as stated in the README, largely sets it apart from similar tools:
→ fluid not rigid 流动,而非僵化
→ iterative not waterfall 迭代,而非瀑布
→ easy not complex 简单,而非复杂
→ built for brownfield 为存量工程而生,不只服务新项目
→ scalable from personal projects to enterprises 个人到团队都能用
Key characteristics:
- Tool-independent: supports 30+ AI coding tools (Claude Code, Cursor, CodeBuddy, Codex, Copilot, Trae, Qoder, Cline, etc.). Essentially it writes skills / commands /
AGENTS.mdinto the project, not bound to any IDE or model. - Pure Markdown: no private syntax, no database, no server. Everything lives in Git, so it's naturally diffable and reviewable.
- Brownfield-focused: a dual-directory model —
specs/describes the system's current behavior (single source of truth),changes/holds in-progress change proposals. Old projects can gradually bake behaviors into specs without needing to document everything at once. - No phase gates: any artifact can be revised at any time; there's no requirement to "freeze requirements before starting".
3. Up and running in five minutes
3.1 Installation and initialization
The official installation methods are varied (refer to the README for the latest):
# macOS / Linux: standalone binary
curl -fsSL https://openspec.dev/cli/install.sh | sh
# Windows PowerShell
irm https://openspec.dev/cli/install.ps1 | iex
# Or via package manager (npm requires Node.js 22.12.0+)
npm install -g @fission-ai/openspec@latest
brew install openspec
Inside your project, initialize and choose your AI tool:
cd your-project
openspec init # interactive tool selection
openspec init --tools claude,cursor # non-interactive, specify directly
openspec init --tools none # only the openspec/ structure, no tool files
After initialization, the project gains these files:
your-project/
├── openspec/
│ ├── config.yaml # project-level config: schema, context, rules
│ ├── specs/ # single source of truth: current system behavior, organized by capability
│ │ └── <capability>/spec.md
│ └── changes/ # in-progress changes, one folder per requirement
│ └── <change-name>/
│ ├── .openspec.yaml
│ ├── proposal.md
│ ├── specs/ # delta spec
│ ├── design.md
│ └── tasks.md
├── .claude/skills/ # or .cursor/, .codebuddy/, etc., generated per chosen tool
└── AGENTS.md # entry point for AI
Tip: After installing, restart IDE / session; slash commands are loaded at startup. Command syntax varies by tool: for Claude Code, CodeBuddy it's
/opsx:apply, for Cursor, Copilot it's/opsx-apply, for Amazon Q it's@opsx-apply, and for Codex it's the skill name$openspec-apply-change.openspec initprints the correct syntax for your chosen tool; don't copy someone else's.
The default profile is core, containing explore / propose / apply / archive. For finer-grained workflows (/opsx:new, continue, ff, verify, bulk-archive, onboard), run:
openspec config profile # interactive selection
openspec update # make new workflows effective
3.2 A five-step loop
Every OpenSpec change follows the same loop:
1 Explore → 2 Propose → 3 Review → 4 Apply → 5 Archive
想清楚 AI 出计划 人改计划 AI 按任务实现 规约吸收变更
↑ │
└──────────────────── 下一个 change ←────────────────────┘
① Explore — think first, don't rush to write
/opsx:explore 帮我梳理一下「表格列偏好」在这个后台系统里应该怎么设计
This is a "think only, don't implement" mode: AI reads code, offers options, questions your assumptions, and writes no code and no artifact files. For legacy codebases of hundreds of thousands of lines, this step is especially necessary — letting AI lay out the impact surface first is safer than having it start coding right away. Once you've thought it through, saying "write up the proposal we just discussed" carries the context into Propose.
② Propose — produce a reviewable plan
/opsx:propose add-column-preferences
AI creates openspec/changes/add-column-preferences/ with its four artifacts, but stop there — no business code yet. This is OpenSpec's most important gate: before code exists, the plan must be seen by a human.
③ Review — you're editing text, not code
Following the officially recommended order:
proposal.md: is the problem correct? Is the scope too broad?specs/: the highest-value file. Can you accept these requirements as the definition of "done"?tasks.md: do the tasks cover all specs, and does it contain any extra stuff?
To change something, directly edit the Markdown, or tell AI "the spec is missing the not-logged-in scenario, add it". At this point the cost of change is a few minutes; once code is spread out, it becomes hours.
④ Apply — execute per the checklist
/opsx:apply add-column-preferences
The official advice is practical: open a new session (a clean context window leads to higher-quality implementation), and track progress only via the checkboxes in tasks.md. If you get interrupted or the context blows up, open a new session and apply again; AI resumes from the first unchecked task, with no hidden state. If you find the plan is wrong mid-way, go back and edit the artifacts, then continue applying — no need to start over.
⑤ Archive — bring the spec up to date with the code
/opsx:archive add-column-preferences
Archiving does two things: merge the delta spec into openspec/specs/, and move the entire change folder to openspec/changes/archive/ for audit history. The merge semantics are:
ADDED → append to the main spec
MODIFIED → replace the same-named Requirement in the main spec (entire block replacement)
REMOVED → delete from the main spec
RENAMED → only rename, with FROM:/TO:
At this point, openspec/specs/ is once again the single source of truth for the system's current behavior, and the next change starts from it. This is the value of the whole loop: the spec doesn't rot over time.
4. The four artifacts and their responsibilities
The default schema is called spec-driven, and the dependency order of the four artifacts is:
┌─ specs ──┐
proposal ────┤ ├── tasks ── apply
└─ design ─┘
| Artifact | Answers | Key Constraint |
|---|---|---|
proposal.md |
Why do it, what changes, impact surface | Only why/what; implementation details go to design |
specs/<capability>/spec.md |
What counts as "done" | Behavioral contract; no class names, frameworks, or steps |
design.md |
How to build it technically | Technical decisions, trade-offs, risks; may be omitted if not triggered |
tasks.md |
How many steps to finish | Checkboxes - [ ]; progress is state |
proposal.md template:
# Proposal
## Why
<!-- 1-2 句:解决什么问题,为什么现在做 -->
## What Changes
<!-- 具体列出新增/修改/移除,破坏性变更标 **BREAKING** -->
## Capabilities
### New Capabilities
- `<capability-path>`: <这个能力覆盖什么>
### Modified Capabilities
- `<existing-capability-path>`: <哪条需求要变>
## Impact
<!-- 受影响的代码、API、依赖、系统 -->
The biggest trap here is Capabilities naming: name it after a persistent system behavior (like user-auth, column-preferences), not after this dev task (like add-login-button). The official advice is to first run openspec list --specs to inventory existing capabilities, reuse exact paths, and avoid creating near-synonyms that duplicate capabilities. Pure refactors, documentation, and tooling changes with no behavioral change should set skip_specs: true in .openspec.yaml; don't force a requirement just to pass validation.
Delta spec: the soul of the framework
Specs are not rewritten in full each time; they describe increments relative to the current spec. The standard format:
# Spec Delta
## Purpose
<!-- 仅新建 capability 需要,50+ 字符说明这个能力是干什么的 -->
## ADDED Requirements
### Requirement: 保存列偏好
系统 SHALL 在用户调整列显示后持久化该选择,并在下次进入同一列表时恢复。
#### Scenario: 用户勾选新列后重新进入列表
- **WHEN** 用户在「订单列表」勾选「下单时间」列并离开页面
- **THEN** 再次进入「订单列表」时该列保持可见
#### Scenario: 未登录用户
- **WHEN** 未登录用户调整列显示
- **THEN** 选择仅在当前会话生效,且不发起任何写接口请求
Several hard rules; getting them wrong causes silent failures or lost information during archiving:
- Scenarios must use exactly 4
#(#### Scenario:); writing###won't be recognized correctly; - Each Requirement must have at least one Scenario, and each Scenario must have WHEN/GIVEN and THEN;
- Use
SHALL/MUSTfor normative requirements; don't write "should" or "can"; - Four operations supported:
## ADDED / MODIFIED / REMOVED / RENAMED Requirements; - MODIFIED must include the full old Requirement block (all Scenarios) before making changes; sending only fragments loses the original details during archiving. If the old behavior stays the same and you're adding a new concern, use ADDED rather than MODIFIED;
- REMOVED must specify Reason and Migration;
- The delta file must be named
specs/<capability-path>/spec.md; writingspecs/xxx.mdfails validation and archiving.
design.md and tasks.md templates are restrained:
# Design
## Context
## Goals / Non-Goals
**Goals:**
**Non-Goals:**
## Decisions
## Risks / Trade-offs
# Tasks
## 1. <阶段名>
- [ ] 1.1 <具体任务>
- [ ] 1.2 <具体任务>
## 2. <阶段名>
- [ ] 2.1 <具体任务>
A complete small example
Requirement: the admin order list allows users to customize columns and remembers their choices.
openspec/changes/add-column-preferences/
├── .openspec.yaml
├── proposal.md Why: 用户每次进列表都要重勾列
│ Capabilities: New = column-preferences
│ Modified = list-view(默认列需求变更)
├── specs/
│ ├── column-preferences/spec.md ADDED: 保存列偏好、未登录降级
│ └── list-view/spec.md MODIFIED: 默认列展示规则
│ REMOVED: 旧的「固定 8 列」要求 + Reason/Migration
├── design.md localStorage 优先 + 用户配置接口兜底;评估 SSR 首屏闪动
└── tasks.md 1. 数据层 / 2. UI 层 / 3. 测试(含未登录、跨页面恢复)
The original requirement "list always shows 8 columns" in openspec/specs/list-view/spec.md is MODIFIED in this change to "show according to the user's saved preferences, falling back to the default 8 columns when there is no preference". After archiving, the main spec updates automatically, and the next person who picks this up sees the new rule, not a logged conversation.
5. CLI quick reference
Agents call these themselves during the workflow; humans only need to know the common ones:
| Command | Purpose |
|---|---|
openspec init [--tools <ids>] |
Initialize project and tool integration |
openspec update [--force] [--dry-run] |
Refresh AI instruction files; must run after upgrading CLI |
openspec list / openspec list --specs |
List in-progress changes / list existing capabilities |
openspec show <id> --type spec [--json] |
View a change or spec in full |
openspec status --change <name> --json |
View completion status of the four artifacts |
openspec instructions <artifact> --change <name> --json |
Get generation instructions for an artifact (mostly used by agents) |
openspec validate [name] --all --strict |
Validate structure and format; --strict treats warnings as errors |
openspec archive <name> --yes |
Merge delta and archive |
openspec view |
View specs and changes overview in terminal/page |
openspec config profile / config list |
Switch workflow sets, view global config |
openspec schemas / openspec schema fork spec-driven my-workflow |
View / fork custom schema |
openspec store / doctor / context / workset |
Multi-repo spec store (beta) related |
openspec version / install / completion |
Version, standalone binary install, completion |
Always add --json for programmatic calls. Also, openspec change / openspec spec are old nominal commands, deprecated; don't use them in new scripts.
6. Team adoption: from "personal habit" to "engineering constraint"
For a single user, OpenSpec mainly solves "alignment with AI". At the team level, the value shifts to collaboration and knowledge transfer. These are practical practices:
- Commit the code and the change folder together in the same PR. Reviewers read
proposal.mdandspecs/first, then the diff. The review object changes from "are these 800 lines of code correct" to "is the requirement definition correct + does the implementation cover it", cutting cognitive load by an order of magnitude. - Run
openspec validate --strictin CI. Problems like miswritten scenario levels, missing scenarios, or overly long descriptions are caught before merging. - Use
openspec/config.yamlto codify team conventions. This is the lightest customization layer, injecting tech stack, architecture constraints, and test requirements into every workflow, e.g., "tasks must include a test task" or "proposal must state a rollback plan":
schema: spec-driven
context: |
技术栈:TypeScript + Vue 3 + Vite
测试:Vitest + Playwright
rules:
tasks:
- 每个 change 的 tasks 必须包含对应的单测/E2E 任务
proposal:
- 涉及接口变更时必须写明兼容与回滚方案
- Fork the schema when conventions aren't enough.
config.yamlcan only add on top of the standard process; it can't change artifact structure or file names. If you want to dropdesignor add custom sections, useopenspec schema fork spec-driven my-workflowto derive your own schema and templates. - Use Stores (beta) for cross-repo requirements. When a feature spans API, Web, and component-library repos, put
openspec/in a dedicated spec repo that each code repo reads from, avoiding "the platform team writes it once in Wiki, and each of the three repos interprets it differently". - New session + high-reasoning model. The official recommendation is to do implementation in a clean context window and to prefer models with strong reasoning for planning and implementation. Context hygiene has a big impact on the framework's effectiveness.
- Decide whether to keep the archive after archiving.
openspec/changes/archive/keeps the full proposal / design / tasks / delta. If Git is your spec history, commit the archive first, then delete thespecs/subdirectory to reduce search noise;openspec validate --archivedstill works.
7. Choosing among similar solutions
| Solution | Strengths | Costs |
|---|---|---|
| No spec (pure prompt) | Zero cost, fastest start | Requirement drift, unpredictable results, unreviewable |
| GitHub Spec Kit | Complete process, clear phases | Heavy phase gates, many Markdown artifacts, Python dependency |
| Kiro | Complete in-IDE experience | Tied to their IDE and model |
| OpenSpec | Light, fluid, tool/model agnostic, built for brownfield | Relies on self-discipline and process constraints; doesn't force "write spec before code" |
This is the comparison from OpenSpec's official README, and I largely agree: it doesn't try to be the most rigorous one; it tries to be the one that's easiest to stick with in a real old project. The biggest failure mode of spec frameworks is never being too weak; it's the team giving up after two weeks because it's too annoying.
And to return to the "code map" article mentioned at the start: the two can be joined into a pipeline:
代码地图 / Skills → 摸清工程结构与影响面
↓
proposal.md → 把影响面写进 Impact,划定能力边界
↓
delta spec → 定义「改成什么样算完成」与验收场景
↓
tasks.md + apply → 按清单施工,代码地图复核改动范围是否越界
↓
archive → 行为共识回写主 spec,成为下一轮的起点
The map tells AI "where to touch"; the spec tells humans "how to know it's right". The former is visualization, the latter is contract; without either half, you can't say "safe to ship".
8. Pitfall checklist
- Writing
#### Scenarioas###:validatesilently doesn't recognize it, and after archiving the main spec lacks scenarios. - Writing spec as implementation plan: if you see class names, framework names, or step numbers, they belong in
design.md/tasks.md. The test: if the implementation can be swapped out but the externally visible behavior stays the same, it doesn't belong in the spec. - Naming capability after the task:
add-login-buttonbecomes useless after two uses; it should beuser-auth, a behavioral boundary that can own requirements long-term. - A change with zero delta gets rejected by validate: for pure refactors/documentation, set
skip_specs: true; don't force a requirement. - MODIFIED with only fragments: archiving does whole-block replacement and will lose historical scenarios. Copy the complete Requirement block before editing.
- Forgetting
openspec update: after upgrading the CLI, the skills/commands in the tool directory are still old; symptom is "new commands have no response". - Copying commands across tools:
/opsx:apply,/opsx-apply,@opsx-apply,$openspec-apply-changeare the same workflow spelled differently in different tools. - Not clearing context during apply: carrying a bunch of exploration dialogue into implementation visibly lowers quality. Open a new session.
- Archiving too late or too early: if tasks aren't all checked before archiving, the spec becomes "fact" before the code; if a change goes unarchived for long,
specs/stops being the source of truth. Commit code and the change folder together, and archive as soon as the merge is done.
9. Summary
AI coding has driven down the cost of "writing code", so the bottleneck shifts to "alignment" and "acceptance". OpenSpec doesn't invent a new language or a platform; it simply uses a few Markdown files plus a five-step loop to turn "requirements → plan → implementation → archive" back into a reviewable, traceable, diffable engineering chain.
For an existing legacy codebase of hundreds of thousands of lines, the benefit is especially real: no need to start over; you can begin with the next requirement and bake in one capability at a time. After running three to five changes, openspec/specs/ becomes the most valuable document for a new hire to read — and it's always synchronized with the code.
References
- OpenSpec GitHub: https://github.com/Fission-AI/OpenSpec
- OpenSpec official docs: https://openspec.dev/docs
- Quickstart: https://openspec.dev/docs/quickstart
- spec-driven schema: https://openspec.dev/docs/schemas/spec-driven
- CLI reference: https://openspec.dev/docs/cli
- Inspiration: WeChat public account 「bugstack 虫洞栈」《Spec 规约编程,可视化 Skills 技能》 https://mp.weixin.qq.com/s/XPgeCPpaMKaCSToQt-1nIw
This article is based on official documentation from November 2026; please refer to the latest version of the project README / docs for command and artifact formats.
Top 1 from juejin.cn, machine-translated. The original thread is authoritative.
We've already adopted this, and it's a long, cumbersome mess.