源码深度解析

OpenSpec 源码剖析

从 CLI 入口到核心算法,深入理解 AI-native 规格驱动开发框架的实现原理

01

项目概览

OpenSpec 技术画像

属性
定位AI-native 规格驱动开发 (SDD) 框架
语言TypeScript (ESM 模块)
运行时Node.js ≥ 20.19.0
包管理pnpm
代码量~22,000 行 TypeScript,130+ 源文件
测试框架Vitest
CLI 框架Commander.js
核心依赖Zod (校验)、fast-glob (文件匹配)、chalk (输出)、ora (loading)、yaml (解析)

核心设计理念

G

Graph-Driven

用有向无环图 (DAG) 管理 artifact 依赖,Kahn 算法保证拓扑序

S

Schema-First

YAML schema 定义工作流,三级解析(项目 > 用户 > 内置)

A

Adapter Pattern

25+ 工具适配器,统一内容模型 → 多格式输出

F

File-System State

无数据库,文件存在即完成,Git 友好

02

源码目录结构

完整源码树

OpenSpec/
├── bin/openspec.js                    # CLI 启动入口 (shebang 脚本)
├── src/                               # 核心源码 (~130 .ts 文件)
│   ├── cli/index.ts                   # CLI 命令注册与路由 (Commander.js)
│   ├── commands/                      # 命令实现
│   │   ├── workflow/                  # Workflow 命令族
│   │   │   ├── index.ts              # status/instructions/templates/schemas/new
│   │   │   └── ...
│   │   ├── change.ts                  # 变更管理 (legacy)
│   │   ├── spec.ts                    # Spec 命令
│   │   ├── config.ts                  # 配置命令
│   │   ├── validate.ts                # 验证命令
│   │   ├── schema.ts                  # Schema 管理 (fork/init/validate)
│   │   ├── show.ts                    # 详情展示
│   │   ├── completion.ts              # Shell 补全
│   │   └── feedback.ts                # 反馈提交
│   ├── core/                          # 核心业务逻辑
│   │   ├── artifact-graph/            # ★ Artifact 依赖图引擎
│   │   │   ├── graph.ts              # ArtifactGraph 类 + Kahn 算法
│   │   │   ├── resolver.ts           # Schema 三级解析
│   │   │   ├── state.ts              # 完成状态检测
│   │   │   ├── instruction-loader.ts # 指令富化引擎
│   │   │   ├── schema.ts             # YAML 解析 + Zod 校验
│   │   │   └── types.ts              # 类型定义
│   │   ├── command-generation/        # ★ 多工具命令生成
│   │   │   ├── adapters/             # 25+ 工具适配器
│   │   │   │   ├── claude.ts         # Claude Code 适配器
│   │   │   │   ├── cursor.ts         # Cursor 适配器
│   │   │   │   ├── factory.ts        # 适配器工厂
│   │   │   │   └── ...               # 其他工具
│   │   │   ├── generator.ts          # 命令文件生成器
│   │   │   ├── registry.ts           # 工具注册表
│   │   │   └── types.ts              # CommandContent 等类型
│   │   ├── parsers/                   # ★ Markdown 解析
│   │   │   ├── markdown-parser.ts    # Spec 文件解析
│   │   │   ├── change-parser.ts      # Change 文件解析
│   │   │   └── requirement-blocks.ts # Delta Spec 解析
│   │   ├── validation/                # ★ 验证系统
│   │   │   ├── validator.ts          # Validator 类 (Zod + 自定义规则)
│   │   │   ├── types.ts              # ValidationIssue 等类型
│   │   │   └── constants.ts          # 验证常量
│   │   ├── templates/                 # Skill/Command 模板
│   │   │   └── workflows/            # 11 个工作流模板
│   │   │       ├── propose.ts
│   │   │       ├── apply.ts
│   │   │       ├── archive.ts
│   │   │       └── ...
│   │   ├── shared/                    # 共享逻辑
│   │   │   ├── skill-generation.ts   # Skill 内容生成
│   │   │   └── tool-detection.ts     # AI 工具发现
│   │   ├── schemas/                   # Zod Schema 定义
│   │   ├── completions/               # Shell 补全生成器
│   │   ├── init.ts                    # 项目初始化
│   │   ├── archive.ts                 # 归档命令
│   │   ├── specs-apply.ts             # Delta Spec 合并算法
│   │   ├── update.ts                  # 配置更新
│   │   ├── list.ts                    # 列表命令
│   │   ├── view.ts                    # 交互式仪表盘
│   │   ├── config.ts                  # 工具常量 (AI_TOOLS)
│   │   ├── project-config.ts          # config.yaml 解析
│   │   ├── global-config.ts           # XDG 全局配置
│   │   ├── profiles.ts                # 工作流 Profile 系统
│   │   └── migration.ts               # 旧格式迁移
│   ├── utils/                         # 工具函数
│   │   ├── file-system.ts            # 跨平台文件操作
│   │   ├── change-metadata.ts        # .openspec.yaml 解析
│   │   ├── task-progress.ts          # checkbox 进度追踪
│   │   └── interactive.ts            # 交互式提示
│   ├── telemetry/                     # 匿名遥测 (PostHog)
│   └── ui/                            # 欢迎屏幕等 UI 组件
├── schemas/                           # 内置工作流 Schema
│   └── spec-driven/
│       ├── schema.yaml                # 默认 Schema 定义
│       └── templates/                 # Artifact 模板
│           ├── proposal.md
│           ├── spec.md
│           ├── design.md
│           └── tasks.md
├── test/                              # Vitest 测试套件 (50+ 文件)
└── docs/                              # 文档
03

