引擎层与安装器

📑 目录

这是「GSD 全景代码解析」专题的第 45 篇。

flowchart TD
    A[运行时发现状态变更] --> B{变更类型}
    B -->|阶段性| C[写入 .gsd/state.json]
    B -->|临时性| D[写入 /tmp/gsd-*.tmp]
    B -->|持久性| E[写入 ~/.gsd/global-state.json]
    C --> F[状态恢复]
    D --> F
    E --> F
    F --> G[下次启动时加载]

状态文件采用分层存储策略:

  • 项目级状态.gsd/state.json):当前项目的阶段进度、Agent 执行结果、Git 状态快照
  • 全局状态~/.gsd/global-state.json):用户偏好、运行时选择、安装历史
  • 临时状态/tmp/gsd-*.tmp):当前会话的缓存数据,进程结束后可清理
// state.cjs 的核心 API
const state = require('./state.cjs');

// 原子性写入:先写临时文件,再重命名,避免半写状态
state.atomicWrite('.gsd/state.json', {
  phase: 'execute',
  currentWave: 3,
  completedTasks: ['setup', 'lint', 'test'],
  lastCheckpoint: '2026-04-24T10:30:00Z'
});

// 带版本校验的读取
const current = state.read('.gsd/state.json', { schemaVersion: '2.1' });

atomicWritestate.cjs 中最关键的实现细节:它使用 fs.writeFileSync() 写入临时文件,然后通过 fs.renameSync() 原子替换目标文件。这保证了即使在进程崩溃的极端情况下,也不会出现损坏的 JSON 状态文件。

1.3 verify.cjs:验证引擎

verify.cjs 是 GSD 的健康检查与断言系统。在 SDK 层的 Verifier Agent(第 25 篇)负责业务逻辑的验证,而 verify.cjs 则负责运行时环境的验证——检查 Node.js 版本、确认 Git 安装、校验配置文件格式、检测端口占用等。

// verify.cjs 的验证器注册机制
const verify = require('./verify.cjs');

// 注册自定义验证器
verify.register('node-version', () => {
  const semver = process.version.slice(1).split('.');
  const major = parseInt(semver[0], 10);
  if (major < 18) {
    throw new verify.ValidationError(
      `Node.js ${process.version} 不满足最低要求 (>= 18.0.0)`
    );
  }
});

// 执行全部验证
const results = verify.runAll();
// results: [{ name: 'node-version', status: 'pass' }, ...]

verify.cjs 的验证器采用惰性执行模式:验证器在注册时不会被调用,只有当显式执行 runAll()run(name) 时才会触发。这种设计允许安装器在安装过程中按需验证,而 SDK 层则在阶段转换时进行批量检查。

1.4 model-profiles.cjs:模型配置

model-profiles.cjs 维护着 GSD 支持的AI 模型配置档案。当 SDK 层需要调用 LLM API 时,它会通过引擎层查询当前模型的上下文窗口、token 限制、特性支持等元数据。

// model-profiles.cjs 的配置结构
module.exports = {
  'claude-3-5-sonnet': {
    contextWindow: 200000,
    maxOutputTokens: 8192,
    supportsVision: true,
    supportsToolUse: true,
    costPer1MInput: 3.00,
    costPer1MOutput: 15.00,
    recommendedFor: ['plan', 'execute', 'review']
  },
  'claude-3-haiku': {
    contextWindow: 200000,
    maxOutputTokens: 4096,
    supportsVision: false,
    supportsToolUse: true,
    costPer1MInput: 0.25,
    costPer1MOutput: 1.25,
    recommendedFor: ['quick', 'lint', 'format']
  },
  'gpt-4o': {
    contextWindow: 128000,
    maxOutputTokens: 16384,
    supportsVision: true,
    supportsToolUse: true,
    costPer1MInput: 5.00,
    costPer1MOutput: 15.00,
    recommendedFor: ['plan', 'execute']
  }
};

这个模块的设计理念是把模型特性从业务逻辑中抽象出来。当新模型发布时,只需更新这个配置文件,而无需修改 SDK 层的任何代码。

1.5 drift.cjs:漂移检测

drift.cjs 是 GSD 中最具创新性的模块之一。它负责检测实际执行结果与计划预期之间的漂移,并在偏离超过阈值时触发警报。

flowchart LR
    A[Plan 预期输出] --> C[Drift Detector]
    B[实际执行结果] --> C
    C --> D{漂移量 < 阈值?}
    D -->|是| E[继续执行]
    D -->|否| F[记录漂移事件]
    F --> G[通知 Verifier Agent]
    F --> H[更新 drift-report.md]

