Claude Code × Spec Kit 实战:五步构建番茄钟待办 Web 应用
- Published on
- /43 mins read/---
Spec Kit 介绍
什么是 Spec Kit
Spec Kit 是 GitHub 开源的开发工具包,基于**规范驱动开发(Spec-Driven Development)**的理念,帮助开发团队更快地构建高质量软件。
它至今拿了 145,042 Stars,是 GitHub 上增长最快的开发流程类项目。从 2025-08-21 创建至今不到 15 个月,版本已经迭代到 1.2.5(2026-10-28 发布),并且从单一的 SDD 流程扩展成了包含 Bug 修复、想法评估、扩展、预设、Bundle 的完整工具箱。
核心理念:规范驱动开发
传统软件开发流程:
编写规范 → 编写代码 → 测试 → 部署
规范驱动开发则完全颠覆了这一模式:
- 规范成为核心:规范不再是临时性的文档,而是可执行的
- 自动生成代码:规范直接生成可工作的实现代码
- 规范即代码:规范本身就是开发的一部分,而不是前期准备工作
核心特点
01 提升开发效率
- 让组织专注于产品场景,而不是重复编写基础代码
- 减少编写"无差别代码"的时间投入
- 通过标准化流程提高团队协作效率
02 完整的工具生态
- Specify CLI:命令行工具(
uv tool install specify-cli),用于执行规范驱动开发流程 - AI 代理支持:集成 50+ 种 AI 助手,Claude Code 是一等公民
- 文档和最佳实践:提供完整的使用指南和模版
03 解决传统开发痛点
- 规范写完就被丢弃
- 代码与文档脱节
- 重复编写相似的基础代码
- 开发效率低下
Spec Kit 环境搭建详解
下载与安装
现在推荐用 uv 安装(不再需要手动 clone + venv):
uv tool install specify-cli需要 Python 3.11+、uv,以及一个支持的 AI 编码代理(Linux / macOS / Windows 均可)。
初始化项目
specify init focus-todo --integration claude
cd focus-todo两个参数值得解释:
--integration claude:装 Claude Code 集成。会生成.claude/skills/speckit-*/SKILL.md,把每个命令变成一个 skill- 项目名会自动创建对应子目录;
init完会直接打印下一步该敲哪几条斜杠命令
踩坑提示:
--ai-skills这个参数在 1.2.x 已经被移除,敲了会直接报No such option: --ai-skills。从 1.x 起 Claude 默认就用 skills 模式,命令以 skill 形式落在.claude/skills/下,不需要额外开关。另外如果本机没有装 Claude Code,工具检查会拦下来,这时加--ignore-agent-tools跳过检测即可。
和旧版的差别:2025 年那套是
git clone+pip3 install -e .+specify init <name>(交互式选 agent)。现在specify已发布为 PyPI 包,agent 选择改成了--integration参数,claude是合法的 integration key;额外能力(预设、扩展、Bundle)也都收拢到了--preset/--extension这类参数下。
init 到底干了什么
init 一条命令背后是一条九步流水线:
1. Check required tools (ok) ← 检查 git / 代理 CLI 是否在
2. Select coding agent integration ← claude
3. Select script type ← sh / ps / py
4. Install integration (Claude Code) ← 生成 .claude/skills/
5. Install shared infrastructure ← 脚本 + 模板
6. Ensure scripts executable ← chmod +x
7. Constitution setup ← 从模板拷贝
8. Install bundled workflow ← speckit 工作流
9. Finalize (project ready)最终只生成两个目录:
focus-todo/
├── .claude/ ← 智能体侧:10 个 SKILL.md
└── .specify/ ← 引擎侧:脚本 + 模板 + 记忆 + 工作流
.claude/:给模型看的「操作手册」
.claude/skills/speckit-*/SKILL.md,每个都是 YAML frontmatter + 自然语言 SOP,合计约 2500 行:
| Skill | 行数 | 职责 |
|---|---|---|
-constitution | 182 | 写项目宪章 |
-specify | 348 | 生成 spec.md |
-plan | 169 | 生成 plan.md |
-tasks | 218 | 生成 tasks.md |
-implement | 229 | 按任务执行 |
-converge | 285 | 收敛检查 |
-clarify / -analyze / -checklist | 294 / 262 / 386 | 可选增强 |
-taskstoissues | 120 | 任务转 GitHub issue |
这些 SKILL.md 不是简单的命令别名。以 speckit-specify 为例,它内建了整个流程编排:读 .specify/memory/constitution.md 取宪章约束 → 按 init-options.json 的 feature_numbering 算目录前缀 → 把 templates/spec-template.md 拷成 specs/001-xxx/spec.md → 写 .specify/feature.json → 自动生成 checklists/requirements.md 自检 → 最多抛 3 个 [NEEDS CLARIFICATION] 反问用户。
换句话说,你敲 /speckit-specify 时,模型执行的是一份几百行的 SOP,不是一句提示词。 这也是为什么不同人跑出来的产物质量差别很大 —— SOP 是固定的,但填进去的内容质量取决于你自己的输入。
.specify/:引擎侧的四块
1. scripts/bash/(6 个 0755 可执行脚本)
| 脚本 | 行数 | 作用 |
|---|---|---|
common.sh | 942 | 公共库,向上找 .specify 定位仓库根 |
create-new-feature.sh | 504 | 建分支 / 算 feature 编号 / 建目录 |
check-prerequisites.sh | 249 | 前置检查(--json / --require-spec) |
setup-plan.sh / setup-tasks.sh | 89 / 94 | 生成 plan/tasks 上下文 |
resolve-template.sh | 57 | 模板解析(支持 preset 覆盖) |
2. templates/(5 个骨架,共 591 行) —— spec / plan / tasks / constitution / checklist。spec 模板已固化 User Scenarios & Testing、Requirements、Success Criteria、Assumptions 等章节,Claude 只负责填空,不用自由发挥结构。
3. memory/ —— constitution.md 是宪章占位模板,所有下游命令运行时都会读它;旁边的 .constitution-template.json 记下模板的 sha256,用来判断"用户改过没有"。
4. workflows/ —— speckit/workflow.yml 是声明式流水线 specify → gate → plan → gate → tasks → implement,带人工审核关卡;workflow-registry.json 是注册表。
另外还有几个状态与元数据文件:
| 文件 | 作用 |
|---|---|
init-options.json | init 时的选项快照:ai_skills: true(skills 默认开)、feature_numbering: sequential、script: sh |
integration.json | 已装 integration、schema 版本、claude 的 invoke_separator: "-" —— 所以命令是 /speckit-plan 而不是 /speckit.plan |
integrations/*.manifest.json | 每个 integration 装了哪些文件 + 每个文件的 sha256,用于升级、校验、卸载 |
.gitignore | 忽略 feature.json、extensions/*/local-config.yml、integrations/packages/(本机状态不入库) |
跑起来之后的数据流
/speckit-specify
├─ 读 .specify/memory/constitution.md ← 宪章约束
├─ 读 .specify/init-options.json ← 编号策略
├─ 拷 .specify/templates/spec-template.md → specs/001-xxx/spec.md
├─ 调 .specify/scripts/bash/create-new-feature.sh
└─ 写 .specify/feature.json ← 后续命令靠它找当前 feature
feature.json 是本地指针(所以被 .gitignore 排除),团队共享的是 specs/ 目录本身。
实战案例:构建番茄钟待办 Web 应用
为什么选一个最简单的案例
第一次接触规范驱动开发,很容易走偏:挑最难的那个业务来做。结果不是被规范难住,而是被环境、依赖、算力、硬件难住,最后得出"规范驱动开发不适合我"的结论。
所以这个案例刻意选了相反的一端:一个纯前端的番茄钟 + 待办清单,没有后端、没有模型、没有 GPU、没有硬件依赖,pnpm install 之后立刻就能跑起来。
选案例的标准只有三条:
- 五分钟能启动 —— 读者能真正跟完整个流程,不会卡在环境上
- 边界条件可穷举 —— 有真实的坑值得写进 spec,而不是靠规模取胜
- 结果可当场验证 —— 打开浏览器就能判断做对没做对
这不代表简单项目不需要规范。恰恰相反,番茄钟这种"看起来没有技术含量"的玩具,藏着一组非常典型的坑:页面刷新后剩余时间跳变、系统休眠后计时漂移、隐私模式下 localStorage 直接抛异常、两个标签页同时开着互相覆盖。这些坑一个都不会因为你项目小就消失,而它们恰好是规范驱动开发收益最大的地方。
项目背景
本案例用 Vite + React + TypeScript + Tailwind CSS 构建一个纯前端应用:
- 待办清单:新建、完成、编辑、删除,支持预估番茄数
- 番茄钟:专注 / 短休 / 长休三阶段,长休每 4 个番茄出现一次
- 任务联动:开始一个番茄时绑定当前任务,番茄正常结束自动累加到该任务
- 统计:今日完成番茄数与专注时长
- 持久化:localStorage,刷新不丢
- 备份:导出 / 导入 JSON,换设备可迁移
选它的原因:这是一个状态机清晰、时间逻辑可测、边界条件真实存在的小应用。所有"会出错的算术"都集中在计时逻辑里,而这恰恰是纯函数 + 单元测试最擅长覆盖的地方 —— 换句话说,Claude 写出来的第一版几乎必然是错的,规范和测试存在的意义就是把它掰回来。
Spec Kit 五大核心命令
/speckit-constitution/speckit-specify/speckit-plan/speckit-tasks/speckit-implement
本案例跑到哪一步了:
| 命令 | 做什么 | 实际产物 |
|---|---|---|
/speckit-constitution | 写项目宪章 | constitution.md(v1.0.0,已批准) |
/speckit-specify | 扩写需求规格 | spec.md + checklists/requirements.md |
/speckit-plan | 定技术栈与架构 | plan.md、research.md、data-model.md、quickstart.md、contracts/ |
/speckit-tasks | 拆成依赖有序的任务 | tasks.md(尚未生成) |
/speckit-implement | 按任务写代码 | 尚未执行 |
语法变化:旧版是
/speckit.constitution(点号),1.x 改成了/speckit-constitution(连字符),并新增了/speckit-converge作为收敛检查环节。
上面这五个就是主干;.claude/skills/ 下实际还装了 /speckit-clarify、/speckit-analyze、/speckit-checklist、/speckit-taskstoissues 等增强型 skill,用不用取决于你有多在意 spec 的质量(详见上文《init 到底干了什么》)。
进入项目环境
用 Claude Code 打开项目目录。init 不会生成 CLAUDE.md —— 那是 Spec Kit 不管的部分,但恰恰是 Claude Code 相对普通 IDE 助手的关键差异所在。这个案例没有现成的开源项目可以借用,于是我们在 T004 里专门写了一份,内容包括技术栈、目录约定、常用脚本,以及最关键的一条硬性规则:
## 硬性规则
- 所有与时间相关的计算必须放在 `src/domain/timer.ts` 的纯函数里;
组件中禁止出现 `Date.now()` 参与算术,只允许读取纯函数的返回值。这正是 Claude Code 相对普通 IDE 助手的关键差异:项目级的 CLAUDE.md 会被自动加载,配合 Spec Kit 生成的 constitution.md,形成两层约束。规则写在文件里,而不是每次在 prompt 里重复粘贴。
Spec Kit 五大核心命令详解
制定项目宪章 - /speckit-constitution
/speckit-constitution —— 写出项目的核心治理原则与开发标准(已生成 constitution.md,v1.0.0 于 2026-11-02 批准)。
目的:建立项目的核心治理原则和开发标准。
在 Claude Code 中的执行方式:直接输入
/speckit-constitution 制定以代码质量、测试标准、时间与状态正确性、性能预算与数据可迁移性为核心的原则
输出成果:生成 constitution.md 文件。针对这个项目,我会把宪章写成这样:
# Focus-Todo Constitution
## Core Principles
### I. 代码质量(不可妥协)
- MUST 通过 ESLint + Prettier + `tsc --noEmit`,三者任一失败则禁止合并。
- MUST 开启 TypeScript `strict`,禁止使用 `any` 逃逸类型检查。
- MUST 公共组件与领域函数具备类型签名与最小示例;破坏性变更需变更记录。
### II. 测试标准与覆盖率
- MUST 测试优先(Red-Green-Refactor):先写测试、确保失败,再实现。
- MUST `src/domain/` 与 `src/storage/` 行覆盖率 ≥ 90%,全局 ≥ 80%。
- MUST CI 中执行测试并作为合并门禁。
- MUST 每个 Edge Case 至少对应一条测试用例。
### III. 时间与状态正确性(本项目特有)
- MUST 剩余时间以绝对结束时间戳计算,禁止用定时器累减。
- MUST 计时状态只能是有限状态机的合法状态,禁止出现非法中间态。
- MUST 时钟回拨、页面休眠、跨天等场景的剩余时间不得为负、不得凭空增加。
### IV. 性能预算
- MUST 在 plan.md 中声明可衡量目标(首屏可交互 < 1s;gzip < 150 kB)。
- MUST 计时每秒只更新计时组件,不得触发任务列表整体重渲染。
- MUST 提供构建产物体积检查并纳入 CI。
### V. 数据可迁移性
- MUST 持久化数据带 `schemaVersion`,读取时按版本迁移,不得直接崩溃。
- MUST 导入备份先校验再写入,校验失败必须保留原有数据。
- MUST 导出文件为人类可读 JSON,便于手工排查。
## 质量门禁与度量
- 门禁项:Lint / Typecheck 全通过;测试通过且覆盖率达标;构建产物体积不超标;
**Edge Case 清单逐条有对应测试**。
- 失败则禁止合并;豁免需在 PR 描述记录、关联后续任务并标注影响范围与期限。为什么第三条不可妥协:代码质量和测试标准是通用 Web 项目的共识,任何团队都适用;但时间与状态正确性是这个领域专属的 —— 一个番茄钟哪怕只是"多算了 3 秒",用户也会立刻失去信任。这类领域规则一旦不写进宪章,Claude 生成的代码里就不会有对应的检查。换一个业务,宪章的第三条就应该跟着换。
需求规格说明 - /speckit-specify
/speckit-specify —— 把一句话需求扩写成可测试的规格(已生成 spec.md 与 checklists/requirements.md)。
目的:描述要构建什么,专注于 what 和 why,而非技术实现。
/speckit-specify 我要做一个番茄钟 + 待办清单 Web 应用(纯前端):待办支持新建、
完成、编辑、删除和预估番茄数;番茄钟分专注、短休、长休三个阶段;番茄结束后把
完成数累加到当前绑定的任务;统计今日番茄数与专注时长;数据存 localStorage;
支持导出/导入 JSON 备份。刷新页面不能丢状态,时间不能漂移。
输出成果:生成 specs/001-pomodoro-todo/ 目录,含 spec.md 与 checklists/requirements.md 两个文件。先看 spec.md(节选:真实的 7 个用户故事 / 39 条 FR / 12 条 SC 压缩成 3 / 8 / 5,编号也重排了):
# Feature Specification: 番茄钟 + 待办清单(Pomodoro Todo)
**Feature Branch**: `001-pomodoro-todo`
**Status**: Draft
## User Scenarios & Testing
### User Story 1 - 管理任务清单 (Priority: P1)
用户新建待办、设定预估番茄数、标记完成,并在开始计时前挑选当前任务。
**Why this priority**: 没有任务清单,番茄钟只是一个计时器,这是应用的主线。
**Independent Test**: 仅实现任务的增删改查与持久化,即可独立完成"建任务—刷新—仍在"。
**Acceptance Scenarios**:
1. Given 输入框有内容,When 点击新增,Then 列表出现该任务,输入框清空且焦点保留。
2. Given 任务未完成,When 勾选完成,Then 任务置为完成态并沉底,但不从列表消失。
3. Given 存在未完成任务,When 刷新页面,Then 任务列表与完成状态保持不变。
4. Given 任务标题为空或仅空格,When 提交,Then 拒绝创建并给出提示。
### User Story 2 - 运行番茄钟并与任务联动 (Priority: P1)
用户启动一个专注番茄,倒计时结束后自动进入休息,并把完成的番茄记到当前任务上。
**Why this priority**: 这是应用存在的核心理由,也是计时正确性的主要风险点。
**Independent Test**: 仅实现计时与状态恢复,即可独立完成"开始—刷新—剩余时间不变"。
**Acceptance Scenarios**:
1. Given 处于空闲状态,When 点击开始,Then 计时器显示剩余时间并按秒更新。
2. Given 专注进行中,When 刷新页面,Then 剩余时间与刷新前一致(误差 ≤ 1 秒),
且阶段未被重置。
3. Given 专注计时结束,When 阶段切换,Then 进入休息阶段并弹出提示。
4. Given 绑定了任务,When 一个番茄正常结束,Then 该任务完成数 +1,今日统计同步更新。
5. Given 绑定的任务在计时中被删除,When 番茄结束,Then 计时正常结束但不累加到任何任务。
### User Story 3 - 查看统计并备份数据 (Priority: P2)
用户查看今日专注时长与完成数,导出一份 JSON 备份,换设备时导入恢复。
**Why this priority**: 统计是留存用户的钩子,备份是数据不丢的兜底,都不影响主线跑通。
**Independent Test**: 预置一份备份文件即可独立运行导入导出。
**Acceptance Scenarios**:
1. Given 今天已完成 3 个番茄,When 打开统计面板,Then 显示番茄数 3 与专注时长 75 分钟。
2. Given 点击导出,When 文件保存,Then 得到符合 schemaVersion 约定的 JSON 文件。
3. Given 导入文件格式非法,When 选择导入,Then 报错并保持现有数据不变。
### Edge Cases
- **页面关闭或系统休眠**:计时以绝对时间戳为准,重新打开后按真实时间计算剩余时间,
不做累加。
- **超过 12 小时未完成的番茄**:视为中断,不计入统计,状态回到空闲并提示。
- **localStorage 不可用**(隐私模式、配额满):应用必须仍可运行,降级为内存存储并
显示提示,不得白屏。
- **多标签页同时打开**:状态通过 storage 事件同步,冲突时以最后写入为准,
且不得同时跑两个番茄。
- **导入备份的 schemaVersion 高于当前应用**:拒绝导入并提示版本过新。
- **系统时钟被手动回拨**:剩余时间不得为负,也不得凭空增加。
## Requirements
### Functional Requirements
- **FR-001**: 系统 MUST 支持任务的创建、完成、编辑、删除。
- **FR-002**: 系统 MUST 将任务、计时状态、统计数据持久化到 localStorage。
- **FR-003**: 系统 MUST 支持专注 / 短休 / 长休三阶段,长休每 4 个番茄出现一次。
- **FR-004**: 计时 MUST 以绝对结束时间戳计算剩余时间,不得依赖定时器累加。
- **FR-005**: 系统 MUST 将完成的番茄累加到当前绑定任务,并更新今日统计。
- **FR-006**: 系统 MUST 支持导出 / 导入 JSON 备份,文件带 schemaVersion 字段。
- **FR-007**: localStorage 不可用时系统 MUST 降级运行并提示,不得抛出未捕获异常。
- **FR-008**: 核心操作 MUST 有键盘快捷键,交互元素可聚焦且有可见焦点样式。
### Key Entities
- **Task**: 待办项,含 id、标题、预估番茄数、已完成番茄数、状态(todo/done)、创建时间。
- **TimerState**: 计时状态,含阶段(focus/shortBreak/longBreak)、阶段结束时间戳、
绑定任务 id、是否运行中。
- **Stats**: 按日期聚合的番茄数与专注时长。
- **Backup**: 导出文件,含 schemaVersion 与 tasks / timer / stats 快照。
## Success Criteria
- **SC-001**: 计时核心纯函数(剩余时间、阶段推进、统计累加)行覆盖率 ≥ 90%。
- **SC-002**: 刷新页面后剩余时间误差 ≤ 1 秒;休眠 1 小时后恢复,误差 ≤ 1 秒。
- **SC-003**: 首屏可交互时间 < 1s,构建产物 gzip 后 < 150 kB。
- **SC-004**: 键盘可完成新增任务、开始/暂停计时、切换任务、导出备份全部核心操作。
- **SC-005**: localStorage 被禁用时应用不崩溃,且有可见提示。输出结构:spec.md 与 checklists/requirements.md
跑完 /speckit-specify 后,specs/ 下会出现这样一棵目录树:
specs/
└── 001-pomodoro-todo/
├── spec.md ← 需求规格本体
└── checklists/
└── requirements.md ← 规格质量自检表目录名的两段都有讲究:
001来自.specify/init-options.json的feature_numbering: "sequential"—— 扫描specs/目录,取下一个可用的三位编号pomodoro-todo是模型从你的原始描述里提炼的 2–4 词短名(action-noun 格式),不是你自己指定的
同时 .specify/feature.json 会写入 {"feature_directory": "specs/001-pomodoro-todo"}。这个文件是本地指针,被 .specify/.gitignore 排除在版本库之外;团队共享的是 specs/ 目录本身。feature.json 只负责让 /speckit-plan、/speckit-tasks 找到"当前这个 feature"。
spec.md —— 拷模板 + 填空
它不是模型自由发挥写出来的,而是 .specify/templates/spec-template.md 的副本。章节顺序和标题被硬性固定,其中四块标了 *(mandatory)*,必须存在:
| 章节 | 作用 |
|---|---|
User Scenarios & Testing | 按优先级排的用户故事,每条自带 Independent Test |
Requirements | FR 条目,用 RFC 2119 关键词(MUST / MUST NOT)逐条陈述 |
Success Criteria | 技术无关的可度量指标 |
Assumptions | 需求没写、但必须有个默认答案的地方 |
关键是它规定的是 WHAT,不是 HOW。全文没有出现 React、TypeScript、组件、hook 这些词 —— 出现"localStorage"也是作为业务约束(数据只存本地、刷新不丢),而不是"用某个 API 实现"。技术栈是下一步 /speckit-plan 的事。
三层内容是递进的 —— User Story 说清用户能拿到什么价值,FR 把它拆成可测试的条目,SC 给出可度量的验收线。举一组真实产出:
- User Story 5(P1):刷新或重开页面不丢状态、时间不漂移
- FR-029:计时 MUST 以阶段结束时刻为权威来源,MUST NOT 以累计的定时器回调次数推算进度
- SC-011:在页面保持打开期间,系统时间被前调或后调 10 分钟,进行中计时的剩余时间变化不超过 2 秒
注意 FR-029 这一句的写法 —— 它没有说"用 Date.now()",也没说"用 performance.now()",只禁止了一种错误的算法。技术实现留给 plan,spec 只负责把"对的结果"钉死。
checklists/requirements.md —— 它不是 /speckit-checklist 的产物
这点最容易误解。/speckit-checklist 走的是 checklist-template.md,条目是 CHK001 编号的通用自定义清单;而 checklists/requirements.md 是 /speckit-specify 内建的规格质量自检表,条目列表直接硬编码在 SKILL.md 里,由模型在写完 spec.md 之后逐项自评、打勾:
写 spec.md → 生成 checklist → 逐项验证 → 不合格就改 spec 重跑(最多 3 轮)
→ 仍有 [NEEDS CLARIFICATION] 则抛出最多 3 个问题给用户它检查的是"需求写得好不好",不是"代码做没做完"。条目分三组:Content Quality(有没有泄漏实现细节)、Requirement Completeness(澄清标记清没清、是否可测、边界是否圈定)、Feature Readiness(每个 FR 有没有对应的验收场景)。末尾的 Notes 则是留给下游命令看的人话:
## Notes
- Items marked incomplete require spec updates before `/speckit-clarify` or `/speckit-plan`.
- 澄清问题已提出,等待用户选择:Q1 导入与现有数据冲突时的处理策略、
Q2 阶段时长是否可配置、Q3 统计是否保留历史每日明细。踩坑提示:自检表由模型自己填写,勾选项和说明可能不同步。这次勾选项已经写成"3 markers resolved by user decision(Q2=A 阶段时长固定 25/5/15、Q3=A 只展示今日统计、Q1=A 导入完全替换 + 二次确认)",末尾 Notes 却还留着"澄清问题已提出,等待用户选择"。以勾选项和 spec 正文为准,Notes 只是模型随手记的便签。
所以进
/speckit-plan之前自己扫一遍:grep -n "NEEDS CLARIFICATION" specs/确认没有残留,再核对 FR 编号连续性。自检表是辅助,不是保证。
最关键的一条是"刷新和休眠之后,剩余时间必须和真实时间一致" —— 这是只有把 spec 写细才会浮出水面的需求。第一版实现几乎必然踩中这个坑(详见后文《第一版效果呈现》),而它之所以能被提前拦住,就是因为 FR-004 明确禁止了"定时器累减"这种实现方式。
Edge Cases 要写在自己业务的真实痛点上:番茄钟怕的是时间漂移与存储不可用,换一个业务怕的东西就变了。重要的是,spec 里没写过的边界,Claude 不会替你想到。
技术实现规划 - /speckit-plan
/speckit-plan —— 定技术栈、架构与目录结构,并对照宪章自检(已生成 plan.md、research.md、data-model.md、quickstart.md、contracts/)。
目的:指定实现技术栈、框架和架构设计。
/speckit-plan 用 Vite + React + TypeScript + Tailwind CSS 实现,纯前端无后端;
状态用 useReducer + Context,不引入状态管理库;数据存 localStorage;
时间计算全部抽到 src/domain/ 的纯函数里。
输出成果:生成 plan.md:
# Implementation Plan: Focus Todo
## Summary
- 纯前端单页应用:Vite + React + TypeScript + Tailwind CSS,无后端、无网络请求。
- 核心设计:时间与统计计算抽成纯函数(domain/),UI 只负责渲染与派发;
持久化集中在 storage/ 层并带版本迁移。
- 测试强制:Vitest 单元测试覆盖 domain/ 与 storage/,组件测试用 Testing Library,
端到端用 Playwright 跑一条冒烟。
## Technical Context
**Language/Version**: TypeScript 5.x(strict)
**Primary Dependencies**: react、react-dom、tailwindcss、vite、vitest、
@testing-library/react、playwright
**Package Management**: pnpm(lockfile 必须提交)
**Storage**: localStorage,key `focus-todo/v1`,带 schemaVersion
**Testing**: Vitest(单元)+ Testing Library(组件)+ Playwright(端到端冒烟)
**Target Platform**: 现代浏览器(Chrome / Safari / Firefox 最近两个大版本)
**Project Type**: 单页应用
**Performance Goals**: 首屏可交互 < 1s;gzip < 150 kB;计时 tick 不重渲染任务列表
**Constraints**: 不引入状态管理库;domain 层零依赖、零副作用、100% 可单测
## Constitution Check
_GATE: Must pass before Phase 0 research. Re-check after Phase 1 design._
- Code Quality: ESLint + Prettier + `tsc --noEmit`,pre-commit 钩子。[PLANNED]
- Testing: Vitest 单元 + 组件测试;domain/ 覆盖率 ≥ 90%。[PLANNED]
- Time Correctness: 时间计算集中在 domain/timer.ts 纯函数,组件禁止参与时间算术。[PLANNED]
- Performance: 计时每秒只更新计时组件;构建体积纳入 CI 检查。[PLANNED]
- Data Portability: 备份带 schemaVersion,导入先校验后写入。[PLANNED]
Status: PASS (planned). Re-check after Phase 1 and adjust if any violations arise.
## Project Structure
### Documentation (this feature)
specs/001-pomodoro-todo/
├── plan.md # 本文件
├── research.md # Phase 0 输出
├── data-model.md # Phase 1 输出(Task / TimerState / Stats / Backup)
├── quickstart.md # Phase 1 输出
├── contracts/ # Phase 1 输出
└── tasks.md # Phase 2 输出
### Source Code
src/
├── domain/
│ ├── types.ts # Task / TimerState / Stats / Backup 类型定义
│ ├── timer.ts # 纯函数:remainingMs、nextPhase、advance
│ ├── tasks.ts # 纯函数:创建、完成、排序、过滤
│ └── stats.ts # 纯函数:累加、按日聚合
├── storage/
│ ├── persist.ts # 读写 + 版本迁移 + 降级为内存存储
│ └── backup.ts # 导出 / 导入 + schemaVersion 校验
├── state/
│ ├── reducer.ts # 唯一状态机(useReducer)
│ └── AppContext.tsx
├── hooks/
│ ├── useTimer.ts # 每秒 tick + 阶段推进
│ └── useStorageSync.ts # 多标签页同步
├── components/
│ ├── TaskList.tsx
│ ├── TaskItem.tsx
│ ├── TimerPanel.tsx
│ ├── StatsPanel.tsx
│ └── Shortcuts.tsx
└── App.tsx
tests/
├── unit/ # domain 与 storage
├── component/ # Testing Library
└── e2e/ # Playwright 冒烟这个 plan 里最关键的一行是 domain/ 目录零依赖零副作用 —— 这不是随手加的目录,而是宪章第三条"时间与状态正确性"的落地形式。计划阶段把架构约束写死,Claude 才会把时间算术放进该放的地方,而不是顺手写在组件里。plan.md 里的每一条 Constraint,其实都对应着宪章里的某一条 MUST —— 两份文件互为表里。
任务分解 - /speckit-tasks
/speckit-tasks —— 把上述设计拆成依赖有序的实现任务
目的:将实现计划分解为详细的可执行任务。
输出成果:生成 tasks.md。下面是预期形态 —— 真实项目跑到这一步时,这个命令还没执行:
## Phase 1: Setup
- [ ] T001 初始化 Vite + React + TypeScript 项目,接入 pnpm 与 Tailwind
- [ ] T002 [P] 配置 ESLint / Prettier / `tsc --noEmit` 与 pre-commit
- [ ] T003 [P] 配置 Vitest + Testing Library + Playwright
- [ ] T004 编写 CLAUDE.md:技术栈、目录约定、常用命令、硬性规则
## Phase 2: Foundational(阻塞项)
- [ ] T005 定义领域类型 src/domain/types.ts(Task / TimerState / Stats / Backup)
- [ ] T006 [P] 实现计时纯函数 src/domain/timer.ts(remainingMs / nextPhase)
- [ ] T007 [P] 实现任务纯函数 src/domain/tasks.ts
- [ ] T008 [P] 实现统计纯函数 src/domain/stats.ts
- [ ] T009 实现持久化层 src/storage/persist.ts(版本迁移 + 降级)
- [ ] T010 实现备份导入导出 src/storage/backup.ts
- [ ] T011 实现全局 reducer 与 Context src/state/
**Checkpoint**: Foundation ready
## Phase 3: User Story 1 - 任务清单 (P1) 🎯 MVP
### US1 测试(强制)
- [ ] T012 [P] [US1] 单元测试:任务纯函数 src/domain/tasks.ts
- [ ] T013 [P] [US1] 单元测试:持久化读写与版本迁移 src/storage/persist.ts
- [ ] T014 [P] [US1] 组件测试:新增 / 完成 / 删除任务
### 实现
- [ ] T015 [US1] 实现 TaskList / TaskItem 组件
- [ ] T016 [US1] 接入 reducer 的任务动作
**Checkpoint**: US1 可独立演示
## Phase 4: User Story 2 - 番茄钟 (P1)
### US2 测试(强制)
- [ ] T017 [P] [US2] 单元测试:计时纯函数(刷新 / 休眠 / 时钟回拨)
- [ ] T018 [P] [US2] 单元测试:统计累加 src/domain/stats.ts
### 实现
- [ ] T019 [US2] 实现 TimerPanel 组件与 useTimer hook
- [ ] T020 [US2] 实现番茄与任务联动(含任务被删除的处理)
**Checkpoint**: US2 可独立演示
## Phase 5: User Story 3 - 统计与备份 (P2)
### US3 测试(强制)
- [ ] T021 [P] [US3] 单元测试:备份序列化与非法导入 src/storage/backup.ts
### 实现
- [ ] T022 [US3] 实现 StatsPanel 与导入导出入口
**Checkpoint**: US3 可独立验证
## Phase 6: Polish
- [ ] T023 [P] 实现键盘快捷键与可见焦点样式
- [ ] T024 [P] 实现多标签页 storage 同步
- [ ] T025 端到端冒烟:Playwright 跑通"建任务 → 开始番茄 → 刷新 → 剩余时间不变"
- [ ] T026 编写 quickstart.md 与 README
## Dependencies & Execution Order
- Setup(Phase 1): 无依赖,可立即开始
- Foundational(Phase 2): 依赖 Setup,阻塞所有用户故事
- User Stories(Phase 3+): 均依赖 Foundational;US1 / US2 / US3 可并行
- Polish: 依赖所有目标用户故事完成边界条件对应的任务密度很高:T013、T017、T021、T024 这四个任务,全是为了让 Edge Cases 变成可执行的断言 —— 每个 Edge Case 都得有一处代码或一条测试真正把它兜住,否则它就只是一句写在文档里的愿望。
注意 T017 被放在 US2 的测试位置而不是实现之后:计时纯函数的测试先写,"刷新后剩余时间不变"这条断言会在实现之前就失败,Claude 必须把实现写到能通过它为止。
代码实现 - /speckit-implement
/speckit-implement —— 按 tasks.md 的依赖顺序逐个落地
目的:根据任务列表执行具体的代码实现。
/speckit-implement
在 Claude Code 中的执行方式:Claude Code 会自动读取 tasks.md,按任务依赖顺序执行。几个实操要点:
第一,会话管理。Claude Code 的上下文窗口有限,26 个任务一次性跑完必然上下文溢出。正确做法是按 Phase 分会话:
# 会话 1:Setup + Foundational
claude --resume # 完成后新开会话
# 会话 2:US1
# 会话 3:US2
# 会话 4:US3 + Polish第二,允许 Claude 执行命令。测试和构建都需要执行权限:
claude --allowedTools "Bash(pnpm:*),Bash(pnpm run:*),Read,Edit,Write,Grep,Glob"第三,把硬规则放进 CLAUDE.md 而不是 prompt。T004 写的那条"时间计算只能出现在 domain/ 纯函数里",是 Claude 每个会话都会读到的约束。如果只在某一个会话的 prompt 里说了,换个会话就失效了。
第四,先测试再实现。宪章第二条要求 Red-Green-Refactor,在任务列表里体现为每个用户故事下面先跟一组"测试(强制)"任务。Claude 执行时会先看到测试任务,生成的实现自然对着断言写。
总结与实践建议
通过 Spec Kit 的五大核心命令,我们从项目宪章的制定,到需求规格的明确、技术规划的细化、任务分解的执行,直至代码实现的自动化生成,完整地跑通了一遍规范驱动的开发流程。
这个案例展示了 Spec Kit 如何把一个模糊的想法,转化为一个边界清晰、可测试、可维护的小应用。
这个案例想说明的三件事
一、规范驱动开发的门槛不在项目难度,而在"边界条件有没有被写下来"。
番茄钟没有硬件、没有模型、没有分布式,但刷新、休眠、隐私模式、多标签页这四条边界,足以在第一版里制造 bug。写进 spec 之后,它们变成 4 条 FR 和 4 组断言 —— 规范是 bug 的清单,不是文档的仪式。
二、最小的规范单位是"可验证的约束"。
"剩余时间误差 ≤ 1 秒"能变成断言;"代码要优雅"不能。写宪章和 spec 时,凡是能用"可测量"替换"要好"的句子,都替换掉 —— 这是让 Claude 真正对齐的最低成本手段。
三、案例越简单,越能看清流程本身。
复杂业务会把注意力整个吸走,你会分不清是自己规范没写好,还是环境没配好、依赖装不上。先用一个能跑起来的小项目把流程走顺,再去接自己的业务,成功率高得多。
这个案例的可迁移之处
- Edge Cases 的重心随领域而变:番茄钟怕的是"时间漂移、存储不可用",表单怕的是"重复提交、并发写",列表怕的是"分页错乱、排序不一致"。具体怕什么会变,但方法一致 —— 都得在 spec 里写死,交给 Claude 之前先替它想一遍。
- 依赖树小到可以逐个读完:这是小案例最大的好处 —— 你能确认每一行依赖都是有意加的,也更容易判断 AI 生成的代码到底在干什么。
- 验收标准当场可验:打开浏览器点几下就知道做对没做对,不必等流水线跑完才拿到反馈。
实战建议
先写宪章,再写 spec。 小项目的宪章 30 行足够,但要有第三条这种"领域专属的不可妥协项",否则 Claude 不会在代码里加对应检查。
spec 里必须写 Edge Cases。 这是整个流程里性价比最高的一段。写不出 Edge Cases,通常说明需求本身还没想清楚。
让纯函数承担所有"会出错的算术"。 计时、金额、坐标这类计算一旦散进组件里,就只能靠人肉发现问题。抽成纯函数之后,测试可以穷举各种异常输入。
别在一次会话里跑完所有任务。 按 Phase 分会话,Setup + Foundational 一个会话,每个用户故事一个会话。
规则写进 CLAUDE.md,而不是每次在 prompt 里重复。 项目级的约束文件会自动加载,是最省事也最可靠的注入方式。
别一上来就拿最难的业务练手。 先用一个小项目把规范流程跑顺,确认自己理解了每一步在做什么,再去接真正复杂的系统。
文章大纲
- Spec Kit 介绍
- 什么是 Spec Kit
- 核心理念:规范驱动开发
- 核心特点
- Spec Kit 环境搭建详解
- 下载与安装
- 初始化项目
- init 到底干了什么
- .claude/:给模型看的「操作手册」
- .specify/:引擎侧的四块
- 跑起来之后的数据流
- 实战案例:构建番茄钟待办 Web 应用
- 为什么选一个最简单的案例
- 项目背景
- Spec Kit 五大核心命令
- 进入项目环境
- Spec Kit 五大核心命令详解
- 制定项目宪章 - /speckit-constitution
- 需求规格说明 - /speckit-specify
- 输出结构:spec.md 与 checklists/requirements.md
- 技术实现规划 - /speckit-plan
- 任务分解 - /speckit-tasks
- 代码实现 - /speckit-implement
- 总结与实践建议
- 这个案例想说明的三件事
- 这个案例的可迁移之处
- 实战建议
评论区未配置
在 .env 中填入NEXT_PUBLIC_GISCUS_REPO/NEXT_PUBLIC_GISCUS_REPOSITORY_ID/NEXT_PUBLIC_GISCUS_CATEGORY/NEXT_PUBLIC_GISCUS_CATEGORY_ID 后重新构建站点即可启用评论。