模块依赖关系

模块调用图

CLI 层 (src/cli/index.ts)
  │
  ├──→ commands/*             命令实现层
  │     ├── workflow/*         → core/artifact-graph/* (查询 artifact 状态)
  │     ├── validate.ts        → core/validation/validator.ts
  │     ├── schema.ts          → core/artifact-graph/resolver.ts
  │     └── show.ts            → core/parsers/*
  │
  ├──→ core/init.ts           项目初始化
  │     ├──→ command-generation/*    命令文件生成
  │     │     ├── generator.ts       → adapters/* (25+ 适配器)
  │     │     └── registry.ts        → 工具注册表
  │     └──→ shared/skill-generation.ts  Skill 内容生成
  │           └──→ templates/workflows/*  工作流模板
  │
  ├──→ core/archive.ts        归档流程
  │     ├──→ core/specs-apply.ts     Delta Spec 合并
  │     │     └──→ parsers/requirement-blocks.ts  解析 delta 格式
  │     └──→ core/validation/validator.ts  归档前验证
  │
  └──→ core/artifact-graph/*  ★ 核心引擎
        ├── graph.ts           ArtifactGraph + Kahn 算法
        ├── state.ts           文件系统状态检测
        ├── resolver.ts        Schema 三级解析
        ├── instruction-loader.ts  指令富化
        └── types.ts           Zod 类型定义

数据流向

schema.yaml ──→ ArtifactGraph ──→ getBuildOrder()
                     │                    │
                     │              getNextArtifacts()
                     │                    │
config.yaml ──→ instructions ◀── template + instruction
                     │
                     ▼
              AI 创作 artifact
                     │
                     ▼
              detectCompleted() ──→ 文件系统扫描
                     │
                     ▼
              archive ──→ Delta Spec 合并 ──→ 主 specs/
04

CLI 入口与路由

入口文件:src/cli/index.ts

OpenSpec 使用 Commander.js 作为 CLI 框架,在一个文件中注册所有命令和全局钩子。

// src/cli/index.ts (核心结构)

import { Command } from 'commander';

const program = new Command();

program
  .name('openspec')
  .description('AI-native system for spec-driven development')
  .version(version);

// 全局钩子:每个命令执行前/后自动触发
program.hook('preAction', async (thisCommand, actionCommand) => {
  // 1. 颜色检测
  // 2. 遥测通知(首次运行提示)
  // 3. 命令追踪
  const commandPath = getCommandPath(actionCommand);
  await trackCommand(commandPath, version);
});

program.hook('postAction', async () => {
  await shutdown(); // 关闭遥测客户端
});

命令路径解析

嵌套命令通过 getCommandPath 函数转化为 : 分隔的路径:

// openspec change show → "change:show"
// openspec schema fork → "schema:fork"
function getCommandPath(command: Command): string {
  const names: string[] = [];
  let current: Command | null = command;

  while (current) {
    const name = current.name();
    if (name && name !== 'openspec') {
      names.unshift(name);
    }
    current = current.parent;
  }

  return names.join(':') || 'openspec';
}

命令注册一览

命令实现文件职责
init [path]core/init.ts项目初始化,生成 AI 工具配置
update [path]core/update.ts升级后重新生成配置文件
listcore/list.ts列出变更或 specs
viewcore/view.ts交互式仪表盘
archive [name]core/archive.ts归档变更 + 合并 specs
validate [item]commands/validate.ts验证格式
show [item]commands/show.ts查看详情
statuscommands/workflow/artifact 状态
instructionscommands/workflow/获取富化指令
new changecommands/workflow/创建变更目录
schema fork|initcommands/schema.tsSchema 管理
config profile|deliverycommands/config.ts全局配置
completioncommands/completion.tsShell 补全
05

init 初始化流程

初始化执行链路:core/init.ts

openspec init [path] --tools claude,cursor
    │
    ├── 1. 检测遗留文件 (detectLegacyArtifacts)
    │      → 发现旧格式文件则提示清理
    │
    ├── 2. 交互式工具选择(非 --tools 模式)
    │      → isInteractive() 检测终端
    │      → 列出 AI_TOOLS 供选择
    │
    ├── 3. 创建 openspec/ 目录结构
    │      ├── openspec/config.yaml
    │      ├── openspec/specs/
    │      ├── openspec/changes/
    │      └── openspec/agent-instructions.md
    │
    ├── 4. 生成 Skill 文件
    │      ├── getSkillTemplates() → 获取工作流模板
    │      ├── generateSkillContent() → 渲染 YAML frontmatter
    │      └── 写入各工具的 skills 目录
    │
    ├── 5. 生成 Command 文件
    │      ├── getCommandContents() → 获取命令内容
    │      ├── CommandAdapterRegistry → 查找适配器
    │      └── generateCommands() → 按工具格式写入
    │
    └── 6. 运行旧格式迁移
           → migrateIfNeeded()

工具常量定义:core/config.ts

AI_TOOLS 是一个数组,定义了所有支持的 AI 工具:

export const AI_TOOLS: AIToolOption[] = [
  { name: 'Claude Code', value: 'claude', skillsDir: '.claude/skills' },
  { name: 'Cursor',      value: 'cursor', skillsDir: '.cursor/skills' },
  { name: 'GitHub Copilot', value: 'github-copilot', skillsDir: '.github/prompts' },
  { name: 'Windsurf',    value: 'windsurf', skillsDir: '.windsurf/workflows' },
  { name: 'Gemini CLI',  value: 'gemini', skillsDir: '.gemini/commands' },
  // ... 20+ 更多工具
];

工作流 → Skill 目录映射

// core/init.ts
const WORKFLOW_TO_SKILL_DIR: Record<string, string> = {
  'explore':      'openspec-explore',
  'new':          'openspec-new-change',
  'continue':     'openspec-continue-change',
  'apply':        'openspec-apply-change',
  'ff':           'openspec-ff-change',
  'sync':         'openspec-sync-specs',
  'archive':      'openspec-archive-change',
  'bulk-archive': 'openspec-bulk-archive-change',
  'verify':       'openspec-verify-change',
  'onboard':      'openspec-onboard',
  'propose':      'openspec-propose',
};

每个工作流对应一个 skill 目录,init 时根据 profile (core/custom) 选择生成哪些。

06

Workflow 命令族

commands/workflow/index.ts

Workflow 命令是 OpenSpec 的核心交互接口,所有命令共享同一套 artifact-graph 引擎:

// status 命令:查询 artifact 状态
export async function statusCommand(options: StatusOptions) {
  const context = loadChangeContext(projectRoot, changeName, options.schema);
  const status = formatChangeStatus(context);
  // 输出 JSON 或人类可读格式
}

// instructions 命令:获取富化指令
export async function instructionsCommand(
  artifactId: string,
  options: InstructionsOptions
) {
  const context = loadChangeContext(projectRoot, changeName, options.schema);
  const instructions = generateInstructions(context, artifactId, projectRoot);
  // 返回 template + instruction + context + rules + dependencies
}

// new change 命令:创建变更目录
export async function newChangeCommand(
  changeName: string,
  options: NewChangeOptions
) {
  // 创建 openspec/changes/<name>/
  // 写入 .openspec.yaml 元数据
}

命令间的数据流

new change → 创建目录 + .openspec.yaml
     │
     ▼
  status → loadChangeContext() → ArtifactGraph + detectCompleted()
     │
     ▼
instructions → generateInstructions() → 富化指令 JSON
     │
     ▼
  (AI 创建 artifact 文件)
     │
     ▼
  status → detectCompleted() 重新检测 → 更新状态
     │
     ▼
  archive → 合并 Delta Specs + 移动到 archive/
07

Artifact 依赖图引擎

ArtifactGraph 类:core/artifact-graph/graph.ts

ArtifactGraph 是 OpenSpec 的核心数据结构,将 schema.yaml 中定义的 artifact 及其依赖关系建模为有向无环图 (DAG)

export class ArtifactGraph {
  private artifacts: Map<string, Artifact>;
  private schema: SchemaYaml;

  // 私有构造函数,通过静态工厂方法创建
  private constructor(schema: SchemaYaml) {
    this.schema = schema;
    // 将 artifact 数组转为 Map,以 id 为 key
    this.artifacts = new Map(schema.artifacts.map(a => [a.id, a]));
  }

  // 三种工厂方法
  static fromYaml(filePath: string): ArtifactGraph      // 从文件路径
  static fromYamlContent(yamlContent: string): ArtifactGraph  // 从字符串
  static fromSchema(schema: SchemaYaml): ArtifactGraph   // 从已解析对象
}

Artifact 类型定义

// core/artifact-graph/types.ts
export interface Artifact {
  id: string;           // 唯一标识,如 "proposal"
  generates: string;    // 产出路径,如 "proposal.md" 或 "specs/**/*.md"
  description: string;  // 描述
  template: string;     // 模板文件相对路径
  instruction?: string; // 创作指导
  requires: string[];   // 依赖的 artifact ID 列表
}

