GitHub Spec Kit 实战(四):读懂和干预 /speckit.plan——AI 最自由发挥的一步

AI 时代程序员必备技能

Codex、Claude Code、Cursor、Hermes Agent、OpenClaw等工程化实战专栏 ,讲透 AI 如何接管脏活累活

GitHub Spec Kit 实战(四):读懂和干预 /speckit.plan——AI 最自由发挥的一步

前两篇讲了 specify 怎么写不偏、constitution 怎么定规矩。/speckit.plan 这一步,AI 最容易"自由发挥",也是人最该把关的地方。

为什么?

  • specify 阶段有"不许写技术栈"的强约束,AI 戴着手铐写
  • constitution 是项目级硬规则,AI 读到会遵守
  • plan 阶段是第一次要 AI 选型——技术栈、依赖、架构、项目结构,全靠 AI 推断。完全没有"不许发挥"的限制

我前前后后看了 30 多份 AI 出的 plan.md,翻车方式高度雷同:

  • 选型"看起来很合理"但和项目现状冲突(明明项目用 Spring Boot,AI 给规划了一套 Express + Prisma)
  • “一次性做了 8 个功能模块”——P1 之外的也顺手规划了
  • 选型决策没记录 rationale——3 个月后没人记得"为什么用 X"
  • Constitution Check 流于形式——AI 自己写的、自检的、自通过的

这篇文章把 /speckit.plan 怎么读、怎么改、怎么卡住 AI 讲透。

plan.md 的骨架:6 个必填节

仓库的 plan-template.md 给的骨架:

章节填什么
Summary一句话讲明白做什么 + 怎么实现
Technical Context6 个字段:语言/版本、主依赖、存储、测试、目标平台、项目类型
Constitution Check双重门禁——Phase 0 前 + Phase 1 后各查一次
Project Structure文档 + 源码目录树(3 选 1,未选的要删
Complexity Tracking违反宪法时的"罪状表"(多数 plan 留空)
Phase 0/1 产物research.md / data-model.md / contracts/ / quickstart.md

plan.md 命令源码里写明:

Phase 0: Generate research.md (resolve all NEEDS CLARIFICATION)
Phase 1: Generate data-model.md, contracts/, quickstart.md
Re-evaluate Constitution Check post-design

两阶段不是装饰——Phase 0 解决"用什么",Phase 1 解决"怎么用"。AI 必须先做完 Phase 0 才有 Phase 1。

第一个关卡:Technical Context 别让 AI 瞎填

Technical Context 有 6 个字段,每个都可能 NEEDS CLARIFICATION

**Language/Version**: [e.g., Python 3.11, Swift 5.9, Rust 1.75 or NEEDS CLARIFICATION]
**Primary Dependencies**: [e.g., FastAPI, UIKit, LLVM or NEEDS CLARIFICATION]
**Storage**: [if applicable, e.g., PostgreSQL, CoreData, files or N/A]
**Testing**: [e.g., pytest, XCTest, cargo test or NEEDS CLARIFICATION]
**Target Platform**: [e.g., Linux server, iOS 15+, WASM or NEEDS CLARIFICATION]
**Project Type**: [e.g., library/cli/web-service/mobile-app/compiler/desktop-app or NEEDS CLARIFICATION]
**Performance Goals**: [domain-specific, e.g., 1000 req/s, 10k lines/sec, 60 fps or NEEDS CLARIFICATION]
**Constraints**: [domain-specific, e.g., <200ms p95, <100MB memory, offline-capable or NEEDS CLARIFICATION]
**Scale/Scope**: [domain-specific, e.g., 10k users, 1M LOC, 50 screens or NEEDS CLARIFICATION]

AI 翻车点 1:直接把 NEEDS CLARIFICATION 全填上"合理默认"

我看到的 plan 经常长这样:

Language/Version: TypeScript 5.6
Primary Dependencies: React 18, Express 4, Prisma
Storage: PostgreSQL 16

AI 自己拍脑袋填的。没有问你,没有写 rationale。

plan.md 命令源码里的原话是"mark unknowns as NEEDS CLARIFICATION"——AI 应该不知道时标不知道,不是 AI 替用户决定。

正确做法: 给 plan 命令加约束(写在 constitution 的 Additional Constraints 里):

### VI. Plan Stage Discipline
- Technical Context 字段中,AI 推断出的选型必须用 [INFERRED: 原因] 标注
- 任何 [INFERRED] 项必须在 Phase 0 research.md 中给出 Decision/Rationale/Alternatives
- 用户未在 spec 中明确提到的技术选型,必须保留 NEEDS CLARIFICATION 让用户确认

读 plan 的第一步:打开 Technical Context,逐项检查——

  • 有没有 NEEDS CLARIFICATION 留下没解决的?(不该有,Phase 0 必须解决)
  • 有没有 AI 推断但没标 [INFERRED] 的?(项目里搜"Primary Dependencies"等关键字段,看 AI 是否偷偷填了)
  • 推断是否和 codebase 现状冲突?(项目用 Spring Boot,AI 规划 Express——这就是冲突)

第二个关卡:Constitution Check 双重门禁

plan-template.md 顶部明文写:

GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.

两重门禁。Phase 0 前查一次——技术选型是否违反宪法;Phase 1 后查一次——数据模型、接口契约是否违反宪法。

AI 翻车点 2:Constitution Check 自写自审

我见过太多 plan 的 Constitution Check 段是:

Constitution Check

  • ✅ Library-First: 本 feature 设计为独立模块
  • ✅ CLI Interface: 暴露 CLI
  • ✅ Test-First: 测试先行
  • ✅ Observability: 结构化日志

All gates pass.

AI 自己写的、自检的、全部通过。这等于没有检查。

正确做法:把 Constitution Check 改造成机械检查而不是 AI 自评。比如改成:

## Constitution Check

| Principle | Gate | Status | Evidence |
|-----------|------|--------|----------|
| I. Library-First | 是否独立 library? | ✅ | `packages/photo-album/` 独立目录,含独立 package.json |
| II. CLI Interface | 是否暴露 CLI? | ⚠️ | CLI 计划在 v1.1,v1 仅提供 HTTP API |
| III. Test-First (NON-NEGOTIABLE) | 测试是否先写? | ✅ | tasks.md 中 [TEST] 任务排在 [IMPL] 之前 |
| IV. Integration Testing | 是否规划集成测试? | ✅ | tasks.md 含 `tests/integration/` 任务 |
| V. Observability | 是否带结构化日志? | ✅ | data-model.md 含 audit_log 实体 |

**Violations requiring justification**: II. CLI Interface 推迟到 v1.1
(见 Complexity Tracking 第 1 条)

关键差异

  • 每条原则都问具体问题(“是否独立 library?”),不是抽象的"是否符合"
  • Status 用 ✅ / ⚠️ / ❌ 三档,不是非黑即白
  • Evidence 列指向具体证据(文件路径、章节),不是"已规划"
  • 违反项必须进 Complexity Tracking

第三个关卡:Project Structure 别让 AI 留 3 套选项

plan-template.md 给的源码段是这样:

### Source Code (repository root)

```text
# [REMOVE IF UNUSED] Option 1: Single project (DEFAULT)
src/
├── models/
...

# [REMOVE IF UNUSED] Option 2: Web application (when "frontend" + "backend" detected)
backend/
...
frontend/
...

# [REMOVE IF UNUSED] Option 3: Mobile + API (when "iOS/Android" detected)
api/
...
ios/ or android/
...

plan-template.md 注释里明文要求:

ACTION REQUIRED: Replace the placeholder tree below with the concrete layout for this feature. Delete unused options and expand the chosen structure with real paths (e.g., apps/admin, packages/something). The delivered plan must not include Option labels.

AI 翻车点 3:3 个 Option 全部保留

我看过一个 plan,三套结构全留着,最后加一句"Selected: Option 1"。这是偷懒——AI 没做选型决策。

正确读法:plan 里如果出现 Option 1 / Option 2 / Option 3 文字,直接打回让 AI 改。要求 AI:

  1. 选 1 个 Option
  2. 删掉另外 2 个的代码块
  3. 选中的 Option 展开成真实路径(不是 src/ models/ 这种占位)
  4. Structure Decision 段写为什么选这个

相册例子改后:

### Source Code (repository root)

```text
packages/
├── photo-album-core/        # 核心库,Library-First 原则
│   ├── src/
│   │   ├── models/
│   │   ├── services/
│   │   └── lib/
│   └── tests/
│       ├── unit/
│       └── integration/
└── photo-album-cli/         # CLI 入口,CLI Interface 原则
    └── src/

apps/
└── web/                     # Web 界面,v1.1
    ├── src/
    └── tests/

Structure Decision: 选 Option 1 变体——把源码拆成 packages/(核心库 + CLI)和 apps/(前端),因为宪法 Principle I 要求每个 feature 起步为独立 library,Principle II 要求暴露 CLI。


## 第四个关卡:读 research.md 看 AI 怎么选型

Phase 0 产出的 `research.md` 是 plan 里**最值得读的一份**。`plan.md` 命令源码里规定的格式:

> - Decision: [what was chosen]
> - Rationale: [why chosen]
> - Alternatives considered: [what else evaluated]

**AI 翻车点 4:Decision + Rationale 齐全,但 Alternatives 是凑数的**

我看过的 research.md 一半长这样:

> ### Question 1: 数据库选型
>
> **Decision**: PostgreSQL
> **Rationale**: 团队熟悉
> **Alternatives considered**: MySQL, MongoDB

**"团队熟悉"不是 rationale**。Alternatives 凑了 2 个名字、没讲为什么 rejected——3 个月后没人能回溯决策。

**正确版:**

```markdown
### Question 1: 主数据库选型

**Decision**: PostgreSQL 16

**Rationale**:
1. 团队 12 个工程师中 10 个有 PG 生产经验
2. ACID 强一致,FR-005(相册嵌套限制)需事务约束
3. EXIF 元数据(FR-001)用 JSONB 存储可减少表数量
4. TimescaleDB 扩展可应对 SC-001 性能要求

**Alternatives considered**:
- MySQL 8: 团队经验相同,但 JSON 支持弱于 PG;事务隔离级别对嵌套相册场景不够
- MongoDB: 无强事务,与 FR-005 冲突;运维成本高(团队无 NoSQL 经验,见 constitution Principle VI)
- SQLite: 单机性能优秀,但 SC-002 要求 5000 张照片 5 秒内检索,多用户并发不可行

**Trade-offs**:
- PG 学习曲线低(团队熟)
- 但 PG 写性能弱于 MongoDB 大批量插入——本场景是读多写少,可接受

每条 Alternative 都要讲为什么 rejected——这才叫选型。读 plan 阶段最快的"过稿"方法:扫一眼 research.md 的 Alternatives 字段,全是敷衍描述的 plan 风险高,全部详细比较的 plan 风险低。

第五个关卡:data-model.md 别让 AI 替你做领域决策

Phase 1 产出的 data-model.md,由 plan.md 命令源码规定:

  1. Extract entities from feature specdata-model.md:
    • Entity name, fields, relationships
    • Validation rules from requirements
    • State transitions if applicable

AI 翻车点 5:字段过多,把数据库 schema 写进 spec

我看过的 data-model.md:

### Photo
- id: BIGSERIAL PRIMARY KEY
- file_path: VARCHAR(500) NOT NULL
- exif_date: TIMESTAMP WITH TIME ZONE
- file_size: BIGINT
- mime_type: VARCHAR(50)
- upload_time: TIMESTAMP DEFAULT NOW()
- album_id: BIGINT REFERENCES albums(id)
- user_id: BIGINT REFERENCES users(id) NOT NULL
- created_at: TIMESTAMP DEFAULT NOW()
- updated_at: TIMESTAMP DEFAULT NOW()
- deleted_at: TIMESTAMP

这是 DDL,不是 data model。 字段类型、大小、DEFAULTREFERENCES——全是 schema 决策。plan 阶段不该决定这些。

正确版:

### Photo
- **id**: 系统生成的唯一标识
- **file_path**: 照片存储路径(本地或对象存储,由 storage 层决定)
- **exif_date**: 拍摄日期(来自 EXIF,FR-001)
- **file_size**: 文件大小(用于 UI 显示)
- **mime_type**: 文件类型(用于前端展示)
- **album_id**: 所属相册(强约束:每张照片属且仅属一个相册,FR-005)
- **uploaded_at**: 上传时间(用于 UI 排序)

**Validation rules**:
- exif_date 缺失时使用 uploaded_at 作为 fallback(FR-001 的兜底)
- file_size > 0
- mime_type 必须在白名单 [image/jpeg, image/png, image/heic]

**State transitions**:
- 上传中 → 已归档(exif_date 解析完成)
- 已归档 → 已重分组(用户拖拽,FR-004)
- 已归档 → 已删除(v2 引入软删,v1 物理删除)

关键差异

  • 不写 SQL 类型(BIGSERIAL / TIMESTAMP
  • 不写 schema 细节(REFERENCES / DEFAULT
  • 业务约束(“每张照片属且仅属一个相册”——这条直接来自 FR-005)
  • 验证规则(白名单、兜底策略)
  • 状态机(v1 没有但预留扩展)

读 plan 阶段最快的"过稿"方法 2:data-model.md 里出现 SQL DDL(CREATE TABLE、PRIMARY KEY、FOREIGN KEY、DEFAULT)——直接打回

第六个关卡:contracts/ 和 quickstart.md

plan.md 命令源码对这两个的说明:

Define interface contracts (if project has external interfaces) → /contracts/:

  • Identify what interfaces the project exposes to users or other systems
  • Document the contract format appropriate for the project type
  • Examples: public APIs for libraries, command schemas for CLI tools, endpoints for web services, grammars for parsers, UI contracts for applications
  • Skip if project is purely internal (build scripts, one-off tools, etc.)

Create quickstart validation guidequickstart.md:

  • Document runnable validation scenarios that prove the feature works end-to-end
  • Include prerequisites, setup commands, test/run commands, and expected outcomes

AI 翻车点 6:contracts 写成 API 文档,quickstart 写成 README

# contracts/api.md
## POST /api/photos
Body: multipart/form-data
Response: 200 OK
...

# quickstart.md
## Run the server
npm install
npm start

contracts 不是 API 文档——是契约。Web 服务的契约是 OpenAPI/GraphQL schema,CLI 工具是命令 schema(argparse 定义、exit code 列表、stdout 格式),库的契约是 public API 接口签名。

quickstart 不是 README——是可执行的端到端验证场景plan.md 命令明文要求"runnable validation scenarios"——每个场景要能跑、跑完有明确预期结果。

相册例子的 quickstart:

## 验证场景 1: 单张照片自动归类

**Prerequisites**:
- PostgreSQL 已启动且空库
- 编译产物已生成
- 测试目录有 `fixtures/photo_2024_03_15.jpg`(EXIF 含 2024-03-15)

**Steps**:
```bash
$ pbm-cli upload fixtures/photo_2024_03_15.jpg

Expected output:

[INFO] Uploading photo...
[INFO] EXIF date detected: 2024-03-15
[INFO] Created album "2024 年 3 月" (id=1)
[INFO] Photo added to album 1
[OK] Upload complete

Validation:

SELECT COUNT(*) FROM albums WHERE title = '2024 年 3 月';  -- 应返回 1
SELECT COUNT(*) FROM photos WHERE album_id = 1;             -- 应返回 1

每个场景都包含:**前置条件**、**可执行命令**、**预期输出**、**验证 SQL**——**可以直接在 CI 里跑**。

## Complexity Tracking:违反宪法时的"罪状表"

`plan-template.md` 最后一段:

> **Fill ONLY if Constitution Check has violations that must be justified**
>
> | Violation | Why Needed | Simpler Alternative Rejected Because |
> |-----------|------------|-------------------------------------|
> | [e.g., 4th project] | [current need] | [why 3 projects insufficient] |
> | [e.g., Repository pattern] | [specific problem] | [why direct DB access insufficient] |

**关键三列**:

1. **Violation**: 违反了哪条
2. **Why Needed**: 当前什么需求必须违反
3. **Simpler Alternative Rejected Because**: 为什么简单的方案不行

**AI 翻车点 7:Complexity Tracking 写"我觉得需要"**

我看过的:

> | Violation | Why Needed | Simpler Alternative Rejected Because |
> |-----------|------------|-------------------------------------|
> | 引入 Kafka | 当前需求 | 简单方案不行 |

**等于没写**。`Why Needed` 是当前需求的具体痛点,不是"当前需求"。`Simpler Alternative Rejected Because` 要讲清楚试过哪个简单方案、卡在哪。

**正确版:**

> | Violation | Why Needed | Simpler Alternative Rejected Because |
> |-----------|------------|-------------------------------------|
> | 引入 Redis 缓存(违反 Principle VII Simplicity 倾向) | SC-001 要求 5000 张照片 5 秒内检索,PG 单表扫描 P95 850ms 不达标 | 1) 试过加 PG 索引:P95 降到 220ms,仍不达标<br>2) 试过分区表:维护成本高,小团队不划算<br>3) 试过只查最近 90 天照片:UX 部门拒绝(用户期望翻历史照片) |
> | 拆 4 个微服务(违反 Principle I 单一 library 倾向) | 用户上传链路涉及 EXIF 解析、AI 标签、缩略图、CDN 推送,单进程无法满足 SC-001 的 30s 要求 | 1) 试过单进程异步队列:P95 仍超时<br>2) 试过拆 2 个服务(上传/处理分离):处理服务仍是瓶颈<br>3) 折中方案:保留 2 服务,AI 标签和缩略图作为上传服务的 worker 进程 |