漂移检测的核心算法是将计划中的预期产出(如「应创建 3 个文件」)与实际结果(如「只创建了 1 个文件」)进行对比。drift.cjs 提供了一套声明性 API,允许工作流在关键节点注册检测点:

// drift.cjs 的使用模式
const drift = require('./drift.cjs');

// 在计划阶段注册预期
const checkpoint = drift.expect({
  filesCreated: 3,
  testsPassing: true,
  coverageAbove: 0.8
});

// 在执行阶段检测实际结果
const actual = {
  filesCreated: 1,
  testsPassing: false,
  coverageAbove: 0.45
};

const report = drift.compare(checkpoint, actual);
// report.driftScore = 0.67 (越高表示漂移越大)
// report.violations = ['filesCreated', 'testsPassing', 'coverageAbove']

二、引擎层的职责

五个 .cjs 模块共同构成了 GSD 的底层引擎层。如果说 TypeScript SDK 是「大脑」,那么引擎层就是「小脑」——它们在大脑睡着时(例如 SDK 未安装或损坏)仍能维持基本生命体征。

2.1 运行时抽象

flowchart TB
    subgraph SDK_Layer["SDK 层(TypeScript)"]
        S1[Phase Runner]
        S2[Context Engine]
        S3[Plan Parser]
    end

    subgraph Engine_Layer["引擎层(CommonJS)"]
        E1[core.cjs]
        E2[state.cjs]
        E3[verify.cjs]
        E4[model-profiles.cjs]
        E5[drift.cjs]
    end

    subgraph System_Layer["系统层"]
        SYS1[Node.js 内置模块]
        SYS2[文件系统]
        SYS3[环境变量]
    end

    S1 -->|"调用 engine API"| E1
    S2 -->|"读取/写入状态"| E2
    S3 -->|"查询模型元数据"| E4
    E1 --> E2
    E1 --> E3
    E1 --> E5
    E2 --> SYS2
    E3 --> SYS1
    E4 --> SYS3

引擎层为 SDK 层提供了一个稳定的抽象层。SDK 不需要知道状态是写入了 SQLite 还是 JSON,也不需要了解模型配置的来源——这些细节都被封装在引擎层中。这种分层带来的好处是:

  1. 可替换性:未来可以将 JSON 状态存储替换为 Redis 或 SQLite,SDK 层代码无需改动
  2. 可测试性:引擎层的 .cjs 模块可以独立测试,不依赖 TypeScript 编译链路
  3. 可降级性:当 SDK 层不可用时,CLI 命令仍可通过引擎层提供基本功能

2.2 状态持久化

状态持久化是引擎层最核心的职责。GSD 的执行流程可能跨越多个进程(例如,用户先执行 gsd plan,关闭终端,第二天再执行 gsd execute),因此状态必须持久化到磁盘。

引擎层的状态管理遵循**写时复制(Copy-on-Write)**策略:

sequenceDiagram
    participant SDK as SDK 层
    participant Core as core.cjs
    participant State as state.cjs
    participant FS as 文件系统

    SDK->>Core: updateState({ phase: 'execute' })
    Core->>State: writeState(delta)
    State->>FS: read existing state
    FS-->>State: currentState
    State->>State: merge(currentState, delta)
    State->>FS: write to .gsd/state.json.tmp
    State->>FS: rename to .gsd/state.json
    FS-->>State: ok
    State-->>Core: success
    Core-->>SDK: ok

这个流程确保了:

  • 原子性:即使进程在写入过程中崩溃,也不会留下损坏的状态文件
  • 隔离性:SDK 层操作的是内存中的状态副本,只有显式调用 commit() 才会触发磁盘写入
  • 可观测性:状态文件的每一次变更都被记录在 .gsd/state.log 中,便于调试和审计

2.3 配置解析

引擎层的配置解析职责体现在 core.cjs配置归一化逻辑中。GSD 的配置来源非常复杂:

配置来源优先级示例
CLI 参数最高--runtime=claude-code --verbose
环境变量GSD_RUNTIME=claude-code
项目配置中低.gsd/config.json
用户全局配置~/.gsd/config.json
内置默认值最低core.cjs 中的 DEFAULTS 对象
// core.cjs 中的配置归一化逻辑
function normalizeConfig(inputs) {
  const defaults = require('./defaults.json');
  const globalConfig = loadIfExists('~/.gsd/config.json');
  const projectConfig = loadIfExists('.gsd/config.json');
  const envConfig = parseEnvVars('GSD_');
  const cliConfig = inputs;

  return deepMerge(defaults, globalConfig, projectConfig, envConfig, cliConfig);
}