export interface SchemaYaml {
  name: string;
  version: number;
  description: string;
  artifacts: Artifact[];
  apply?: {
    requires: string[];   // apply 前置条件
    tracks: string;       // 进度追踪文件
    instruction?: string; // 实现指导
  };
}

export type CompletedSet = Set<string>;
export type BlockedArtifacts = Record<string, string[]>;

核心查询方法

方法入参返回用途
getBuildOrder()string[]Kahn 算法拓扑排序
getNextArtifacts(completed)已完成集合string[]可创建的 artifact
isComplete(completed)已完成集合boolean全部完成检测
getBlocked(completed)已完成集合BlockedArtifacts阻塞原因查询
08

Kahn 拓扑排序算法

getBuildOrder() 源码解析

OpenSpec 使用 Kahn 算法计算 artifact 的构建顺序。该算法通过维护入度计数器,从入度为 0 的节点开始逐步剥离:

getBuildOrder(): string[] {
  // Step 1: 初始化入度和反向邻接表
  const inDegree = new Map<string, number>();
  const dependents = new Map<string, string[]>();

  for (const artifact of this.artifacts.values()) {
    inDegree.set(artifact.id, artifact.requires.length);  // 入度 = 依赖数
    dependents.set(artifact.id, []);                       // 反向边列表
  }

  // Step 2: 构建反向邻接表(谁依赖谁 → 被依赖者指向依赖者)
  for (const artifact of this.artifacts.values()) {
    for (const req of artifact.requires) {
      dependents.get(req)!.push(artifact.id);
    }
  }

  // Step 3: 收集入度为 0 的根节点(排序保证确定性)
  const queue = [...this.artifacts.keys()]
    .filter(id => inDegree.get(id) === 0)
    .sort();

  const result: string[] = [];

  // Step 4: BFS 逐层剥离
  while (queue.length > 0) {
    const current = queue.shift()!;   // 取出入度为 0 的节点
    result.push(current);

    // 减少所有后继节点的入度
    const newlyReady: string[] = [];
    for (const dep of dependents.get(current)!) {
      const newDegree = inDegree.get(dep)! - 1;
      inDegree.set(dep, newDegree);
      if (newDegree === 0) {
        newlyReady.push(dep);   // 入度变为 0,加入队列
      }
    }
    queue.push(...newlyReady.sort()); // 排序保证确定性输出
  }

  return result;  // 拓扑序列
}

