每日壁纸

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行数职责
-constitution182写项目宪章
-specify348生成 spec.md
-plan169生成 plan.md
-tasks218生成 tasks.md
-implement229按任务执行
-converge285收敛检查
-clarify / -analyze / -checklist294 / 262 / 386可选增强
-taskstoissues120任务转 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.sh942公共库,向上找 .specify 定位仓库根
create-new-feature.sh504建分支 / 算 feature 编号 / 建目录
check-prerequisites.sh249前置检查(--json / --require-spec)
setup-plan.sh / setup-tasks.sh89 / 94生成 plan/tasks 上下文
resolve-template.sh57模板解析(支持 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.jsoninit 时的选项快照: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 之后立刻就能跑起来。

选案例的标准只有三条:

  1. 五分钟能启动 —— 读者能真正跟完整个流程,不会卡在环境上
  2. 边界条件可穷举 —— 有真实的坑值得写进 spec,而不是靠规模取胜
  3. 结果可当场验证 —— 打开浏览器就能判断做对没做对

这不代表简单项目不需要规范。恰恰相反,番茄钟这种"看起来没有技术含量"的玩具,藏着一组非常典型的坑:页面刷新后剩余时间跳变、系统休眠后计时漂移、隐私模式下 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
RequirementsFR 条目,用 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 里重复。 项目级的约束文件会自动加载,是最省事也最可靠的注入方式。

别一上来就拿最难的业务练手。 先用一个小项目把规范流程跑顺,确认自己理解了每一步在做什么,再去接真正复杂的系统。

评论区未配置

在 .env 中填入NEXT_PUBLIC_GISCUS_REPO/NEXT_PUBLIC_GISCUS_REPOSITORY_ID/NEXT_PUBLIC_GISCUS_CATEGORY/NEXT_PUBLIC_GISCUS_CATEGORY_ID 后重新构建站点即可启用评论。