OpenSpec 项目学习指南

面向首次接触该项目的开发者,帮助你从零快速上手并深入理解 OpenSpec。


目录

  1. 项目简介
  2. 目录结构说明
  3. 架构设计
  4. 核心流程解析
  5. 关键设计与实现
  6. 环境搭建与运行
  7. 推荐学习路线
  8. 常见问题与注意事项

1. 项目简介

一句话说明

OpenSpec 是一个 AI 原生的规格驱动开发(Spec-Driven Development)CLI 工具,让开发者和 AI 编程助手在写代码之前先就”要构建什么”达成一致,从而提升 AI 辅助开发的可预测性与可维护性。

核心功能列表

功能说明
项目初始化openspec init — 在项目中创建规格目录结构,并为 20+ 种 AI 工具生成 Skill/Command 文件
变更管理每个功能变更对应一个独立文件夹,包含提案、规格、设计、任务清单等制品(Artifact)
Delta 规格用 ADDED / MODIFIED / REMOVED / RENAMED 四种操作描述规格变更,而非重写全文
规格归档openspec archive — 将 Delta 规格合并到主规格库,并将变更文件夹移入历史存档
规格验证openspec validate — 校验规格和变更的结构合法性,支持并发批量验证
工作流 Schema可自定义制品依赖图(如 proposal → specs → design → tasks),支持内置和项目级 Schema
多工具集成支持 Claude Code、Cursor、GitHub Copilot、Gemini CLI 等 20+ AI 工具的 Skill/Command 生成
Shell 补全为 bash / zsh / fish / powershell 生成命令行自动补全脚本
匿名遥测仅收集命令名和版本号,可随时关闭

技术栈总览