算法执行过程可视化

以默认 spec-driven schema 为例:

依赖关系:
  proposal → (无依赖)
  specs    → [proposal]
  design   → [proposal]
  tasks    → [specs, design]

初始入度:
  proposal=0  specs=1  design=1  tasks=2

第 1 轮:取出 proposal (入度=0)
  → specs 入度 1→0, design 入度 1→0
  → queue: [design, specs]  (排序后)

第 2 轮:取出 design (入度=0)
  → tasks 入度 2→1
  → queue: [specs]

第 3 轮:取出 specs (入度=0)
  → tasks 入度 1→0
  → queue: [tasks]

第 4 轮:取出 tasks (入度=0)
  → queue: []

结果:[proposal, design, specs, tasks]
确定性保证:注意代码中两处 .sort() 调用——初始队列和新就绪节点都排序,确保相同输入总是产出相同顺序。这对 AI 生成的一致性至关重要。

getNextArtifacts() — 就绪节点查询

getNextArtifacts(completed: CompletedSet): string[] {
  const ready: string[] = [];

  for (const artifact of this.artifacts.values()) {
    if (completed.has(artifact.id)) continue; // 已完成,跳过

    // 所有依赖都在 completed 中 → ready
    const allDepsCompleted = artifact.requires.every(
      req => completed.has(req)
    );
    if (allDepsCompleted) {
      ready.push(artifact.id);
    }
  }

  return ready.sort(); // 确定性排序
}

时间复杂度:O(V × D),V = artifact 数量,D = 平均依赖数。对于典型的 4-6 个 artifact 场景,性能微不足道。

09

完成状态检测

基于文件系统的状态机:core/artifact-graph/state.ts

OpenSpec 的一个精妙设计:不用数据库,不用状态文件,文件存在即完成

export function detectCompleted(
  graph: ArtifactGraph,
  changeDir: string
): CompletedSet {
  const completed = new Set<string>();

  // 目录不存在则返回空集
  if (!fs.existsSync(changeDir)) return completed;

  for (const artifact of graph.getAllArtifacts()) {
    if (isArtifactComplete(artifact.generates, changeDir)) {
      completed.add(artifact.id);
    }
  }

  return completed;
}

两种检测模式

function isArtifactComplete(generates: string, changeDir: string): boolean {
  const fullPattern = path.join(changeDir, generates);

  if (isGlobPattern(generates)) {
    // Glob 模式:specs/**/*.md → 有匹配文件即完成
    return hasGlobMatches(fullPattern);
  }

  // 简单路径:proposal.md → 文件存在即完成
  return fs.existsSync(fullPattern);
}

function isGlobPattern(pattern: string): boolean {
  // 检测 *, ?, [ 三种通配符
  return pattern.includes('*') || pattern.includes('?') || pattern.includes('[');
}

function hasGlobMatches(pattern: string): boolean {
  // 跨平台:Windows 反斜杠 → POSIX 正斜杠
  const normalizedPattern = FileSystemUtils.toPosixPath(pattern);
  const matches = fg.sync(normalizedPattern, { onlyFiles: true });
  return matches.length > 0;
}

设计决策:为什么用文件系统而非数据库

优势说明
零依赖不需要数据库、Redis 或任何外部服务
Git 友好状态天然跟随文件进入版本控制
无状态同步多人协作时 git pull 即同步状态,无需额外协议
可审计文件存在/不存在即是证据,无需查日志
手动可干预删除文件即重置状态,无需特殊命令
10

Schema 三级解析

解析优先级:core/artifact-graph/resolver.ts

Schema 解析遵循就近优先原则,三级覆盖:

优先级从高到低:

1. 项目本地 (Project-Local)
   → <projectRoot>/openspec/schemas/<name>/schema.yaml
   → 项目级自定义,Git 随项目走

2. 用户覆盖 (User Override)
   → $XDG_DATA_HOME/openspec/schemas/<name>/schema.yaml
   → ~/.local/share/openspec/schemas/... (Linux/macOS 默认)
   → 个人偏好,跨项目生效

3. 包内置 (Package Built-in)
   → <npm-package>/schemas/<name>/schema.yaml
   → 默认随 npm 包发布,兜底

getSchemaDir() 解析实现

export function getSchemaDir(
  name: string,
  projectRoot?: string
): string | null {
  // 1. 检查项目本地目录
  if (projectRoot) {
    const projectDir = path.join(
      getProjectSchemasDir(projectRoot), name
    );
    if (fs.existsSync(path.join(projectDir, 'schema.yaml'))) {
      return projectDir;
    }
  }

  // 2. 检查用户覆盖目录
  const userDir = path.join(getUserSchemasDir(), name);
  if (fs.existsSync(path.join(userDir, 'schema.yaml'))) {
    return userDir;
  }

  // 3. 检查包内置目录
  const packageDir = path.join(getPackageSchemasDir(), name);
  if (fs.existsSync(path.join(packageDir, 'schema.yaml'))) {
    return packageDir;
  }

  return null; // 未找到
}

路径定位函数