**关键特征**:

- 每个 Violation 都**试过简单方案**
- `Rejected Because` 列出**至少 2 个被否的方案**
- 数字、命令、版本号都给——可复现的拒绝理由

## plan 阶段过稿清单

拿到 plan.md 后,按这个清单走一遍:

[ ] Technical Context
- [ ] 没有未解决的 NEEDS CLARIFICATION
- [ ] AI 推断项都标了 [INFERRED] 并有 research.md 对应
- [ ] 选型与 codebase 现状不冲突

[ ] Constitution Check
- [ ] 每条原则问具体问题,不是抽象陈述
- [ ] Evidence 列指向文件/章节
- [ ] 违反项进了 Complexity Tracking

[ ] Project Structure
- [ ] 只保留 1 个 Option
- [ ] 展开成真实路径
- [ ] Structure Decision 写为什么选

[ ] research.md
- [ ] 每个 Decision 有 Rationale
- [ ] 每个 Decision 有 Alternatives considered(≥2 个)
- [ ] Alternatives 都写为什么 rejected

[ ] data-model.md
- [ ] 没有 SQL DDL(CREATE TABLE / PRIMARY KEY / FOREIGN KEY)
- [ ] 字段都是业务术语
- [ ] Validation rules 引用具体 FR
- [ ] State transitions 标明 v1/v2 范围

