news 2026/10/14 3:24:51

OpenSpec 实战演练:从“猜数字”到“暗黑模式”的规范驱动开发之旅

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
OpenSpec 实战演练:从“猜数字”到“暗黑模式”的规范驱动开发之旅

前言:

上一节我们介绍了 OpenSpec 的安装与配置,这一节我们将通过两个连贯的迭代案例——从零开发“猜数字游戏”和增加“暗黑模式”,完整演示 OpenSpec 的探索 (Explore) → 提案 (Propose) → 实施 (Implement) → 归档 (Archive)全流程。

通过这两个案例,你将亲眼见证规范文档如何在迭代中积累,并最终形成可追溯的工程知识。

1. OpenSpec 的 4 阶段工作流

OpenSpec 的工作流可分为 4 个阶段,每个阶段对应一条 OpenSpec 斜杠命令,如表 1 所示。

表 1:OpenSpec 的 4 阶段工作流

阶段命令作用
探索/openspec-explore了解代码库现状,分析现有能力与约束
提案/openspec-propose创建结构化规范文档
实施/openspec-apply-change按照 proposal 逐步实现代码,并严格对照规范验证
归档/openspec-archive-change将已完成的变更归档,形成可追溯的知识库

OpenSpec 的整体流程如图 1 所示。

图 1:OpenSpec 的整体流程


2. 前置准备:配置项目上下文

在开始实战之前,请确认已按照上一节完成 OpenSpec 的安装与初始化。若尚未初始化,需执行以下命令:

mkdir guess-number-game && cd guess-number-game openspec init

接着编辑openspec/config.yaml,填入猜数字游戏项目的技术栈信息。示例如下:

schema: spec-driven context: | Tech stack: HTML5, CSS3, Vanilla JavaScript Storage: localStorage No backend, pure frontend application rules: proposal: - Always include "## Why" and "## What Changes" sections specs: - Use Given/When/Then format for scenarios

核心提示:config.yaml中的context字段内容就是实际项目词频,以把 OpenSpec 为 AI 提供固定的上下文。例如,当项目是纯前端应用时,应填写“HTML5+CSS3+Vanilla JavaScript”。如果是 Python 后台项目,则补充本应用 Python 版本和本地使用的框架,如“Python 3.12+FastAPI+SQLite”。

配置完成后,有两种驱动方式可以选择以下两种方案驱动工作流:

  • 方式 1:OpenSpec 斜杠命令(推荐):在 TRAE 的 AI 对话中输入/openspec-explore、/openspec-propose、/openspec-apply-change、/openspec-archive-change等命令,自动触发对应的技能工作流。
  • 方式 2:手动输入指令:在 AI 对话中手动输入“请使用 OpenSpec 的 xxx Skill”指令,灵活控制每一步的操作。

3. 第一阶段:探索 (Explore)

探索阶段的目的是让 AI 深入了解代码库现状,在不写任何代码的前提下,理清现有功能、技术约束和已有规范,为后续的提案奠定坚实基础。

对于是否需要探索,可参考表 2 的判断标准。

表 2:探索阶段的适用场景判断

场景是否需要探索说明
全新项目,从零开始通常不需要代码为空,没有需要了解的现状
在已有项目基础上迭代必须进行需要了解现有实现,避免冲突
接手他人代码进行维护强烈建议摸清历史包袱和技术债
修复 Bug 或优化性能建议进行需要定位问题根源

对猜数字游戏这个案例而言,由于是一个全新项目,代码库为空,所以可以跳过探索阶段,直接进入提案阶段。


4. 第二阶段:提案 (Propose)

提案阶段是 OpenSpec 工作流的核心。执行/openspec-propose命令后,AI 会生成完整的规范化文档结构。

4.1 文档结构解析

  • proposal.md:描述为什么做(背景、目标)和做什么(功能范围、影响的能力)。
  • design.md:描述怎么做(技术方案、架构决策)。
  • specs/:能力目录(如specs/game-core/spec.md),每个功能的详细规范(数据模型、场景、验收标准)。
  • tasks.md:将实现过程拆解为可执行的任务列表。

这些文档共同构成了“规范即真理”的事实来源,后续的代码实现必须严格遵循这些文档。