// 包内置 schemas 路径:通过 import.meta.url 定位
export function getPackageSchemasDir(): string {
  const currentFile = fileURLToPath(import.meta.url);
  // 从 dist/core/artifact-graph/ 向上导航到 schemas/
  return path.join(path.dirname(currentFile), '..', '..', '..', 'schemas');
}

// 用户 schemas 路径:遵循 XDG 规范
export function getUserSchemasDir(): string {
  return path.join(getGlobalDataDir(), 'schemas');
  // → ~/.local/share/openspec/schemas/ (Linux)
  // → ~/Library/Application Support/openspec/schemas/ (macOS)
}

// 项目本地 schemas 路径
export function getProjectSchemasDir(projectRoot: string): string {
  return path.join(projectRoot, 'openspec', 'schemas');
}

Schema 变更解析链

当执行命令时,schema 名称的解析经过多层:

CLI 参数 --schema <name>
  → 有值则直接使用
  → 无值则:
     .openspec.yaml 中的 schema 字段
       → 有值则使用
       → 无值则:
          openspec/config.yaml 中的 schema 字段
            → 有值则使用
            → 无值则:默认 "spec-driven"
// utils/change-metadata.ts
export function resolveSchemaForChange(
  changeDir: string,
  explicitSchema?: string
): string {
  if (explicitSchema) return explicitSchema;

  // 尝试从 .openspec.yaml 读取
  const metadata = readChangeMetadata(changeDir);
  if (metadata?.schema) return metadata.schema;

  return 'spec-driven'; // 默认值
}
11

指令富化引擎

generateInstructions():核心指令组装器

位于 core/artifact-graph/instruction-loader.ts,这个函数将 schema 指令、项目配置和模板组装成一份完整的 AI 创作指令:

export function generateInstructions(
  context: ChangeContext,
  artifactId: string,
  projectRoot?: string
): ArtifactInstructions {
  // 1. 查找 artifact 定义
  const artifact = context.graph.getArtifact(artifactId);
  if (!artifact) throw new Error(`Artifact '${artifactId}' not found`);

  // 2. 加载模板
  const templateContent = loadTemplate(
    context.schemaName, artifact.template, context.projectRoot
  );

  // 3. 计算依赖信息
  const dependencies = getDependencyInfo(artifact, context.graph, context.completed);

  // 4. 计算解锁信息(完成后哪些 artifact 变为 ready)
  const unlocks = getUnlockedArtifacts(context.graph, artifactId);

  // 5. 读取项目配置
  let projectConfig = null;
  try {
    projectConfig = readProjectConfig(effectiveProjectRoot);
  } catch { /* 无配置则跳过 */ }

  // 6. 验证 rules 中的 artifact ID 是否有效
  if (projectConfig?.rules) {
    const validArtifactIds = new Set(
      context.graph.getAllArtifacts().map(a => a.id)
    );
    const warnings = validateConfigRules(
      projectConfig.rules, validArtifactIds, context.schemaName
    );
    // 每个 warning 只显示一次(Session 级缓存)
    for (const warning of warnings) {
      if (!shownWarnings.has(warning)) {
        console.warn(warning);
        shownWarnings.add(warning);
      }
    }
  }

  // 7. 提取 context 和 rules(不合并到 template 中!)
  const configContext = projectConfig?.context?.trim() || undefined;
  const configRules = projectConfig?.rules?.[artifactId] || undefined;

  // 8. 组装返回
  return {
    changeName, artifactId, schemaName, changeDir,
    outputPath: artifact.generates,
    description: artifact.description,
    instruction: artifact.instruction,  // 来自 schema
    context: configContext,             // 来自 config.yaml
    rules: configRules,                 // 来自 config.yaml
    template: templateContent,          // 来自 templates/
    dependencies,                       // 已完成的依赖
    unlocks,                           // 将解锁的 artifact
  };
}

依赖信息组装

function getDependencyInfo(
  artifact: Artifact,
  graph: ArtifactGraph,
  completed: CompletedSet
): DependencyInfo[] {
  return artifact.requires.map(id => {
    const depArtifact = graph.getArtifact(id);
    return {
      id,
      done: completed.has(id),              // 是否已完成
      path: depArtifact?.generates ?? id,    // 文件路径
      description: depArtifact?.description ?? '',
    };
  });
}

// 解锁计算:反向查找谁的 requires 包含当前 artifact
function getUnlockedArtifacts(graph: ArtifactGraph, artifactId: string): string[] {
  return graph.getAllArtifacts()
    .filter(a => a.requires.includes(artifactId))
    .map(a => a.id)
    .sort();
}

ChangeContext 接口

export interface ChangeContext {
  graph: ArtifactGraph;      // 依赖图实例
  completed: CompletedSet;   // 已完成 artifact ID 集合
  schemaName: string;        // 使用的 schema 名称
  changeName: string;        // 变更名称
  changeDir: string;         // 变更目录完整路径
  projectRoot: string;       // 项目根目录
}

// 由 loadChangeContext() 一次性加载
export function loadChangeContext(
  projectRoot: string,
  changeName: string,
  schemaName?: string
): ChangeContext {
  const changeDir = path.join(projectRoot, 'openspec', 'changes', changeName);
  const resolvedSchemaName = resolveSchemaForChange(changeDir, schemaName);
  const schema = resolveSchema(resolvedSchemaName, projectRoot);
  const graph = ArtifactGraph.fromSchema(schema);
  const completed = detectCompleted(graph, changeDir);

  return { graph, completed, schemaName: resolvedSchemaName,
           changeName, changeDir, projectRoot };
}
12