类别技术版本
语言TypeScript^5.9.3
运行时Node.js≥ 20.19.0
包管理pnpm
CLI 框架commander^14.0.0
交互式提示@inquirer/prompts、@inquirer/core^7.8.0 / ^10.2.2
终端样式chalk^5.5.0
加载动画ora^8.2.0
文件 Globfast-glob^3.3.3
YAML 解析yaml^2.8.2
数据校验zod^4.0.17
遥测posthog-node^5.20.0
测试框架vitest^3.2.4
模块系统ESM("type": "module"

2. 目录结构说明

OpenSpec/
├── bin/                          # CLI 可执行入口(构建产物)
│   └── openspec.js               # npm 全局安装后的入口脚本

├── src/                          # 源代码(TypeScript)
│   ├── cli/
│   │   └── index.ts              # ★ CLI 主入口:注册所有命令,挂载 Hook
│   │
│   ├── commands/                 # 命令层:每个文件对应一个顶层命令
│   │   ├── change.ts             # openspec change(已废弃,向后兼容)
│   │   ├── completion.ts         # openspec completion(Shell 补全管理)
│   │   ├── config.ts             # openspec config(全局配置管理)
│   │   ├── feedback.ts           # openspec feedback(提交反馈)
│   │   ├── schema.ts             # openspec schema(Schema 管理)
│   │   ├── show.ts               # openspec show(查看变更/规格详情)
│   │   ├── spec.ts               # openspec spec(规格相关命令)
│   │   ├── validate.ts           # openspec validate(验证命令)
│   │   └── workflow/             # 工作流命令组(status/instructions/templates 等)
│   │
│   ├── core/                     # 核心业务逻辑层
│   │   ├── config.ts             # AI 工具配置表(AI_TOOLS 常量)
│   │   ├── config-schema.ts      # 全局配置的 Zod Schema 及工具函数
│   │   ├── global-config.ts      # 全局配置读写(~/.config/openspec/config.json)
│   │   ├── project-config.ts     # 项目配置读写(openspec/config.yaml)
│   │   ├── profiles.ts           # 工作流 Profile 定义(core / custom)
│   │   ├── init.ts               # openspec init 核心逻辑
│   │   ├── update.ts             # openspec update 核心逻辑
│   │   ├── archive.ts            # openspec archive 核心逻辑
│   │   ├── list.ts               # openspec list 核心逻辑
│   │   ├── view.ts               # openspec view(交互式仪表盘)
│   │   ├── specs-apply.ts        # Delta 规格合并引擎(核心算法)
│   │   ├── migration.ts          # 旧版本迁移逻辑
│   │   ├── legacy-cleanup.ts     # 清理旧版遗留文件
│   │   ├── available-tools.ts    # 检测项目中已安装的 AI 工具
│   │   ├── profile-sync-drift.ts # 检测 Profile 与实际文件的同步状态
│   │   │
│   │   ├── artifact-graph/       # 制品依赖图(DAG)解析与状态计算
│   │   ├── command-generation/   # 为各 AI 工具生成 Slash Command 文件
│   │   │   └── adapters/         # 各工具的适配器(Claude、Cursor、Gemini 等)
│   │   ├── completions/          # Shell 补全脚本生成与安装
│   │   ├── converters/           # 格式转换工具
│   │   ├── parsers/              # Markdown 解析器(规格、Delta 解析)
│   │   │   ├── markdown-parser.ts
│   │   │   └── requirement-blocks.ts  # ★ Delta 规格解析核心
│   │   ├── schemas/              # 内置 Schema 定义(spec-driven 等)
│   │   ├── templates/            # 制品模板文件(proposal.md、tasks.md 等)
│   │   │   └── workflows/        # 各工作流的 Skill 模板
│   │   └── validation/           # 规格验证器
│   │       └── validator.ts      # ★ 验证器核心
│   │
│   ├── prompts/
│   │   └── searchable-multi-select.ts  # 自定义可搜索多选交互组件
│   │
│   ├── telemetry/                # 匿名遥测模块
│   │   ├── index.ts              # 遥测主逻辑(PostHog)
│   │   └── config.ts             # 遥测配置持久化
│   │
│   ├── ui/                       # 终端 UI 组件
│   │   ├── ascii-patterns.ts     # ASCII 艺术图案
│   │   └── welcome-screen.ts     # 欢迎界面
│   │
│   └── utils/                    # 通用工具函数
│       ├── file-system.ts        # 文件系统操作封装
│       ├── item-discovery.ts     # 发现项目中的变更/规格列表
│       ├── match.ts              # 模糊匹配(用于错误提示)
│       ├── shell-detection.ts    # 检测当前 Shell 类型
│       ├── task-progress.ts      # 解析 tasks.md 中的任务完成进度
│       ├── change-utils.ts       # 变更相关工具函数
│       └── command-references.ts # 命令引用解析

├── schemas/                      # 内置工作流 Schema 定义(YAML)
│   └── spec-driven/
│       └── schema.yaml           # 默认 Schema:proposal→specs→design→tasks

├── docs/                         # 用户文档
│   ├── getting-started.md        # 快速上手
│   ├── concepts.md               # 核心概念(必读)
│   ├── cli.md                    # CLI 命令完整参考
│   ├── commands.md               # AI Slash 命令参考
│   ├── workflows.md              # 工作流模式
│   └── customization.md          # 自定义 Schema 和配置

├── test/                         # 测试文件(与 src/ 结构对应)
├── scripts/                      # 构建辅助脚本
│   └── postinstall.js            # npm install 后自动执行的脚本
├── build.js                      # 构建脚本(调用 tsc)
├── package.json                  # 项目元信息与依赖
└── tsconfig.json                 # TypeScript 编译配置

3. 架构设计

整体架构模式

OpenSpec 采用分层架构,从外到内依次为:

┌─────────────────────────────────────────────────────┐
│                   CLI 入口层                         │
│         src/cli/index.ts(Commander 注册)           │
└──────────────────────┬──────────────────────────────┘
                       │ 调用
┌──────────────────────▼──────────────────────────────┐
│                   命令层(Commands)                  │
│   src/commands/*.ts — 参数解析、交互提示、错误处理    │
└──────────────────────┬──────────────────────────────┘
                       │ 调用
┌──────────────────────▼──────────────────────────────┐
│                   核心业务层(Core)                  │
│   src/core/*.ts — 业务逻辑、文件操作、算法            │
│   ├── 解析器(parsers/)                             │
│   ├── 验证器(validation/)                          │
│   ├── 制品图(artifact-graph/)                      │
│   └── 命令生成(command-generation/)                │
└──────────────────────┬──────────────────────────────┘
                       │ 读写
┌──────────────────────▼──────────────────────────────┐
│                   文件系统(持久层)                  │
│   openspec/specs/、openspec/changes/、               │
│   ~/.config/openspec/config.json                    │
└─────────────────────────────────────────────────────┘

设计理念

  • 无数据库:所有状态以 Markdown 文件和 YAML 配置存储,人类可读,Git 友好
  • 无服务端:纯本地 CLI,无需网络(遥测除外,且可关闭)
  • 渐进式:核心 Profile 只有 4 个工作流,扩展 Profile 提供更多,用户按需选择

核心模块划分与职责

模块文件职责
CLI 路由src/cli/index.ts注册命令、挂载全局 Hook(遥测、颜色控制)
初始化src/core/init.ts创建目录结构、检测 AI 工具、生成 Skill/Command 文件
更新src/core/update.ts检测版本漂移、重新生成 Skill/Command 文件
归档src/core/archive.ts验证变更、合并 Delta 规格、移动文件夹
Delta 合并src/core/specs-apply.ts解析 Delta 操作并应用到主规格,核心算法
验证src/core/validation/validator.ts校验规格和变更的 Markdown 结构
全局配置src/core/global-config.ts读写 ~/.config/openspec/config.json
项目配置src/core/project-config.ts读写 openspec/config.yaml
Profilesrc/core/profiles.ts定义 core/custom 两种 Profile 及其工作流集合
遥测src/telemetry/index.ts匿名上报命令名和版本(PostHog)

模块间调用关系与数据流向

用户执行命令


src/cli/index.ts
  ├── preAction Hook → 遥测(trackCommand)
  ├── 路由到对应 Command 类
  └── postAction Hook → 遥测关闭(shutdown)


     Command 类(src/commands/)
       ├── 解析 CLI 参数
       ├── 交互式提示(@inquirer/prompts)
       └── 调用 Core 层


          Core 层(src/core/)
            ├── 读取全局配置(global-config.ts)
            ├── 读取项目配置(project-config.ts)
            ├── 操作文件系统(utils/file-system.ts)
            └── 输出结果到终端(chalk + ora)

4. 核心流程解析

4.1 应用启动流程

bin/openspec.js(npm 全局安装的入口)

     └── import src/cli/index.ts

              ├── 创建 Commander program 实例
              ├── 注册全局选项(--no-color)
              ├── 注册 preAction Hook(遥测通知 + 命令追踪)
              ├── 注册 postAction Hook(遥测关闭)
              ├── 注册所有子命令(init / update / list / archive / validate ...)
              └── program.parse()  ← 解析 process.argv,触发对应命令

关键代码(src/cli/index.ts

// 全局 Hook:每个命令执行前都会触发
program.hook('preAction', async (thisCommand, actionCommand) => {
  // 1. 处理 --no-color 全局选项
  if (opts.color === false) {
    process.env.NO_COLOR = '1';
  }
  // 2. 首次运行时显示遥测通知
  await maybeShowTelemetryNotice();
  // 3. 追踪命令执行(仅命令名+版本,无参数内容)
  const commandPath = getCommandPath(actionCommand); // e.g. "archive", "change:show"
  await trackCommand(commandPath, version);
});

4.2 流程一:openspec init(项目初始化)

这是最核心的命令,完整流程如下:

openspec init

     ├── 1. 检测旧版遗留文件(legacy-cleanup.ts)
     │        └── 若存在旧文件,提示用户确认清理

     ├── 2. 读取全局配置(global-config.ts)
     │        └── 获取 profile(core/custom)和 delivery(both/skills/commands)

     ├── 3. 检测项目中已有的 AI 工具目录(available-tools.ts)
     │        └── 扫描 .claude/、.cursor/、.github/ 等目录是否存在

     ├── 4. 工具选择(交互式多选 或 --tools 参数)
     │        └── searchable-multi-select 组件

     ├── 5. 创建目录结构
     │        openspec/specs/
     │        openspec/changes/
     │        openspec/changes/archive/

     ├── 6. 生成 Skill 和 Command 文件(核心步骤)
     │        ├── 根据 profile 获取工作流列表(getProfileWorkflows)
     │        ├── 获取 Skill 模板(getSkillTemplates)
     │        ├── 获取 Command 内容(getCommandContents)
     │        └── 对每个选中的 AI 工具:
     │             ├── 写入 .claude/skills/opsx-propose/SKILL.md 等
     │             └── 写入 .claude/commands/opsx-propose.md 等

     ├── 7. 创建 openspec/config.yaml(若不存在)

     └── 8. 显示成功信息和下一步提示

关键代码(src/core/init.ts

// 根据 profile 决定生成哪些工作流的 Skill
const profile: Profile = this.resolveProfileOverride() ?? globalConfig.profile ?? 'core';
const workflows = getProfileWorkflows(profile, globalConfig.workflows);
// core profile 返回: ['propose', 'explore', 'apply', 'archive']

const skillTemplates = shouldGenerateSkills ? getSkillTemplates(workflows) : [];
const commandContents = shouldGenerateCommands ? getCommandContents(workflows) : [];

4.3 流程二:openspec archive(变更归档)

归档是整个工作流的终点,也是 Delta 规格合并的触发点:

openspec archive add-dark-mode

     ├── 1. 验证变更(Validator.validateChangeDeltaSpecs)
     │        └── 检查 specs/ 下的 Delta 规格格式是否合法

     ├── 2. 检查任务完成进度(getTaskProgressForChange)
     │        └── 解析 tasks.md 中 [x] 和 [ ] 的数量
     │        └── 若有未完成任务,提示用户确认

     ├── 3. 查找需要合并的规格(findSpecUpdates)
     │        └── 扫描 openspec/changes/add-dark-mode/specs/ 目录

     ├── 4. 构建更新后的规格内容(buildUpdatedSpec)
     │        ├── 解析 Delta 规格(parseDeltaSpec)
     │        ├── 预验证:检查重复、冲突
     │        └── 按顺序应用:RENAMED → REMOVED → MODIFIED → ADDED

     ├── 5. 验证合并后的规格(Validator.validateSpecContent)

     ├── 6. 写入更新后的主规格文件(writeUpdatedSpec)
     │        └── openspec/specs/ui/spec.md(更新)

     └── 7. 移动变更文件夹到归档目录
              openspec/changes/add-dark-mode/
              → openspec/changes/archive/2025-01-24-add-dark-mode/

4.4 流程三:Delta 规格合并算法(specs-apply.ts

这是项目最核心的算法,理解它是理解 OpenSpec 的关键:

// Delta 规格示例(openspec/changes/add-dark-mode/specs/ui/spec.md)
// ## ADDED Requirements
// ### Requirement: Theme Selection
// ...
// ## MODIFIED Requirements
// ### Requirement: Accessibility
// ...
// ## REMOVED Requirements
// ### Requirement: High Contrast Mode

合并步骤(顺序严格):

1. parseDeltaSpec(changeContent)
   → 解析出 { added: [], modified: [], removed: [], renamed: [] }

2. 预验证(Pre-validation)
   → 检查同一 section 内是否有重名需求
   → 检查跨 section 冲突(如同一需求既在 ADDED 又在 REMOVED)

3. 加载目标规格(targetContent)
   → 读取 openspec/specs/ui/spec.md
   → 若不存在则创建骨架文件

4. 提取需求块(extractRequirementsSection)
   → 解析 ## Requirements 下的所有 ### Requirement: xxx 块
   → 构建 Map<normalizedName, RequirementBlock>

5. 按顺序应用操作:
   RENAMED → REMOVED → MODIFIED → ADDED
   (顺序重要:先重命名再修改,避免找不到目标)

6. 重组规格文件
   → 保持原有顺序,新增的追加到末尾
   → 去除多余空行

4.5 流程四:openspec validate(规格验证)

openspec validate --all

     ├── 发现所有变更和规格(getActiveChangeIds / getSpecIds)

     ├── 构建并发任务队列(默认并发数 6,可通过 OPENSPEC_CONCURRENCY 调整)

     └── 对每个条目:
          ├── 变更(change)→ validator.validateChangeDeltaSpecs(changeDir)
          │    └── 检查 specs/ 下每个 spec.md 的 Delta 格式
          └── 规格(spec)→ validator.validateSpec(file)
               └── 检查 ## Purpose、## Requirements、### Requirement:、#### Scenario: 等结构

5. 关键设计与实现

5.1 设计模式

命令模式(Command Pattern)

每个 CLI 命令对应一个 Command 类(如 InitCommandArchiveCommandListCommand),封装了该命令的完整执行逻辑:

// src/core/archive.ts
export class ArchiveCommand {
  async execute(changeName?: string, options = {}): Promise<void> {
    // 完整的归档逻辑
  }
}

适配器模式(Adapter Pattern)

src/core/command-generation/adapters/ 目录下为每种 AI 工具实现了适配器,统一接口生成不同格式的 Command 文件:

// 不同工具的 Command 文件路径格式不同
// Claude: .claude/commands/opsx-propose.md
// Cursor: .cursor/rules/opsx-propose.mdc
// 适配器屏蔽了这些差异
const adapter = CommandAdapterRegistry.get(tool.value);
const generatedCommands = generateCommands(commandContents, adapter);

策略模式(Strategy Pattern)

Profile 系统通过 getProfileWorkflows 函数,根据不同 Profile 返回不同的工作流集合:

// src/core/profiles.ts
export function getProfileWorkflows(profile: Profile, customWorkflows?: string[]) {
  if (profile === 'custom') {
    return customWorkflows ?? [];  // 用户自定义工作流
  }
  return CORE_WORKFLOWS;  // ['propose', 'explore', 'apply', 'archive']
}

5.2 数据模型

OpenSpec 没有数据库,所有数据以文件形式存储:

全局配置~/.config/openspec/config.json):

{
  "profile": "core",
  "delivery": "both",
  "workflows": [],
  "featureFlags": {}
}
  • profile"core""custom",决定安装哪些工作流
  • delivery"both" / "skills" / "commands",决定生成 Skill 文件还是 Command 文件
  • workflows:custom profile 下用户选择的工作流列表

项目配置openspec/config.yaml):

schema: spec-driven   # 使用的工作流 Schema
context: |            # 注入到所有制品指令中的项目上下文(可选,最大 50KB)
  This is a React + TypeScript project...
rules:                # 针对特定制品的额外规则(可选)
  proposal:
    - "Always include a rollback plan"

规格文件openspec/specs/<domain>/spec.md):

# Auth Specification

## Purpose
...

## Requirements

### Requirement: User Authentication
The system SHALL issue a JWT token upon successful login.

#### Scenario: Valid credentials
- GIVEN a user with valid credentials
- WHEN the user submits login form
- THEN a JWT token is returned

变更文件夹openspec/changes/<name>/):

add-dark-mode/
├── proposal.md       # 提案:为什么做、做什么
├── design.md         # 设计:怎么做
├── tasks.md          # 任务清单:- [ ] 1.1 ...
└── specs/
    └── ui/
        └── spec.md   # Delta 规格:ADDED/MODIFIED/REMOVED

5.3 配置读取的两层结构

OpenSpec 使用两层配置,职责分离:

全局配置(~/.config/openspec/config.json)
  └── 控制 CLI 行为:profile、delivery、workflows
      适用于该用户的所有项目

项目配置(openspec/config.yaml)
  └── 控制项目内容:schema、context、rules
      适用于当前项目,可提交到 Git

5.4 错误处理策略

  • CLI 层:所有命令用 try/catch 包裹,失败时用 ora().fail() 显示错误,process.exit(1) 退出
  • 遥测:所有遥测操作静默失败(catch {} 不抛出),确保遥测问题不影响 CLI 正常使用
  • 配置解析:使用 Zod 的 safeParse() 进行字段级别的弹性解析,部分字段无效时仍返回有效的部分配置,并打印 console.warn
  • 文件操作:Windows 下 fs.rename 可能因权限问题失败,archive.ts 中实现了 copy-then-remove 的降级策略
// src/core/archive.ts — Windows 兼容的目录移动
async function moveDirectory(src: string, dest: string): Promise<void> {
  try {
    await fs.rename(src, dest);
  } catch (err: any) {
    if (err?.code === 'EPERM' || err?.code === 'EXDEV') {
      // 降级:先复制再删除
      await copyDirRecursive(src, dest);
      await fs.rm(src, { recursive: true, force: true });
    } else {
      throw err;
    }
  }
}

5.5 遥测与隐私

遥测模块(src/telemetry/index.ts)采用隐私优先设计

  • 仅收集:命令名(如 "archive")+ 版本号(如 "1.2.0"
  • 不收集:命令参数、文件路径、文件内容、用户信息
  • 匿名 ID:随机 UUID,与用户身份无关
  • 自动禁用:CI 环境(CI=true)下自动关闭
  • 关闭方式:export OPENSPEC_TELEMETRY=0export DO_NOT_TRACK=1

6. 环境搭建与运行

环境依赖

  • Node.js ≥ 20.19.0(必须)
  • pnpm(开发时使用,npm install -g pnpm
  • Git(可选,用于提交反馈)

作为用户安装使用

# 全局安装
npm install -g @fission-ai/openspec@latest

# 在你的项目中初始化
cd your-project
openspec init

# 查看所有命令
openspec --help

作为开发者本地运行

# 1. 克隆仓库
git clone https://github.com/Fission-AI/OpenSpec.git
cd OpenSpec

# 2. 安装依赖
pnpm install

# 3. 构建(TypeScript → JavaScript)
pnpm run build

# 4. 本地测试 CLI(构建后运行)
pnpm run dev:cli

# 5. 监听模式(修改代码后自动重新编译)
pnpm run dev

# 6. 运行测试
pnpm test

# 7. 运行测试(监听模式)
pnpm test:watch

# 8. 查看测试覆盖率
pnpm test:coverage

关键环境变量说明

变量说明默认值
OPENSPEC_TELEMETRY=0关闭遥测开启
DO_NOT_TRACK=1关闭遥测(通用标准)
CI=true自动关闭遥测(CI 环境)
OPENSPEC_CONCURRENCY批量验证的并发数6
NO_COLOR禁用终端颜色输出
EDITOR / VISUALopenspec config edit 使用的编辑器
XDG_CONFIG_HOME全局配置目录(XDG 规范)~/.config
XDG_DATA_HOME全局数据目录(XDG 规范)~/.local/share

构建产物说明

pnpm run build
# 等价于:node build.js → 调用 tsc
# 输出到 dist/ 目录(TypeScript 编译为 JavaScript)
# bin/openspec.js 是入口,引用 dist/ 中的代码

7. 推荐学习路线

阶段一:入门(理解概念,约 1-2 小时)

目标:理解 OpenSpec 解决什么问题,以及核心概念

  1. 阅读 README.md — 了解项目定位和快速示例
  2. 阅读 docs/concepts.md必读,理解以下核心概念:
    • Spec(规格):描述系统行为的 Markdown 文件
    • Change(变更):一个功能变更对应的文件夹
    • Artifact(制品):变更文件夹中的各类文档(proposal、specs、design、tasks)
    • Delta 规格:用 ADDED/MODIFIED/REMOVED 描述规格变化
    • Archive(归档):将 Delta 合并到主规格,完成变更
  3. 阅读 docs/getting-started.md — 跟着示例走一遍完整流程
  4. 实际运行:在一个测试项目中执行 openspec initopenspec list

阶段二:进阶(理解代码架构,约 3-5 小时)

目标:理解代码如何实现这些概念

  1. CLI 入口:阅读 src/cli/index.ts
    • 理解 Commander 如何注册命令
    • 理解 preAction / postAction Hook 的作用
  2. 初始化流程:阅读 src/core/init.ts
    • 理解 AI 工具检测、Skill 生成的完整流程
    • 关注 generateSkillsAndCommands 方法
  3. Delta 合并算法:阅读 src/core/specs-apply.ts(最重要)
    • 理解 parseDeltaSpec 如何解析 Delta 规格
    • 理解 buildUpdatedSpec 如何按顺序应用操作
  4. 归档流程:阅读 src/core/archive.ts
    • 理解验证 → 合并 → 移动的完整流程
  5. 配置系统:阅读 src/core/global-config.tssrc/core/project-config.ts
    • 理解两层配置的职责划分
    • 理解 XDG 规范的配置目录

阶段三:实践(动手修改,约 4-8 小时)

目标:通过实际修改验证理解

  1. 小任务 1:为 openspec list 命令添加一个新的排序选项(如按字母倒序)

    • 涉及文件:src/cli/index.ts(添加选项)、src/core/list.ts(实现逻辑)
  2. 小任务 2:在 openspec validate 输出中添加验证耗时统计

    • 涉及文件:src/commands/validate.ts
  3. 小任务 3:为一个新的 AI 工具添加适配器

    • 参考 src/core/command-generation/adapters/ 下的现有适配器
    • src/core/config.tsAI_TOOLS 数组中添加新工具
  4. 小任务 4:阅读并运行测试文件

    • test/core/archive.test.ts — 理解归档逻辑的测试方式
    • test/core/validation.test.ts — 理解验证器的测试方式
    • 运行 pnpm test 确认所有测试通过

8. 常见问题与注意事项

容易踩的坑

1. ESM 模块系统

项目使用 "type": "module"(纯 ESM),所有 import 语句必须带 .js 扩展名(即使源文件是 .ts):

// ✅ 正确
import { Validator } from '../core/validation/validator.js';

// ❌ 错误(会导致运行时找不到模块)
import { Validator } from '../core/validation/validator';

2. 动态 import 用于延迟加载

部分模块(如 @inquirer/prompts)使用动态 import() 延迟加载,避免非交互场景的启动开销:

// 只在需要交互时才加载
const { confirm } = await import('@inquirer/prompts');

3. Delta 规格的操作顺序

合并时操作顺序固定为 RENAMED → REMOVED → MODIFIED → ADDED,不可颠倒。如果先 MODIFIED 再 RENAMED,会因为找不到旧名称而报错。

4. openspec change 命令已废弃

openspec change showopenspec change list 等命令已废弃,应使用顶层命令:

  • openspec show 替代 openspec change show
  • openspec list 替代 openspec change list
  • openspec validate 替代 openspec change validate

5. Windows 下的文件移动

Windows 上 fs.rename 在目录非空时可能抛出 EPERM 错误,archive.ts 已有降级处理(copy + delete),但如果有杀毒软件或 IDE 占用文件,仍可能失败。

代码中的特殊约定

1. 命令路径格式

嵌套命令的路径用冒号分隔,如 change:showcompletion:install,这是遥测追踪的格式:

// src/cli/index.ts
function getCommandPath(command: Command): string {
  // 将 ['change', 'show'] 拼接为 'change:show'
  return names.join(':') || 'openspec';
}

2. OPENSPEC_MARKERS 注释标记

配置文件中使用特殊 HTML 注释标记来标识 OpenSpec 管理的内容区域:

// src/core/config.ts
export const OPENSPEC_MARKERS = {
  start: '<!-- OPENSPEC:START -->',
  end: '<!-- OPENSPEC:END -->'
};

3. Skill 文件的 YAML Frontmatter

生成的 SKILL.md 文件包含 YAML Frontmatter,记录生成版本,用于 openspec update 时的版本漂移检测:

---
generatedBy: openspec@1.2.0
---

4. 规格名称规范化

需求名称在比较时会被规范化(normalizeRequirementName),转为小写并去除多余空格,因此 "User Authentication""user authentication" 被视为同一需求。

值得注意的技术债务

  1. openspec change 命令组:已标记为废弃(deprecated),但仍保留向后兼容。未来版本可能移除,新代码不应依赖这些命令。

  2. src/core/index.ts 注释:文件顶部有注释 // Core OpenSpec logic will be implemented here,说明该文件目前只是导出聚合,核心逻辑分散在各子模块中,[待确认] 是否有计划统一整合。

  3. 项目配置缓存project-config.ts 的注释中明确说明当前不做缓存(每次命令读 1-2 次文件,约 1-3ms),认为缓存带来的复杂度不值得。如果未来有高频读取场景,可能需要引入缓存。

  4. openspec experimental 命令:是 openspec init 的隐藏别名,已标记为废弃,仅为向后兼容保留。

  5. 并发验证的错误处理validate.ts 中批量验证的错误捕获使用了 getPlannedId / getPlannedType 辅助函数来还原失败任务的 ID,逻辑较为脆弱,依赖队列索引与 ID 数组的对应关系。


本文档基于 OpenSpec v1.2.0 源码分析生成。如有疑问,欢迎参考 官方文档 或加入 Discord 社区