4.2 实战演示:创建“猜数字游戏”

现在我们在一个纯网页应用实现“猜数字游戏”功能。在 TRAE 的 Agent 模式中,新建一个任务(即创建一个会话),输入以下提示词,触发 AI 的推理:

/openspec-propose 请为“猜数字游戏”创建规范文档

AI 执行后,会在openspec/changes目录下创建一个名为guess-number-game的子目录,并在该子目录下创建多个文件。此文件名称将是变更的任务 ID。在后续的流程中,将通过这个任务 ID 来推进流程。

执行完/openspec-propose后,AI 创建的目录和文件如下:

openspec/changes/guess-number-game/ ├── proposal.md # 提议文档 ├── design.md # 技术设计文档 ├── tasks.md # 任务清单 └── specs/ # 规范清单 ├── game-core/ # 游戏核心逻辑规范 ├── game-state/ # 游戏状态管理规范 ├── score-storage/ # 存储功能规范 ├── history-view/ # 历史记录浏览规范 └── statistics/ # 统计数据展示规范

AI 还会简单列出各个文件的要点,以便于用户审查。例如,在这个项目中,AI 列出的审查信息如下:

1. proposal.md
## Why 猜数字游戏是一个经典的互动游戏。通过网页应用形式实现,可以提供便捷的娱乐体验 ,该游戏能够锻炼玩家的逻辑推理能力,同时也记录成绩可以让玩家追踪自己的游戏历史和表现。 ## What Changes 创建猜数字游戏网页应用,支持 1~100 范围内的随机数生成 实现 7 次猜测机会机制,每次猜测后给出“太高”、“太低”或“正确”的反馈 游戏状态持久化存储到 localStorage 提供历史记录查看功能 提供游戏统计数据展示(胜率、平均猜测次数等) ## Capabilities ### New Capabilities 'game-core': 游戏核心逻辑,包括随机数生成、猜测验证和反馈提示 'game-state': 游戏状态管理,包括剩余猜测次数、当前目标数字等 'score-storage': localStorage 存储管理,自动保存游戏记录 'history-view': 历史记录查看界面,展示过往猜测记录 'statistics': 统计数据计算和展示 ### Modified Capabilities -: 无现有功能能力修改 ## Impact 新增 HTML/CSS/JS 文件实现游戏前端业务逻辑 使用 localStorage 进行数据持久化 无后端依赖,纯前端实现
2. design.md
design.md 将 proposal.md 中的“做什么”转化为“怎么做”,它确保了以下事项: 1.技术选型有依据:每个技术选择都有明确的理由,而不是“因为 AI 默认用了这个”。 2.架构设计有规划:在写代码之前明确了目录结构、localStorage 存储策略。 3.关键决策有记录:游戏核心算法(二分查找策略)、localStorage 存储策略、 响应式设计方案等决策被显式记录下来,供后续参考。 4.验收标准可追溯:每条验收标准都有对应的实现方式和验证方法。

接下来 AI 生成规范,规范有多个文件,位于 specs 目录下,使用的是 Gherkin 格式。规范详细定义了边界条件、异常处理以及用户交互细节,如下所示:

# game-core Specification ## Purpose TODO - created by archiving change guess-number-game. Update Purpose after archiving. ## Requirements ### Requirement: Generate random target number The system SHALL generate a random integer between 1 and 100 (inclusive) as the target number when starting a new game. #### Scenario: Generate target on new game - **WHEN** user starts a new game - **THEN** system generates a random integer between 1 and 100 - **AND** the generated number is stored as the target number
3. tasks.md

提案阶段的最后一步,AI 会根据技术方案生成任务列表 tasks.md,具体内容如下:

## 项目初始化与基础文件 - [ ] 1.1 创建 index.html 主页面 - [ ] 1.2 创建 styles.css 样式文件 - [ ] 1.3 创建 script.js 脚本逻辑文件 ## 实现游戏核心逻辑 - [ ] 2.1 实现随机数生成函数 (1~100) - [ ] 2.2 实现猜测验证函数 (范围检查、格式检查、重置检查) - [ ] 2.3 实现猜测比较逻辑 (太高/太低/正确) - [ ] 2.4 实现游戏状态管理 (目标数字、猜测次数、已猜测序列) ## 实现用户界面 - [ ] 3.1 实现游戏标题和说明区域 - [ ] 3.2 实现剩余次数显示组件 - [ ] 3.3 实现已猜测数字显示区域 - [ ] 3.4 实现输入框和提交按钮 - [ ] 3.5 实现反馈消息区域 - [ ] 3.6 实现游戏结果弹窗 (胜利/失败) - [ ] 3.7 添加响应式布局适配 ## 实现 localStorage 存储 - [ ] 4.1 实现游戏记录保存函数 - [ ] 4.2 实现历史记录加载函数 - [ ] 4.3 实现历史记录显示组件 - [ ] 4.4 实现统计计算逻辑 (胜率、最佳成绩) - [ ] 4.5 测试浏览器兼容性 ## 测试和优化 - [ ] 5.1 测试输入验证逻辑 - [ ] 5.2 测试游戏流程 (胜利/失败路径) - [ ] 5.3 测试 localStorage 持久化功能 - [ ] 5.4 测试响应式设计 - [ ] 5.5 修复发现的 bug ## 优化和完善 - [ ] 6.1 添加动画效果支持 (渐入渐出) - [ ] 6.2 添加键盘快捷键 - [ ] 6.3 优化 UI 样式

可以看到,我们只是使用/openspec-propose提出了一个需求,AI 就为我们创建了这么多文件。这些文件共同构成了“猜数字游戏”功能的完整事实来源。后续代码必须严格遵循这些文档。这正是规范驱动编程的“规范即真理”执行的体现。


5. 第三阶段:实施 (Implement)

在提案与规范确认无误后,即可进入实施阶段,开始编写代码。

在 TRAE 中输入以下命令,即可启动编码流程:

/openspec-apply-change guess-number-game

若不指定具体一个任务变更,则在直接写如下:

/openspec-apply-change

执行后,AI 将自动加载当前变更目录(如openspec/changes/2026-05-11-guess-number-game/)下的所有规范文件,包括 proposal.md、design.md、specs/ 及 tasks.md,并严格按照 tasks.md 中的任务顺序,逐条实现代码。每完成一项任务,AI 会自动将该任务从未完成标记为已完成。

AI 执行/openspec-apply-change后,生成或修改后的文件如下:

index.html # 主页面 css/ └── styles.css # 样式文件 js/ ├── game.js # 游戏核心逻辑 ├── storage.js # 存储管理 └── ui.js # UI 交互逻辑 openspec/changes/guess-number-game/ ├── proposal.md # 提议文档 ├── design.md # 技术设计文档 ├── specs/ # 规范清单 └── tasks.md # 任务清单 (已全部完成)

在本案例中,最重要的业务逻辑位于game.js,代码如下:

class GuessNumberGame { constructor() { this.targetNumber = null; this.remainingGuesses = 7; this.guessHistory = []; this.gameStatus = 'playing'; // playing, won, lost this.minGuesses = 1; this.maxGuesses = 100; } // 生成随机目标数字 generateTargetNumber() { return Math.floor(Math.random() * (this.maxGuesses - this.minGuesses + 1)) + this.minGuesses; } // 开始新游戏 startNewGame() { this.targetNumber = this.generateTargetNumber(); this.remainingGuesses = this.maxGuesses; this.guessHistory = []; this.gameStatus = 'playing'; this.saveCurrentGame(); return this.getGameState(); } // 验证输入 validateInput(input) { const number = parseInt(input, 10); if (isNaN(number)) { return { valid: false, message: '请输入有效的数字' }; } if (number < this.minGuesses || number > this.maxGuesses) { return { valid: false, message: `请输入 ${this.minGuesses}-${this.maxGuesses} 之间的数字` }; } return { valid: true, number }; } // 进行猜测 makeGuess(input) { if (this.gameStatus !== 'playing') { return { success: false, message: '游戏已结束,请开始新游戏', gameOver: true, won: false }; } const validation = this.validateInput(input); if (!validation.valid) { return { success: false, message: validation.message, gameOver: false, won: false }; } const guess = validation.number; this.remainingGuesses--; let result = ''; let isCorrect = false; if (guess === this.targetNumber) { result = 'correct'; isCorrect = true; this.gameStatus = 'won'; } else if (guess > this.targetNumber) { result = 'too-high'; } else { result = 'too-low'; } this.guessHistory.push({ number: guess, result: result }); // 检查是否用完所有机会 if (!isCorrect && this.remainingGuesses === 0) { this.gameStatus = 'lost'; } this.saveCurrentGame(); // 如果游戏结束,保存记录 if (this.gameStatus !== 'playing') { this.saveGameRecord(); } return { success: true, message: this.getFeedbackMessage(result, isCorrect), result: result, remainingGuesses: this.remainingGuesses, gameOver: this.gameStatus !== 'playing', won: this.gameStatus === 'won', guessHistory: this.guessHistory, targetNumber: this.gameStatus !== 'playing' ? this.targetNumber : null }; } // 获取反馈消息 getFeedbackMessage(result, isCorrect) { if (isCorrect) { return `恭喜!你猜对了!答案就是 ${this.targetNumber}!`; } if (this.gameStatus === 'lost') { return ` 游戏结束!正确答案是 ${this.targetNumber},再试一次吧!`; } if (result === 'too-high') { return `太高了!你还有 ${this.remainingGuesses} 次机会`; } return `太低了!你还有 ${this.remainingGuesses} 次机会`; } // 获取游戏状态 getGameState() { return { targetNumber: this.gameStatus !== 'playing' ? this.targetNumber : null, remainingGuesses: this.remainingGuesses, guessHistory: this.guessHistory, gameStatus: this.gameStatus, maxGuesses: this.maxGuesses }; } // 保存当前游戏状态到 localStorage saveCurrentGame() { try { const gameState = { targetNumber: this.targetNumber, remainingGuesses: this.remainingGuesses, guessHistory: this.guessHistory, gameStatus: this.gameStatus }; localStorage.setItem('guessNumberGame_current', JSON.stringify(gameState)); } catch (e) { console.error('保存游戏状态失败:', e); } } // 从 localStorage 恢复游戏状态 loadCurrentGame() { try { const saved = localStorage.getItem('guessNumberGame_current'); if (saved) { const gameState = JSON.parse(saved); this.targetNumber = gameState.targetNumber; this.remainingGuesses = gameState.remainingGuesses; this.guessHistory = gameState.guessHistory; this.gameStatus = gameState.gameStatus; return true; } } catch (e) { console.error('加载游戏状态失败:', e); } return false; } // 清除当前游戏状态 clearCurrentGame() { try { localStorage.removeItem('guessNumberGame_current'); } catch (e) { console.error('清除游戏状态失败:', e); } } // 保存游戏记录 saveGameRecord() { const record = { timestamp: Date.now(), targetNumber: this.targetNumber, guesses: this.guessHistory.length, isWon: this.gameStatus === 'won', guessHistory: this.guessHistory }; try { const records = StorageManager.getRecords(); records.unshift(record); StorageManager.saveRecords(records); } catch (e) { console.error('保存游戏记录失败:', e); } } }

5.1 代码与规范的严格对齐

AI 执行/openspec-apply-change后,生成的代码必须逐条对照规范文档中的验收标准进行验证。表 3 列出了针对核心验收标准的验证要点及验证方法。

表 3:验证要点及验证方法

验证要点验证方法
随机数生成进行多次游戏,确认目标数字在 1~100 范围内且随机
输入验证测试 0、101、非数字、空输入、重复数字
反馈显示猜大、猜小、猜对,分别验证提示文字
次数限制连续猜错 7 次后检查游戏是否结束
历史记录刷新页面后检查记录是否存在
统计数据验证胜率计算公式是否正确

5.2 界面效果

实施阶段确保生成的代码与规范严格对齐,具体表现在以下几方面:

  1. 游戏逻辑与规范一致:随机数范围、猜测次数限制、反馈规则与规范文档完全一致。
  2. 存储机制与规范一致:localStorage键名、数据结构与规范文档完全一致。
  3. 验收标准可验证:每条验收标准都有对应的代码实现和验证方法。
  4. 技术约束被遵守:纯前端实现,无后端依赖,响应式设计。

6. 第四阶段:归档 (Archive)

在功能实现并通过验收后,我们需要将当前的变更从“活跃状态”转变为“历史资产”。

6.1 执行归档命令

在 TRAE 中输入如下命令,将当前变更从openspec/changes/移动到openspec/changes/archive/,完成归档:

# 在 TRAE 中输入 /openspec-archive-change guess-number-game

归档不仅仅是“移动文件”,它标志着该功能的规范文档已进入只读状态,除非有新的变更提案,否则不再修改。归档后的文件会纳入版本管理,成为项目历史的一部分。手动执行下面的命令,将规范文档纳入版本管理,和代码一起同源管理:

git add openspec/changes/archive/2026-05-11-guess-number-game/ git commit -m "feat: archive guess-number-game change"

6.2 归档的价值

归档赋予规范文档以下 3 个重要属性:

  1. 可追溯性:每个功能的完整需求、设计和实现决策都有据可查。
  2. 可复用性:遇到类似功能时,直接参考已归档的规范,无须从头设计。
  3. 可审计性:规范文档纳入版本管理,团队可以看到每个变更的完整历史。

7. 进阶实战:增加暗黑模式

接下来为猜数字游戏增加暗黑模式,演示 OpenSpec 如何在已有功能基础上新增能力。因为本次变更是增加新功能,所以使用/openspec-explore进行探索。

7.1 探索:了解现有实现

在 TRAE 中,通过以下提示词即可触发探索。AI 会探索当前的项目目录、功能,并给出实现建议。

/openspec-explore 为猜数字游戏增加暗黑模式

AI 会基于对现有代码的分析,给出相应的设计方案。例如,在本案例中,AI 给出了下列建议:

当前代码结构分析
├── index.html # 主页面(无主题切换按钮) ├── css/styles.css # 样式文件(纯亮色主题) ├── js/game.js # 游戏核心逻辑 ├── js/storage.js # 存储管理 └── js/ui.js # UI 交互逻辑
暗黑模式实现方案

方案:CSS 变量 + JavaScript 切换
核心思路:使用 CSS 自定义属性 (CSS Variables) 定义主题颜色,通过 JavaScript 切换'dark'class 来改变主题。

需要修改的文件

文件修改内容
index.html添加主题切换按钮
css/styles.css添加 CSS 变量和暗黑模式样式
js/ui.js添加主题切换逻辑和持久化

配色方案对比

元素亮色模式暗黑模式
背景渐变#667eea → #764ba2#1a1a2e → #16213e
容器背景white#1f1f3a
游戏区域#f8f9fa#2a2a4a
文字颜色#333#e0e0e0
边框颜色#eee#3a3a5a

实现步骤规划

  1. 修改 CSS:定义 CSS 变量,添加.dark类样式
  2. 修改 HTML:添加主题切换按钮(太阳/月亮图标)
  3. 修改 JS:添加切换逻辑,保存到 localStorage
  4. 处理边界情况:页面加载时获取用户偏好

潜在风险与考虑

  • 图标颜色:FontAwesome 图标在暗黑模式下可能需要调整
  • 动画效果:确保动画在两种模式下都正常显示
  • 兼容性:CSS 变量在现代浏览器中支持良好
  • 性能:切换主题是纯 CSS 操作,性能开销小

在探索阶段可以和 AI 反复对话,确定方案。在方案确定好后,可以直接使用/openspec-propose进行提案。

7.2 提案:定义暗黑模式规范

在 TRAE 中使用以下提示词进行提案,提案会自动使用探索阶段确定的方案。

/openspec-propose 为猜数字游戏增加暗黑模式

TRAE 执行完提案后,会在openspec/changes目录下创建一个名为add-dark-mode的子目录,其中add-dark-mode就是本次变更的任务 ID。

和前面的流程一样,提案会创建相应的proposal.md、design.md、规范文档和tasks.md,这里不再赘述。

7.3 归档:形成知识库

在完成了功能开发并归档后可以看到,openspec/changes/archive/目录下保存了已完成变更的完整规范文档:

  1. 2026-05-11-guess-number-game/:猜数字游戏的原始实现。
  2. 2026-05-11-add-dark-mode/:暗黑模式的功能迭代。

这个目录结构本身就是项目的历史记录。当有团队新成员加入时,其只需浏览openspec/changes/archive/目录,就能了解项目的完整演进历史——每个功能是什么时候加的、为什么加、设计了什么、是如何实现的,这些信息都在规范文档中,而不是散落在 Git 提交历史和办公聊天工具中。


8. 其他操作与总结

除了上述核心步骤,OpenSpec 还提供以下辅助操作:

  1. 规范审查:在规范文档编写完成后,提交给团队成员审查,重点检查功能范围是否清晰、数据模型是否合理、验收标准是否可验证。
  2. 规范复用:遇到类似功能时,直接参考已归档的规范文档,修改功能范围和数据模型即可快速复用。

此外,4 个阶段可根据任务复杂度灵活裁剪,具体建议如表 4 所示。

表 4:不同复杂度任务的工作流裁剪建议

任务复杂度推荐工作流
简单任务(修复 Bug、小改动)直接实施,可跳过探索和提案
中等任务(新增单个功能)提案→实施→归档
功能迭代(在已有功能基础上新增功能)探索→提案→实施→归档
复杂任务(涉及多个功能、架构变更)完整 4 阶段

OpenSpec 的四个阶段构成了一个从“模糊需求”到“可追溯规范”的完整循环。探索阶段让 AI 了解现状,提案阶段让 AI 明确目标,实施阶段让 AI 按规范行动,归档阶段将这一切固化为工程资产。这正是规范驱动编程的核心价值——通过结构化的流程保障质量下限,并将每次迭代的知识转化为团队可传承的资产。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 3:24:45

收藏这份9000字知识库指南:从零理解RAG、向量数据库到GraphRAG,小白程序员必看!

本文以软件工程和AI系统架构视角&#xff0c;深入解析知识库、RAG、向量数据库、图数据库、知识图谱及GraphRAG等核心概念。内容涵盖各术语定义、知识加工成AI可检索知识的流程、向量生成与数据库检索原理、图数据库存储查询方式、知识图谱构建方法&#xff0c;以及RAG和GraphR…

作者头像 李华
网站建设 2026/10/14 3:23:21

66-基于ZigBee的温室大棚环境监控系统设计与实现

1、项目介绍1.1 背景及意义我国是农业大国&#xff0c;设施农业是现代农业发展的重要组成部分&#xff0c;温室大棚作为设施农业的典型载体&#xff0c;能够突破自然气候条件限制&#xff0c;实现果蔬、花卉等农作物反季节栽培&#xff0c;大幅提升土地产出效率与农产品经济效…

作者头像 李华
网站建设 2026/10/14 3:23:17

Flutter多宽高比网格布局:自定义引擎突破childAspectRatio限制

1. 为什么默认的 childAspectRatio 解决不了“多宽高比网格”1.1 先还原一下这个需求的真实形态我在接手这个项目时&#xff0c;需求方给的设计稿是这样的&#xff1a;首页信息流里混着横版视频封面、竖版商品主图、接近正方形的头像卡&#xff0c;偶尔还有一张超长条的活动横幅…

作者头像 李华
网站建设 2026/10/14 3:23:11

样章-第03讲-FreeRTOS在16KB-RAM上的裁剪与陷阱

第 3 讲 | FreeRTOS 在 16KB RAM 上的裁剪与陷阱【本讲要点】FreeRTOS 默认配置是给 64KB RAM 的芯片准备的。搬进 16KB 的板子&#xff0c;你不裁剪它&#xff0c;它就撑死你。一、那 10KB 是怎么来的 上一讲我们算过&#xff1a;一颗 16KB RAM 的芯片&#xff0c;FreeRTOS He…

作者头像 李华
网站建设 2026/10/14 3:22:54

DeepEP:专为MoE模型打造的All-to-All通信库,如何优化训练与推理?

1. 为什么DeepSeek开源周第二天要放出一个通信库DeepSeek 开源周第二天发布的 DeepEP&#xff0c;一眼看过去并不是那种能立刻引爆社交媒体的炫酷项目。没有漂亮的 Demo&#xff0c;没有刷榜的数据&#xff0c;它就是一个通信库。但懂行的人都知道&#xff0c;在大模型训练和推…

作者头像 李华