Markdown 解析器

Requirement 块解析器:parsers/requirement-blocks.ts

这是 Delta Spec 合并和验证的基础——将 Markdown 中的 Requirement 块解析为结构化数据:

export interface RequirementBlock {
  headerLine: string; // '### Requirement: Something'
  name: string;       // 'Something'
  raw: string;        // 完整块(header + body)
}

export interface RequirementsSectionParts {
  before: string;          // ## Requirements 之前的内容
  headerLine: string;      // '## Requirements' 行
  preamble: string;        // header 和第一个 Requirement 之间的内容
  bodyBlocks: RequirementBlock[]; // 解析后的 Requirement 块
  after: string;           // ## Requirements 之后的内容
}

extractRequirementsSection() 核心逻辑

export function extractRequirementsSection(content: string):
  RequirementsSectionParts {
  const lines = normalizeLineEndings(content).split('\n');

  // 1. 定位 "## Requirements" header
  const reqHeaderIndex = lines.findIndex(
    l => /^##\s+Requirements\s*$/i.test(l)
  );

  // 2. 如果不存在,创建空 section
  if (reqHeaderIndex === -1) {
    return { before: content, headerLine: '## Requirements',
             preamble: '', bodyBlocks: [], after: '\n' };
  }

  // 3. 找到 section 结束位置(下一个 ## 级别标题)
  let endIndex = lines.length;
  for (let i = reqHeaderIndex + 1; i < lines.length; i++) {
    if (/^##\s+/.test(lines[i])) { endIndex = i; break; }
  }

  // 4. 解析 section 内的 Requirement 块
  const sectionBodyLines = lines.slice(reqHeaderIndex + 1, endIndex);
  const blocks: RequirementBlock[] = [];

  // 跳过 preamble 直到第一个 ### Requirement:
  // 然后按 ### Requirement: 切分块
  // ...

  return { before, headerLine, preamble, bodyBlocks: blocks, after };
}

Delta Spec 解析:parseDeltaSpec()

export interface DeltaPlan {
  added: RequirementBlock[];          // 新增
  modified: RequirementBlock[];       // 修改(完整替换内容)
  removed: string[];                  // 删除(Requirement 名称)
  renamed: Array<{ from: string; to: string }>; // 重命名
  sectionPresence: {                  // 各分区是否存在
    added: boolean; modified: boolean;
    removed: boolean; renamed: boolean;
  };
}

export function parseDeltaSpec(content: string): DeltaPlan {
  // 按 ## ADDED/MODIFIED/REMOVED/RENAMED Requirements 分区
  // 每个分区内再按 ### Requirement: 切分块
  // 返回结构化的 DeltaPlan
}
13

Delta Spec 合并算法

buildUpdatedSpec():core/specs-apply.ts

归档时的核心算法——将 delta spec 合并到主 spec:

export async function buildUpdatedSpec(
  update: SpecUpdate,
  changeName: string
): Promise<{ rebuilt: string; counts: {...} }> {
  // 1. 读取 delta spec 并解析
  const changeContent = await fs.readFile(update.source, 'utf-8');
  const plan = parseDeltaSpec(changeContent);

  // 2. 验证无重复 Requirement 名称
  const addedNames = new Set<string>();
  for (const add of plan.added) {
    const name = normalizeRequirementName(add.name);
    if (addedNames.has(name)) throw new Error(`Duplicate: ${add.name}`);
    addedNames.add(name);
  }

  // 3. 读取主 spec 或创建空文件
  let existingContent = '';
  if (update.exists) {
    existingContent = await fs.readFile(update.target, 'utf-8');
  }

  // 4. 提取主 spec 的 Requirements section
  const parts = extractRequirementsSection(existingContent);
  const existingBlocks = new Map(
    parts.bodyBlocks.map(b => [normalizeRequirementName(b.name), b])
  );

  // 5. 执行 REMOVED — 从 Map 中删除
  for (const name of plan.removed) {
    existingBlocks.delete(normalizeRequirementName(name));
  }

  // 6. 执行 RENAMED — 修改 header 中的名称
  for (const { from, to } of plan.renamed) {
    const block = existingBlocks.get(normalizeRequirementName(from));
    if (block) {
      existingBlocks.delete(normalizeRequirementName(from));
      block.name = to;
      block.headerLine = `### Requirement: ${to}`;
      existingBlocks.set(normalizeRequirementName(to), block);
    }
  }

  // 7. 执行 MODIFIED — 整块替换
  for (const mod of plan.modified) {
    existingBlocks.set(normalizeRequirementName(mod.name), mod);
  }

  // 8. 执行 ADDED — 追加新块
  for (const add of plan.added) {
    existingBlocks.set(normalizeRequirementName(add.name), add);
  }

  // 9. 重建完整 spec 文件
  const rebuilt = reassemble(parts, existingBlocks);
  return { rebuilt, counts: { added, modified, removed, renamed } };
}

合并操作示意

主 spec (openspec/specs/auth/spec.md):
  ## Requirements
  ### Requirement: Login
  ### Requirement: Session Timeout (60min)
  ### Requirement: Remember Me

Delta spec (changes/add-2fa/specs/auth/spec.md):
  ## ADDED Requirements
  ### Requirement: Two-Factor Authentication
  ## MODIFIED Requirements
  ### Requirement: Session Timeout (改为 30min)
  ## REMOVED Requirements
  ### Requirement: Remember Me

合并后的主 spec:
  ## Requirements
  ### Requirement: Login                     ← 保留不变
  ### Requirement: Session Timeout (30min)   ← 被 MODIFIED 替换
  ### Requirement: Two-Factor Authentication ← ADDED 追加

安全检查

  • 重名检测:ADDED 分区内不允许重复名称,也不允许与已有 Requirement 重名
  • MODIFIED 完整性:必须包含完整的 Requirement 内容(非差异),因为是整块替换
  • 归档前验证:合并后的 spec 会经过 Validator 再次验证格式
14

验证系统

Validator 类:core/validation/validator.ts

双层验证架构:Zod Schema 结构验证 + 自定义规则语义验证。

export class Validator {
  private strictMode: boolean;

  constructor(strictMode: boolean = false) {
    this.strictMode = strictMode;
  }

  // Spec 文件验证
  async validateSpec(filePath: string): Promise<ValidationReport> {
    const content = readFileSync(filePath, 'utf-8');
    const parser = new MarkdownParser(content);
    const spec = parser.parseSpec(specName);

    // 层 1:Zod Schema 结构验证
    const result = SpecSchema.safeParse(spec);
    if (!result.success) {
      issues.push(...this.convertZodErrors(result.error));
    }

    // 层 2:自定义规则验证
    issues.push(...this.applySpecRules(spec, content));

    return this.createReport(issues);
  }

  // Change 文件验证
  async validateChange(filePath: string): Promise<ValidationReport> {
    // 类似流程,使用 ChangeParser + ChangeSchema
  }

  // 字符串内容验证(归档前的合并结果验证)
  async validateSpecContent(
    specName: string, content: string
  ): Promise<ValidationReport> { ... }
}

验证报告结构

export interface ValidationIssue {
  level: 'ERROR' | 'WARNING';  // 严重级别
  path: string;                 // 问题路径
  message: string;              // 错误描述
}

export interface ValidationReport {
  valid: boolean;        // 是否通过
  issues: ValidationIssue[];
  errors: number;
  warnings: number;
}

自定义规则示例

  • Purpose 长度:spec 的 Purpose 描述不能少于 MIN_PURPOSE_LENGTH 字符
  • Requirement 文本长度:不能超过 MAX_REQUIREMENT_TEXT_LENGTH
  • Scenario 格式:必须使用 #### (4 个 #),不是 3 个
  • RFC 2119 关键字:检查 SHALL/MUST/SHOULD/MAY 的使用
15

命令生成架构

统一内容模型

命令生成系统的核心思想:一次生成内容,多格式输出

// core/command-generation/types.ts
export interface CommandContent {
  id: string;           // 命令 ID,如 "propose"
  name: string;         // 显示名称
  description: string;  // 命令描述
  category: string;     // 分类
  tags: string[];       // 标签
  body: string;         // 完整的指令内容
}

export interface ToolCommandAdapter {
  toolId: string;
  getFilePath(commandId: string): string;      // 输出文件路径
  formatFile(content: CommandContent): string;  // 格式化为文件内容
}

生成流程

getCommandContents()        获取所有命令内容 (CommandContent[])
        │
        ▼
CommandAdapterRegistry      查找目标工具的适配器
        │
        ▼
adapter.getFilePath(id)     计算输出文件路径
        │
        ▼
adapter.formatFile(content) 按工具格式渲染文件
        │
        ▼
写入文件系统                 .claude/commands/opsx/propose.md
                            .cursor/rules/opsx/propose.md
                            .github/prompts/opsx-propose.prompt.md
16

适配器模式详解

Claude Code 适配器源码

// core/command-generation/adapters/claude.ts

// YAML 值转义
function escapeYamlValue(value: string): string {
  const needsQuoting = /[:\n\r#{}[\],&*!|>'"%@`]|^\s|\s$/.test(value);
  if (needsQuoting) {
    const escaped = value
      .replace(/\\/g, '\\\\')
      .replace(/"/g, '\\"')
      .replace(/\n/g, '\\n');
    return `"${escaped}"`;
  }
  return value;
}

export const claudeAdapter: ToolCommandAdapter = {
  toolId: 'claude',

  getFilePath(commandId: string): string {
    // → .claude/commands/opsx/propose.md
    return path.join('.claude', 'commands', 'opsx', `${commandId}.md`);
  },

  formatFile(content: CommandContent): string {
    // Claude Code 的 YAML frontmatter 格式
    return `---
name: ${escapeYamlValue(content.name)}
description: ${escapeYamlValue(content.description)}
category: ${escapeYamlValue(content.category)}
tags: ${formatTagsArray(content.tags)}
---

${content.body}
`;
  },
};

适配器工厂

// core/command-generation/adapters/factory.ts
// 适配器注册表,按 toolId 查找
const adapters: Map<string, ToolCommandAdapter> = new Map([
  ['claude', claudeAdapter],
  ['cursor', cursorAdapter],
  ['github-copilot', copilotAdapter],
  ['windsurf', windsurfAdapter],
  ['gemini', geminiAdapter],
  ['codex', codexAdapter],
  // ... 20+ 更多
]);

export function getAdapter(toolId: string): ToolCommandAdapter | undefined {
  return adapters.get(toolId);
}

各适配器输出路径对比

工具输出路径格式特点
Claude Code.claude/commands/opsx/<id>.mdYAML frontmatter (name, description, category, tags)
Cursor.cursor/rules/opsx/<id>.mdCursor 特定 frontmatter
GitHub Copilot.github/prompts/opsx-<id>.prompt.mdPrompt 文件格式
Gemini CLI.gemini/commands/opsx/<id>.mdGemini 命令格式
Continue.continue/config/<id>.jsonJSON 配置
17

Skill 生成系统

工作流模板:core/templates/workflows/

每个工作流都有一个 TypeScript 文件,返回结构化的 SkillTemplate:

// core/templates/workflows/propose.ts
export function getOpsxProposeSkillTemplate(): SkillTemplate {
  return {
    name: 'openspec-propose',
    description: 'Propose a new change with all artifacts...',
    instructions: `
      Propose a new change...

      **Steps**
      1. Ask what they want to build
      2. Create change directory: openspec new change "<name>"
      3. Get artifact build order: openspec status --change "<name>" --json
      4. Create artifacts in dependency order:
         a. Get instructions: openspec instructions <id> --change "<name>" --json
         b. Read template, instruction, context, rules
         c. Create artifact file
      5. Continue until all applyRequires are done
    `,
  };
}

Profile 系统

// core/profiles.ts
export const CORE_WORKFLOWS = [
  'propose', 'apply', 'archive'
];

export const ALL_WORKFLOWS = [
  'explore', 'new', 'continue', 'apply', 'ff',
  'sync', 'archive', 'bulk-archive', 'verify', 'onboard', 'propose'
];

export function getProfileWorkflows(profile: Profile): string[] {
  if (profile === 'core') return CORE_WORKFLOWS;
  return ALL_WORKFLOWS;
}

core profile:只生成 propose/apply/archive 三个核心命令。
custom profile:生成全部 11 个工作流命令。

18

配置系统

双层配置架构

全局配置 (core/global-config.ts)
  → ~/.config/openspec/config.json (XDG 规范)
  → 存储:profile, delivery, workflows, featureFlags
  → 影响:所有项目

项目配置 (core/project-config.ts)
  → openspec/config.yaml
  → 存储:schema, context, rules
  → 影响:当前项目

项目配置解析:core/project-config.ts

export interface ProjectConfig {
  schema?: string;
  context?: string;                    // 最大 50KB
  rules?: Record<string, string[]>;    // artifactId → rules[]
}

export function readProjectConfig(projectRoot: string): ProjectConfig | null {
  const configPath = path.join(projectRoot, 'openspec', 'config.yaml');
  if (!fs.existsSync(configPath)) return null;

  const content = fs.readFileSync(configPath, 'utf-8');
  const parsed = yaml.parse(content);
  // Zod 验证 + 类型安全
  return ProjectConfigSchema.parse(parsed);
}

// 验证 rules 中的 artifact ID 是否存在于当前 schema
export function validateConfigRules(
  rules: Record<string, string[]>,
  validArtifactIds: Set<string>,
  schemaName: string
): string[] {
  const warnings: string[] = [];
  for (const key of Object.keys(rules)) {
    if (!validArtifactIds.has(key)) {
      warnings.push(
        `Warning: rules key "${key}" is not a valid artifact ID in schema "${schemaName}"`
      );
    }
  }
  return warnings;
}
19

遥测系统

匿名遥测:src/telemetry/

OpenSpec 使用 PostHog 收集匿名使用统计:

  • 收集内容:命令名称 + CLI 版本号
  • 不收集:命令参数、文件路径、用户数据 (PII)
  • 首次运行提示:通过 maybeShowTelemetryNotice() 显示一次
// 每个命令执行前自动追踪
program.hook('preAction', async (thisCommand, actionCommand) => {
  await maybeShowTelemetryNotice();
  const commandPath = getCommandPath(actionCommand);
  await trackCommand(commandPath, version);
});

// 命令完成后关闭客户端
program.hook('postAction', async () => {
  await shutdown();
});
# 退出遥测
export OPENSPEC_TELEMETRY=0
# 或
export DO_NOT_TRACK=1
20

设计模式总结

核心设计模式

模式应用位置解决的问题
工厂方法ArtifactGraph.fromYaml() / fromSchema()多种方式构建图实例
适配器command-generation/adapters/*统一接口适配 25+ 工具
策略completions/generators/*不同 shell 的补全生成
注册表CommandAdapterRegistry按 toolId 动态查找适配器
模板方法templates/workflows/*统一结构,定制内容
BuilderbuildUpdatedSpec()逐步构建合并后的 spec
状态机artifact-graph/state.tsdone / ready / blocked 三态

架构亮点

  • 文件即状态:用文件系统代替数据库,零依赖且 Git 友好
  • Schema 驱动:schema.yaml 定义工作流,修改 YAML 即改变行为
  • 三级覆盖:项目 > 用户 > 内置,满足个性化需求
  • 内容与格式分离:CommandContent 统一内容,Adapter 负责格式转换
  • 确定性排序:Kahn 算法中双重 sort(),保证相同输入相同输出
  • 渐进式采纳:brownfield 优先设计,通过隔离的 change 目录避免侵入
21

总结

OpenSpec 源码核心技术总结

模块核心技术关键文件
依赖图Kahn 拓扑排序artifact-graph/graph.ts
状态检测文件系统 + fast-globartifact-graph/state.ts
Schema 解析三级覆盖 + XDG 规范artifact-graph/resolver.ts
指令富化多源聚合(schema + config + template)artifact-graph/instruction-loader.ts
Spec 合并Delta 操作 (ADDED/MODIFIED/REMOVED)core/specs-apply.ts
验证Zod Schema + 自定义规则validation/validator.ts
多工具适配适配器模式 + 注册表command-generation/adapters/*
Markdown 解析正则 + 行级扫描parsers/requirement-blocks.ts

延伸阅读