[ ] contracts/
- [ ] 是契约不是文档(OpenAPI/argparse/exit code list)
- [ ] 跳过项有说明(“纯内部工具,跳过”)

[ ] quickstart.md
- [ ] 每个场景有 Prerequisites/Steps/Expected/Validation
- [ ] 命令可直接复制执行
- [ ] 预期输出可断言

[ ] Complexity Tracking
- [ ] 每行 Violation 试过 ≥1 个简单方案
- [ ] Rejected Because 含具体数据


**任何一项 ❌ 就打回让 AI 改**。`plan.md` 命令是 spec 之后**最值得花时间审**的——后面 `/speckit.tasks` 直接消费 plan.md,plan 错了任务清单全错。

## 写 plan 时卡住 AI 的 3 个实用技巧

最后说几个我在 constitution 里加的、专门管 plan 阶段的"硬规则":

**1. INFERRED 标记强制**

```markdown
### VII. Plan Stage: Mark All Inferences
Technical Context 中任何 AI 推断的技术选型,必须用 [INFERRED] 包裹。
未标 [INFERRED] 的选型视为用户已确认。
Phase 0 research.md 中每个 [INFERRED] 必须有对应 Decision/Rationale/Alternatives。

效果:AI 不再"偷偷"填字段,每个推断都暴露在阳光下。

2. Project Structure 单一选项

### VIII. Plan Stage: Single Structure Only
plan.md 的 Project Structure 必须只保留 1 个 Option,
且必须展开为真实路径(不允许 src/、models/ 这种占位)。
保留多个 Option 视为 plan 失败。

效果:AI 不再"三选一"敷衍,必须做选型决策。

3. data-model.md 无 DDL

### IX. Plan Stage: data-model.md is Domain, Not Schema
data-model.md 不得出现 SQL DDL 关键字(CREATE/ALTER/DROP/INDEX/REFERENCES/DEFAULT)。
字段类型、大小、约束由 /speckit.tasks 阶段决定。

效果:data-model.md 聚焦业务领域,schema 决策留给 tasks。

小结

关卡核心防翻车点
Technical Context不让 AI 偷偷填,强制 [INFERRED] 标注
Constitution Check双重门禁、机械检查、Evidence 指向文件
Project Structure只保留 1 个 Option,展开真实路径
research.md每个 Decision 都有 Alternatives + rejected 理由
data-model.md没有 SQL DDL,业务约束代替
contracts/是契约不是文档
quickstart.md可执行的端到端场景
Complexity Tracking试过简单方案再写 Violation

plan 阶段是 spec-kit 工作流里最需要人把关的一步——spec 阶段 AI 是戴手铐的,tasks 阶段 AI 是按 plan 走的。只有 plan 阶段 AI 第一次"选型",最容易跑偏。

下一篇会写 /speckit.tasks 怎么拆——task 颗粒度、依赖关系、并行度,都是这个阶段的活。

AI 时代程序员必备技能

Codex、Claude Code、Cursor、Hermes Agent、OpenClaw等工程化实战专栏 ,讲透 AI 如何接管脏活累活

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码绘春秋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值