deepMerge 的实现特别注意了数组覆盖策略:当多个来源都定义了 runtimes 数组时,高优先级的配置会完全覆盖低优先级的数组,而不是合并。这避免了「半吊子配置」的问题——用户如果在 CLI 中指定了运行时列表,就应该完全信任用户的判断。


三、bin/install.js(275KB)架构回顾

在第 4 篇中,我们首次介绍了 bin/install.js 这个 275KB 的巨型安装脚本。经过 41 篇文章的积累,现在是时候用更深入的视角重新审视它了。

3.1 为什么安装器有 275KB?

275KB 对于一个安装脚本来说似乎过大。但拆解其内容后,你会发现它并非臃肿,而是内聚——它将大量运行时适配逻辑直接嵌入脚本,避免了对网络或外部依赖的依赖。

pie title install.js 体积构成
    "运行时适配模板" : 45
    "内容转换引擎" : 25
    "文件系统操作" : 15
    "交互式 CLI UI" : 10
    "验证逻辑" : 5

3.2 运行时检测

安装器的第一步是运行时检测——扫描用户系统中已安装的 AI 编程工具:

// install.js 中的运行时检测逻辑(简化)
const RUNTIME_DETECTION_RULES = [
  {
    name: 'claude-code',
    check: () => fs.existsSync(path.join(os.homedir(), '.claude', 'settings.json')),
    commandPath: () => which('claude')
  },
  {
    name: 'cursor',
    check: () => fs.existsSync(path.join(os.homedir(), '.cursor', 'rules')),
    commandPath: () => which('cursor')
  },
  {
    name: 'copilot',
    check: () => fs.existsSync(path.join(os.homedir(), '.github', 'copilot')),
    commandPath: () => null // Copilot 无独立 CLI
  }
];

function detectRuntimes() {
  return RUNTIME_DETECTION_RULES
    .filter(rule => rule.check())
    .map(rule => ({ name: rule.name, path: rule.commandPath() }));
}

检测逻辑的设计体现了防御性编程:每个运行时的检测都是独立的,一个运行时的检测失败不会影响其他运行时的检测。即使 .claude/settings.json 是损坏的 JSON,detectRuntimes() 仍会返回其他已成功检测的运行时。

3.3 内容转换

安装器最核心的工程价值在于其内容转换引擎。GSD 的源文件是统一的 Markdown 格式,但不同的运行时要求不同的目标格式:

运行时源格式目标格式转换操作
Claude Code.md.md复制 + 注入 frontmatter
Codex.md.mdTOML frontmatter 转换
Copilot.md.md工具名映射(Bash→execute)
Cursor.md.cursorrules提取规则 + 格式化
Cline.md.clinerules规则提取 + 合并
flowchart TD
    A[源 Markdown 文件] --> B{目标运行时}
    B -->|Claude Code| C[注入 slash-command frontmatter]
    B -->|Codex| D[转换为 TOML frontmatter]
    B -->|Copilot| E[映射工具名]
    B -->|Cursor| F[提取规则并格式化为 .cursorrules]
    B -->|Cline| G[提取规则并合并到 .clinerules]
    C --> H[写入目标路径]
    D --> H
    E --> H
    F --> H
    G --> H

内容转换引擎的关键设计是纯函数转换:给定相同的输入和运行时参数,转换结果总是确定的。这使得安装过程可以被单元测试覆盖,也支持幂等安装——多次运行安装器不会产生不同的结果。

3.4 文件写入

安装器的文件写入逻辑不是简单的 fs.copyFileSync()。它实现了三阶段安全写入

  1. 备份阶段:如果目标文件已存在,先将其复制到 .backup/ 目录
  2. 写入阶段:将转换后的内容写入目标路径的 .tmp 文件
  3. 提交阶段:原子性地将 .tmp 文件重命名为最终文件名
// install.js 中的安全写入逻辑
function safeWrite(targetPath, content) {
  const backupDir = path.join(path.dirname(targetPath), '.backup');
  const tmpPath = targetPath + '.tmp';

  // 阶段 1:备份
  if (fs.existsSync(targetPath)) {
    fs.mkdirSync(backupDir, { recursive: true });
    const backupName = `${path.basename(targetPath)}.${Date.now()}`;
    fs.copyFileSync(targetPath, path.join(backupDir, backupName));
  }

  // 阶段 2:写入临时文件
  fs.writeFileSync(tmpPath, content, 'utf-8');

  // 阶段 3:原子提交
  fs.renameSync(tmpPath, targetPath);
}

这种设计确保了安装过程是可逆的:如果安装后发现问题,用户可以从 .backup/ 目录恢复原始文件。同时,原子提交避免了「半写文件」的问题——用户在安装过程中按下 Ctrl+C 不会留下损坏的配置文件。


