这是「GSD 全景代码解析」专题的第 38 篇。
在前 37 篇文章中,我们系统梳理了 GSD 的命令系统、工作流编排层、Agent 执行层和上下文工程体系。但有一个关键问题始终悬而未决:工作流文件中频繁出现的 gsd-sdk 调用,究竟是如何工作的?
<!-- 工作流中的典型 SDK 调用 -->
{{ GSD.SDK.query('init.execute') }}
{{ GSD.SDK.runPhase('plan') }}
{{ GSD.SDK.parsePlan(planText) }}这些调用不是魔法,而是 GSD TypeScript SDK 提供的标准化接口。本篇将揭开这个连接工作流与底层运行时的关键桥梁。
一、SDK 的定位:编排层与运行时的翻译官
在 GSD 的分层架构中,SDK 承担着承上启下的核心角色:
flowchart TB
subgraph User["用户层"]
U1[自然语言指令]
end
subgraph Orchestration["编排层"]
W1[工作流 .md]
A1[Agent .md]
end
subgraph SDK_Layer["SDK 层"]
S1[CLI Transport]
S2[Phase Runner]
S3[Context Engine]
S4[Plan Parser]
S5[Prompt Builder]
end
subgraph Runtime["运行时层"]
R1[Claude Code]
R2[Gemini CLI]
R3[Codex]
R4[Copilot]
end
U1 --> W1
W1 --> S1
A1 --> S1
S1 --> S2
S2 --> S3
S2 --> S4
S2 --> S5
S1 --> R1
S1 --> R2
S1 --> R3
S1 --> R41.1 为什么需要 SDK
工作流和 Agent 文件本质上是带 frontmatter 的 Markdown。它们不能直接操作文件系统、不能发起 HTTP 请求、也不能感知当前运行在哪个 AI IDE 中。这些底层能力必须由一个专门的软件层来提供。
GSD SDK 的设计目标非常清晰:
| 目标 | 说明 |
|---|---|
| 运行时无关 | 同一套工作流可以在 Claude Code、Gemini CLI、Codex 等 15+ 运行时上无缝运行 |
| 类型安全 | 全链路 TypeScript 类型覆盖,编译期捕获接口不匹配问题 |
| 声明式接口 | 工作流作者只需关心"做什么",不关心"怎么做" |
| 可测试 | 每个模块独立可测,支持 Mock Transport 进行单元测试 |
1.2 SDK 与 GSD 其他组件的关系
flowchart LR
subgraph WF["工作流"]
wf1[execute-phase.md]
wf2[plan-phase.md]
end
subgraph SDK["GSD SDK"]
sdk1[query/]
sdk2[plan-parser.ts]
sdk3[phase-runner.ts]
sdk4[context-engine.ts]
end
subgraph Agent["Agent"]
ag1[gsd-executor.md]
ag2[gsd-planner.md]
end
subgraph State["状态文件"]
st1[STATE.md]
st2[ROADMAP.md]
end
wf1 -->|调用| SDK
ag1 -->|调用| SDK
SDK -->|读写| State
SDK -->|加载| Agent
SDK -->|解析输出| wf2SDK 不直接执行代码生成或架构设计——那是 Agent 的职责。SDK 提供的是通用基础设施:上下文查询、阶段执行、计划解析、提示词构建、工具调用等。
二、sdk/src/ 目录结构全景
GSD SDK 是一个标准的 TypeScript 项目,核心源码位于 sdk/src/ 目录下。让我们按文件大小和重要性逐一解析:
sdk/src/
├── cli.ts # 22KB - CLI Transport 入口
├── phase-runner.ts # 39KB - 阶段执行引擎(最大文件)
├── types.ts # 23KB - 全系统类型定义
├── gsd-tools.ts # 20KB - 工具函数集
├── plan-parser.ts # 14KB - 计划文本解析器
├── prompt-builder.ts # 12KB - 提示词构建器
├── context-engine.ts # 11KB - 上下文引擎
├── index.ts # 5KB - 包入口
└── query/
├── index.ts # 查询总入口
├── init.ts # init.* 查询实现
├── context.ts # 上下文查询
├── state.ts # 状态文件查询
└── agent.ts # Agent 元数据查询2.1 文件大小背后的架构信号
这 8 个文件的体积分布非常有信息量:
| 文件 | 大小 | 职责密度 | 架构意义 |
|---|---|---|---|
phase-runner.ts | 39KB | 极高 | 核心引擎,包含阶段生命周期管理、Hook 调用、状态转换 |
types.ts | 23KB | 高 | 全系统契约,160+ 类型定义,是架构的"源代码" |
cli.ts | 22KB | 高 | Transport 层主实现,处理 15+ 运行时的适配逻辑 |
gsd-tools.ts | 20KB | 中高 | 工具函数库,被所有上层模块依赖 |
plan-parser.ts | 14KB | 中高 | 将自然语言计划解析为结构化数据 |
prompt-builder.ts | 12KB | 中 | 动态提示词组装,支持模板和变量插值 |
context-engine.ts | 11KB | 中 | 上下文加载、缓存和预算控制 |
query/ | ~18KB | 中 | 声明式查询 DSL 的实现 |
关键洞察:phase-runner.ts 以 39KB 独占鳌头,说明阶段执行是 SDK 最复杂的领域。这与我们在第 13 篇《工作流架构总览》中观察到的现象一致——工作流的核心复杂度在于阶段(Phase)的生命周期管理。
三、核心模块详解
3.1 types.ts:架构的契约层
types.ts 是 SDK 中最早应该阅读的文件。它定义了 GSD 全系统的类型契约,相当于架构的"源代码"。
flowchart TB
subgraph Types["types.ts 核心类型族"]
T1[GSDContext]
T2[GSDPhase]
T3[GSDPlan]
T4[GSDRuntime]
T5[GSDTool]
T6[GSDHook]
end
subgraph Relations["类型间关系"]
T1 -->|contains| T2
T2 -->|produces| T3
T4 -->|executes| T2
T5 -->|called by| T2
T6 -->|injected into| T2
end核心的 6 个类型族覆盖了 SDK 的全部领域:
GSDContext:上下文容器
interface GSDContext {
project: ProjectMeta; // 项目元数据(名称、路径、配置)
state: StateSnapshot; // STATE.md 的内存表示
roadmap: RoadmapItem[]; // ROADMAP.md 解析结果
agents: AgentRegistry; // 当前可用的 Agent 列表
references: ReferenceDoc[]; // @reference 加载的参考文档
budget: ContextBudget; // 上下文预算(tokens/字符数)
}GSDContext 是一个不可变快照。每次 SDK 操作都会产生新的 Context 实例,旧版本可供回溯和审计。
GSDPhase:阶段定义
interface GSDPhase {
id: string; // 唯一标识,如 "plan", "execute", "verify"
name: string; // 人类可读名称
hooks: PhaseHooks; // 生命周期钩子(before/after/around)
allowedAgents: string[]; // 本阶段可调用的 Agent 白名单
outputFormat: OutputSpec; // 期望的输出格式规范
exitCriteria: string[]; // 阶段完成的判定条件
}GSDPhase 是工作流编排的基本执行单元。一个工作流通常由 3-7 个 Phase 串联或并联组成。
GSDPlan:结构化计划
interface GSDPlan {
version: '2.0';
phases: PlanPhase[];
dependencies: Dependency[]; // 阶段间依赖图
estimatedEffort: Effort; // 预估工作量
risks: RiskItem[]; // 风险清单
checkpoints: Checkpoint[]; // 检查点(可验证的里程碑)
}Plan 是 GSD spec-driven 开发的核心产物。plan-parser.ts 的职责就是将自然语言计划文本转换为这个结构化对象。
GSDRuntime:运行时抽象
interface GSDRuntime {
name: RuntimeName; // 'claude-code' | 'gemini-cli' | 'codex' | ...
version: string;
capabilities: Capability[]; // 支持的特性(tools, vision, mcp 等)
commandPrefix: string; // 命令前缀(/gsd- 或 $gsd-)
toolMapping: ToolMap; // 工具名映射(如 Copilot 的 Read→read)
}GSDRuntime 是 GSD 实现运行时无关性的关键抽象。CLI Transport 在初始化时会检测当前环境,构造对应的 Runtime 实例。
3.2 phase-runner.ts:阶段执行引擎
作为 SDK 中最大的文件(39KB),phase-runner.ts 是整个系统的"心脏"。
flowchart LR
subgraph Input["输入"]
I1[Phase ID]
I2[Context]
I3[Agent 选择]
end
subgraph Runner["Phase Runner"]
R1[验证阶段权限]
R2[执行 beforeHooks]
R3[调用 Agent]
R4[解析 Agent 输出]
R5[执行 afterHooks]
R6[更新 STATE.md]
R7[执行 aroundHooks]
end
subgraph Output["输出"]
O1[新 Context]
O2[PhaseResult]
end
I1 --> R1
I2 --> R1
I3 --> R1
R1 --> R2
R2 --> R3
R3 --> R4
R4 --> R5
R5 --> R6
R6 --> R7
R7 --> O1
R7 --> O2
R3 -.->|aroundHook 包裹| R3Phase Runner 的执行流程遵循严格的生命周期契约:
- 验证(Validation):检查当前 Runtime 是否支持该 Phase,Agent 是否在白名单中
- 前置钩子(beforeHooks):加载上下文、设置环境变量、记录日志
- 执行(Execution):通过 CLI Transport 向 Runtime 发送指令,启动 Agent
- 解析(Parsing):使用
plan-parser.ts将 Agent 的自然语言输出转为结构化数据 - 后置钩子(afterHooks):验证输出格式、更新状态文件、触发副作用
- 环绕钩子(aroundHooks):在 before/after 之间包裹,用于重试、超时、熔断等横切逻辑
Hook 系统是 Phase Runner 的灵魂
interface PhaseHooks {
before?: HookFn[]; // 同步执行,顺序调用
after?: HookFn[]; // 同步执行,顺序调用
around?: AroundHookFn[]; // 嵌套执行,类似 Koa 的洋葱模型
onError?: ErrorHookFn[]; // 错误处理,支持重试和降级
}Hook 机制让 GSD 可以在不修改核心执行逻辑的情况下,插入自定义行为。例如:
- 审计 Hook:记录每个 Phase 的输入输出,用于事后分析
- 预算 Hook:在 before 阶段检查上下文预算,在 after 阶段更新已用量
- 重试 Hook:around 级别捕获异常,按指数退避策略重试
- 通知 Hook:after 阶段发送 Webhook 通知外部系统
3.3 cli.ts:CLI Transport 层
cli.ts 是 SDK 与外部世界交互的唯一出口。它屏蔽了 15+ 运行时的差异,提供统一的命令接口。
flowchart TB
subgraph SDK_Internal["SDK 内部"]
S1[phase-runner.ts]
S2[query/index.ts]
S3[gsd-tools.ts]
end
subgraph Transport["CLI Transport"]
T1[命令序列化]
T2[运行时检测]
T3[适配器选择]
T4[命令发射]
T5[输出捕获]
end
subgraph Runtimes["目标运行时"]
R1[Claude Code]
R2[Gemini CLI]
R3[Codex]
R4[Copilot]
R5[Cursor]
end
S1 --> T1
S2 --> T1
S3 --> T1
T1 --> T2
T2 --> T3
T3 -->|适配器 A| T4
T3 -->|适配器 B| T4
T4 --> R1
T4 --> R2
T4 --> R3
T4 --> R4
T4 --> R5
R1 --> T5
R2 --> T5
T5 --> S1运行时检测机制
CLI Transport 在初始化时会通过环境变量和文件系统探测当前运行时:
function detectRuntime(): GSDRuntime {
if (process.env.CLAUDE_CODE) {
return createClaudeCodeRuntime();
}
if (process.env.GEMINI_CLI_VERSION) {
return createGeminiRuntime();
}
if (existsSync('~/.codex/config.toml')) {
return createCodexRuntime();
}
// ... 更多检测逻辑
return createGenericRuntime(); // 兜底
}命令适配策略
不同运行时对命令格式、工具调用和输出解析有不同的要求。CLI Transport 通过适配器模式处理这些差异:
| 运行时 | 特殊适配 |
|---|---|
| Claude Code | 原生 Custom Slash Commands,直接透传 |
| Codex | Skills 格式,TOML 配置包裹 |
| Copilot | 工具名映射(Read→read, Bash→execute) |
| Cursor | .cursor/rules 文件集成 |
| Cline | .clinerules 规则文件注入 |
输出捕获与解析
Agent 的输出通常是自然语言文本。CLI Transport 负责:
- 流式捕获:实时读取 stdout/stderr
- 边界检测:通过特殊标记(如
<!-- GSD_OUTPUT_BEGIN -->)识别结构化内容 - 错误分类:区分运行时错误(工具调用失败)和业务错误(Agent 报告的计划不可行)
- 超时处理:为长时间运行的 Agent 设置合理的超时阈值
3.4 plan-parser.ts:从自然语言到结构化数据
plan-parser.ts 解决了一个核心问题:Agent 输出的是自然语言,但工作流需要结构化数据。
flowchart LR
subgraph Input["输入"]
I1[自然语言计划文本]
end
subgraph Parser["Plan Parser"]
P1[分块:按标题层级切分]
P2[提取:正则匹配关键字段]
P3[验证:JSON Schema 校验]
P4[补全:填充默认值]
end
subgraph Output["输出"]
O1[GSDPlan 对象]
end
I1 --> P1
P1 --> P2
P2 --> P3
P3 --> P4
P4 --> O1Parser 采用多策略容错设计:
- 显式标记优先:如果 Agent 输出了
\``gsd-plan` 代码块,直接按 JSON 解析 - Markdown 结构解析:如果没有显式标记,通过标题层级和列表结构推断层级关系
- 正则提取:针对常见模式(如 "Risk: XXX"、"Estimated: YYY hours")使用正则提取关键字段
- LLM 后处理:如果前三种策略都失败,构造一个精简的 prompt 让 LLM 重新格式化输出
3.5 context-engine.ts:上下文加载与预算控制
context-engine.ts 是我们在第 31-37 篇中深入讨论的 Context Engineering 的工程实现。
flowchart TB
subgraph Request["查询请求"]
R1[query('init.execute')]
end
subgraph Engine["Context Engine"]
E1[解析查询 DSL]
E2[加载基础上下文]
E3[应用 @reference]
E4[计算 token 预算]
E5[优先级排序]
E6[截断或分页]
end
subgraph Output["输出"]
O1[裁剪后的 Context]
O2[预算使用报告]
end
R1 --> E1
E1 --> E2
E2 --> E3
E3 --> E4
E4 --> E5
E5 --> E6
E6 --> O1
E6 --> O2Context Engine 的核心算法是优先级驱动的预算分配:
function allocateBudget(context: GSDContext, budget: ContextBudget): TrimmedContext {
const items = flattenContext(context);
// 按优先级排序(必需 > 重要 > 可选)
const sorted = items.sort((a, b) => b.priority - a.priority);
let usedTokens = 0;
const selected: ContextItem[] = [];
for (const item of sorted) {
if (usedTokens + item.tokenCount <= budget.maxTokens) {
selected.push(item);
usedTokens += item.tokenCount;
} else if (budget.allowTruncation) {
const remaining = budget.maxTokens - usedTokens;
selected.push(truncate(item, remaining));
break;
}
}
return { items: selected, usedTokens, dropped: sorted.length - selected.length };
}3.6 prompt-builder.ts:动态提示词组装
prompt-builder.ts 将静态模板和动态变量结合,生成最终发送给 LLM 的提示词。
interface PromptTemplate {
id: string;
template: string; // 支持 {{variable}} 插值
partials: string[]; // 子模板引用
version: string; // SemVer,用于缓存失效
requiredVars: string[]; // 必填变量列表
}
function buildPrompt(template: PromptTemplate, vars: Record<string, any>): string {
// 1. 验证必填变量
validateVars(template.requiredVars, vars);
// 2. 加载并渲染 partials
const partials = template.partials.map(p => loadPartial(p));
// 3. 主模板插值
let output = template.template;
for (const [key, value] of Object.entries(vars)) {
output = output.replace(new RegExp(`{{\\s*${key}\\s*}}`, 'g'), String(value));
}
// 4. 拼接 partials
return partials.join('\n---\n') + '\n---\n' + output;
}Prompt Builder 支持模板继承和条件渲染,使得工作流作者可以用声明式语法构建复杂的提示词:
<!-- 模板示例 -->
{{> header}}
{{#if roadmap}}
## 当前路线图
{{roadmap}}
{{/if}}
{{#each agents}}
- {{name}}: {{description}}
{{/each}}
{{> footer}}3.7 gsd-tools.ts:工具函数库
gsd-tools.ts 是 SDK 的"瑞士军刀",提供各模块共享的底层工具函数:
| 类别 | 函数示例 | 用途 |
|---|---|---|
| 文件操作 | readFileSafe, writeFileAtomic | 带编码检测和原子写入的文件 I/O |
| 路径解析 | resolveGSDPath, findProjectRoot | 从任意位置定位 GSD 项目根目录 |
| 文本处理 | slugify, extractFrontmatter | Markdown frontmatter 解析和字符串处理 |
| Git 集成 | getGitStatus, getDiffStats | 读取仓库状态,用于执行前的基线记录 |
| 校验 | validateAbbrlink, checkSchema | abbrlink 唯一性检查和 JSON Schema 验证 |
| 日志 | logPhaseStart, logPhaseEnd | 结构化日志,支持 JSONL 格式 |
这些工具函数遵循纯函数优先原则:无副作用、输入确定则输出确定、便于单元测试。
3.8 query/ 目录:声明式查询 DSL
query/ 目录实现了 GSD SDK 的声明式查询接口,让工作流可以用字符串表达式获取上下文:
// 工作流中的调用
SDK.query('init.execute'); // 加载 execute 阶段的初始化上下文
SDK.query('context.agents'); // 获取当前可用的 Agent 列表
SDK.query('state.roadmap'); // 获取路线图状态
SDK.query('agent.gsd-executor'); // 获取特定 Agent 的元数据flowchart LR
subgraph Query["查询表达式"]
Q1["init.execute"]
Q2["context.agents"]
Q3["state.roadmap"]
end
subgraph Router["Query Router"]
R1[按命名空间分发]
end
subgraph Handlers["处理模块"]
H1[init.ts]
H2[context.ts]
H3[state.ts]
H4[agent.ts]
end
Q1 --> R1
Q2 --> R1
Q3 --> R1
R1 -->|namespace=init| H1
R1 -->|namespace=context| H2
R1 -->|namespace=state| H3
R1 -->|namespace=agent| H4Query DSL 的设计借鉴了 GraphQL 的思想:一次查询,精确获取所需数据,避免过度加载。每个 query handler 都返回一个标准的 QueryResult<T>,包含数据、元信息和缓存控制头。
四、模块依赖关系图
flowchart TB
subgraph Entry["入口层"]
IDX[index.ts]
CLI[cli.ts]
end
subgraph Core["核心层"]
PR[phase-runner.ts]
QE[query/index.ts]
end
subgraph Engine["引擎层"]
CE[context-engine.ts]
PB[prompt-builder.ts]
PP[plan-parser.ts]
end
subgraph Foundation["基础层"]
GT[gsd-tools.ts]
TP[types.ts]
end
IDX --> CLI
IDX --> PR
IDX --> QE
CLI --> PR
CLI --> GT
PR --> CE
PR --> PB
PR --> PP
PR --> GT
QE --> CE
QE --> GT
CE --> GT
PB --> GT
PP --> GT
GT --> TP
CE --> TP
PB --> TP
PP --> TP
PR --> TP
CLI --> TP4.1 依赖关系的设计原则
从依赖图中可以观察到三个重要原则:
单向依赖:所有箭头都向下或平级,不存在循环依赖。
types.ts位于最底层,被所有人依赖但不依赖任何人。入口隔离:
index.ts是唯一的公共 API 入口,内部模块不直接暴露。这种"宽入口、窄出口"的设计便于后续重构。工具下沉:
gsd-tools.ts被所有上层模块使用,但它只依赖types.ts。这意味着工具函数的修改影响面可控。
五、TypeScript 类型系统的设计哲学
GSD SDK 的类型系统不是简单的"加类型注解",而是架构设计的表达工具。
5.1 区分系统类型与业务类型
// ===== 系统类型(SDK 内部使用)=====
interface TransportAdapter {
send(command: Command): Promise<Response>;
supports(capability: Capability): boolean;
}
// ===== 业务类型(工作流和 Agent 可见)=====
interface GSDPhase {
id: PhaseId;
hooks: PhaseHooks;
// ...
}
// ===== 边界类型(序列化/反序列化)=====
interface SerializedPlan {
version: '2.0';
phases: Array<Pick<GSDPlan, 'id' | 'name'>>;
}系统类型关注"如何运行",业务类型关注"做什么",边界类型关注"如何传输"。三者严格分离,避免概念混淆。
5.2 branded type 防止标识符混用
GSD 使用 TypeScript 的** branded type** 技术,防止不同领域的字符串标识符被错误混用:
type PhaseId = string & { __brand: 'PhaseId' };
type AgentId = string & { __brand: 'AgentId' };
type Abbrlink = string & { __brand: 'Abbrlink' };
function createPhaseId(id: string): PhaseId {
return id as PhaseId;
}
// 编译期错误:不能将 AgentId 赋值给 PhaseId
const agentId: AgentId = createAgentId('gsd-executor');
const phaseId: PhaseId = agentId; // ❌ Type Error这种设计在大型代码库中特别有价值——当系统有 60+ 工作流、33+ Agent、80+ 命令时,标识符类型的混淆是导致 bug 的高频原因。
5.3 条件类型实现运行时特性检测
type RuntimeTools<T extends GSDRuntime> =
T extends { capabilities: infer C }
? C extends (infer U)[]
? U extends { type: 'tool' }
? U['name']
: never
: never
: never;
// 使用:获取 Claude Code 支持的工具名
type ClaudeTools = RuntimeTools<ClaudeCodeRuntime>;
// → 'Read' | 'Bash' | 'Edit' | 'Glob' | ...条件类型让 SDK 在编译期就能根据运行时特性进行类型收窄,实现"一个 SDK,多种类型体验"。
六、CLI Transport 的扩展机制
CLI Transport 的设计预留了三个扩展点:
6.1 自定义 Runtime 适配器
interface RuntimeAdapterFactory {
detect(): boolean; // 是否匹配当前环境
create(): GSDRuntime; // 构造 Runtime 实例
}
// 注册自定义适配器
SDK.registerRuntimeAdapter({
detect: () => existsSync('.my-custom-runtime'),
create: () => ({
name: 'my-runtime',
capabilities: ['tools', 'mcp'],
// ...
})
});6.2 自定义 Hook
SDK.registerHook('execute', {
around: async (ctx, next) => {
console.time('phase-execute');
const result = await next();
console.timeEnd('phase-execute');
return result;
}
});6.3 自定义 Query Handler
SDK.registerQuery('custom', async (params, context) => {
const data = await fetchCustomData(params);
return { data, meta: { cached: false } };
});这三个扩展点遵循开闭原则:SDK 核心稳定不变,新需求通过扩展机制实现,无需修改源码。
七、小结
本文系统解析了 GSD TypeScript SDK 的架构设计:
- 定位:SDK 是连接工作流编排层与底层运行时的翻译官和基础设施层,不是业务逻辑的执行者
- 目录结构:8 个核心文件 + query/ 目录,总代码量约 164KB,其中
phase-runner.ts(39KB)和types.ts(23KB)承载最多架构信息 - 模块关系:单向依赖、无循环依赖、
types.ts作为公共契约层 - 类型系统:160+ 类型定义,branded type 防混用,条件类型实现编译期特性检测
- CLI Transport:适配器模式屏蔽 15+ 运行时差异,流式输出捕获,多策略容错解析
- 扩展机制:Runtime 适配器、Phase Hook、Query Handler 三个扩展点,支撑 SDK 的可持续发展
SDK 的设计充分体现了 GSD 的工程成熟度:它不是为某个特定场景快速 hack 出来的工具,而是经过深思熟虑的平台级基础设施。工作流和 Agent 作者可以在完全不关心底层运行时差异的情况下,编写出可移植、可测试、可维护的编排逻辑。
下一篇预告: 第 39 篇《Phase Runner 核心引擎》——深入剖析 phase-runner.ts 的 39KB 源码,详解阶段生命周期管理、Hook 执行模型、状态机转换和错误恢复机制,带你领略 GSD 最核心的执行引擎。