四、引擎层与 SDK 的关系

经过以上分析,我们可以清晰地看到引擎层与 SDK 之间的协作关系:

flowchart TB
    subgraph User["用户"]
        U1[CLI 命令]
        U2[工作流文件]
    end

    subgraph SDK["SDK 层(TypeScript)"]
        S1[Phase Runner]
        S2[Context Engine]
        S3[Plan Parser]
        S4[Prompt Builder]
    end

    subgraph Engine["引擎层(CommonJS)"]
        E1[core.cjs
调度中心] E2[state.cjs
状态持久化] E3[verify.cjs
环境验证] E4[model-profiles.cjs
模型配置] E5[drift.cjs
漂移检测] end subgraph Installer["安装器"] I1[install.js
运行时检测] I2[install.js
内容转换] I3[install.js
文件写入] end subgraph System["系统层"] SYS1[文件系统] SYS2[AI 运行时] end U1 --> S1 U2 --> S2 S1 -->|"调用 engine.run()"| E1 S2 -->|"读写状态"| E2 S3 -->|"查询模型限制"| E4 S4 -->|"验证环境"| E3 S1 -->|"检测漂移"| E5 E1 --> I1 I1 --> I2 I2 --> I3 I3 --> SYS1 E2 --> SYS1 I1 --> SYS2

这张图揭示了 GSD 架构的一个关键设计决策:引擎层与 SDK 层不是上下级关系,而是并列协作关系

  • SDK 层负责「高级编排」:阶段管理、上下文组装、Agent 调度——这些工作需要类型安全和复杂的抽象
  • 引擎层负责「底层基础设施」:状态持久化、环境验证、配置解析——这些工作需要稳定性和最小依赖
  • 安装器负责「部署适配」:运行时检测、内容转换、文件写入——这些工作需要独立运行、不依赖项目环境

三者通过清晰的接口契约协作:

// SDK 层调用引擎层的契约示例
interface EngineAPI {
  init(config: RuntimeConfig): Promise<void>;
  getState(): ProjectState;
  setState(partial: Partial<ProjectState>): void;
  verify(checks: string[]): VerificationResult[];
  getModelProfile(modelId: string): ModelProfile;
  detectDrift(expected: Expectation, actual: Actual): DriftReport;
}

这种分层架构的优势在于:

  1. 渐进式采用:用户可以先通过安装器获得基础功能,再逐步引入 SDK 层的高级特性
  2. 故障隔离:SDK 层的类型错误不会波及引擎层,引擎层的文件系统错误不会导致 SDK 层崩溃
  3. 独立演进:引擎层可以针对稳定性优化(减少依赖、增加容错),SDK 层可以针对功能性优化(新特性、更好的类型推断)

五、小结

本文系统解析了 GSD 底层引擎层与安装器的架构设计:

  1. bin/lib/ 目录:五个 .cjs 模块构成了 GSD 的底层运行时,总代码量约 23KB,却承载了状态持久化、环境验证、模型配置、漂移检测四大基础设施职责
  2. core.cjs:最小依赖原则的典范,仅依赖 Node.js 内置模块,提供生命周期管理和配置归一化
  3. state.cjs:原子性写入 + 分层存储,确保进程崩溃后状态可恢复
  4. verify.cjs:惰性执行的验证器注册机制,支持安装器和 SDK 层的按需验证
  5. model-profiles.cjs:模型特性抽象层,新模型发布时只需更新配置
  6. drift.cjs:声明式漂移检测 API,连接计划预期与实际执行结果
  7. install.js(275KB):内置完整运行时检测、内容转换和文件写入引擎,支持 15+ 运行时的幂等安装

引擎层与 SDK 层的协作关系充分体现了 GSD 的工程成熟度:不是把所有逻辑塞进一个层,而是让每一层专注于自己最擅长的事情


预告:第 46 篇——SDK 扩展开发指南

在下一篇文章中,我们将从「阅读源码」转向「动手扩展」,深入讲解如何基于 GSD SDK 开发自定义扩展:

  • Runtime 适配器开发:如何为新的 AI 编程工具添加运行时支持?
  • Phase Hook 开发:如何在阶段执行前后注入自定义逻辑?
  • Query Handler 开发:如何扩展 GSD.SDK.query() 的查询能力?
  • 插件打包与发布:如何将扩展打包为可复用的 GSD 插件?

下一篇预告: 第 46 篇《SDK 扩展开发指南》

我们将深入解析 GSD SDK 的扩展机制,包括如何注册自定义 Agent、如何扩展命令系统、以及如何开发第三方插件。这是动手改造 GSD 的实战指南,敬请期待。