# 输出样式系统完全解析

本章解析 Claude Code 的输出风格切换机制——如何通过加载不同的样式配置，让同一个 AI 在简洁、教学、自定义等多种输出风格之间无缝切换。

---

> **🌍 行业背景**：输出风格定制是 AI 编程工具中一个正在兴起的功能方向，各工具选择了不同的实现范式：
> 
> - **Cursor**：从早期的 `.cursorrules` 单文件演进到 `.cursor/rules/` 多规则目录，支持 Project Rules 和 User Rules 分层，并可通过 `@rules` 在对话中动态引用特定规则——功能上已与 Claude Code 的多层样式系统高度对标。
> - **Windsurf（Codeium）**：通过 `.windsurfrules` 文件和全局 Rules 功能实现了项目级与用户级的分层配置。
> - **GitHub Copilot**：在 VS Code 的 `settings.json` 中通过 `github.copilot.chat.codeGeneration.instructions` 嵌入指令文本——与 IDE 设置系统深度集成，零学习成本但不支持多预设切换。
> - **Aider**：通过 `--edit-format` 在 diff/whole/ask 等结构化输出格式间切换，侧重于代码编辑的**机械格式**而非 AI 的**表达风格**——这是一个有趣的设计分歧：Aider 优化的是工程效率，Claude Code 优化的是用户体验。
> - **ChatGPT**：其后续推出的 Custom Instructions 和 Memory 功能验证了"用户持久化 AI 行为偏好"的市场需求——Claude Code 的样式系统可以看作这个方向在编程工具中的垂直化实现。
> 
> 上述对外部产品的演进描述基于公开资料，具体版本号与时间点可能随产品迭代变化。在这个设计空间中，存在三种范式——(a) IDE 设置集成（Copilot：零成本但不灵活），(b) 纯文本规则文件（Cursor/Aider/Windsurf：简单但缺少结构化元数据），(c) 结构化样式系统（Claude Code：Markdown + Frontmatter 元数据 + 多层优先级合并）。Claude Code 的差异化在于**将样式做成了带 frontmatter 元数据的结构化系统，并配套多层优先级合并**——并非首创"样式即提示词"这个概念。特别是 Learning 模式将"AI 教练"理念具体化为 CLI 环境中的 `TODO(human)` 协议——这在执行层面是新颖的，尽管苏格拉底式对话的教育理念本身已有先例（如 Khan Academy 的 Khanmigo）。

---

## 本章导读

输出样式（Output Styles）是 Claude Code 2.1.88 中一个轻量但影响深远的子系统。它允许用户改变 Claude 的输出风格——同一个编程任务，可以用默认的简洁风格输出，也可以切换为"教学模式"让 Claude 在写代码时附带解释，或者切换为自定义的任何风格。

**技术比喻（OS 视角）**：输出样式系统像操作系统中的**主题引擎（Theme Engine）**——内核（AI 推理）不变，但渲染层（输出方式）可以通过加载不同的主题文件来改变。就像 GTK 主题改变了按钮的外观但不改变按钮的功能，输出样式改变了 Claude 的"说话方式"但不改变它的推理能力。

> 💡 **通俗理解**：输出样式像**聊天软件的主题皮肤**——同一条消息，换个主题后显示效果完全不同。默认主题是简约白，"Explanatory" 主题加上了背景注释框，"Learning" 主题变成了互动教学界面。而且你还能上传自己设计的主题。

## 架构分布

输出样式系统由三个核心文件和若干外围文件组成：

| 文件 | 位置 | 职责 |
|------|------|------|
| `loadOutputStylesDir.ts` | `src/outputStyles/` | 从 `.claude/output-styles/` 加载自定义样式 |
| `outputStyles.ts` | `src/constants/` | 内置样式定义 + 样式合并 + 配置获取 |
| `output-style.tsx` | `src/commands/output-style/` | `/output-style` 命令（已废弃） |
| `OutputStylePicker.tsx` | `src/components/` | 样式选择器 UI |
| `loadPluginOutputStyles.ts` | `src/utils/plugins/` | 从插件加载样式 |

核心模块代码量很小——`loadOutputStylesDir.ts` 98 行，`outputStyles.ts` 216 行（`wc -l` 统计，含空行与注释；非执行语句行数）。与此同时，输出样式的 prompt 会被注入到**每一次 API 调用**的系统提示词中，影响范围远超代码本身的体量。值得注意的是，代码量小并不直接等同于好设计——这也意味着缺少显式的长度限制、内容校验和安全边界检查（详见第 8 节安全分析）。

## 1. 内置样式定义

### 1.1 样式数据结构

`constants/outputStyles.ts` 第 11-23 行定义了样式配置的类型：

```typescript
export type OutputStyleConfig = {
  name: string
  description: string
  prompt: string                           // 注入到系统提示词中的内容
  source: SettingSource | 'built-in' | 'plugin'
  keepCodingInstructions?: boolean         // 是否保留默认编码指令
  forceForPlugin?: boolean                 // 插件是否可以强制应用此样式
}
```

`prompt` 字段是核心——它的内容会被直接注入到 Claude 的系统提示词中，从而改变 Claude 的输出行为。

### 1.2 默认样式（Default）

默认样式的配置值为 `null`：

```typescript
export const DEFAULT_OUTPUT_STYLE_NAME = 'default'

export const OUTPUT_STYLE_CONFIG: OutputStyles = {
  [DEFAULT_OUTPUT_STYLE_NAME]: null,  // null 表示不注入额外提示词
  // ...
}
```

当样式为 `null` 时，不注入任何额外的系统提示词，Claude 使用其默认的输出方式。

### 1.3 Explanatory 模式

"Explanatory"（解释型）模式让 Claude 在执行任务时添加教育性注释（第 43-54 行）：

```typescript
Explanatory: {
  name: 'Explanatory',
  source: 'built-in',
  description: 'Claude explains its implementation choices and codebase patterns',
  keepCodingInstructions: true,
  prompt: `You are an interactive CLI tool that helps users with software 
engineering tasks. In addition to software engineering tasks, you should 
provide educational insights about the codebase along the way.

# Explanatory Style Active
## Insights
In order to encourage learning, before and after writing code, always 
provide brief educational explanations about implementation choices using:
"\`★ Insight ─────────────────────────────────────\`
[2-3 key educational points]
\`─────────────────────────────────────────────────\`"
`
}
```

注意 `keepCodingInstructions: true`——这意味着 Explanatory 样式不会替换默认的编码指令，而是在其基础上**追加**教育性内容。

### 1.4 Learning 模式

"Learning"（学习型）模式更进一步，让 Claude 暂停执行并请求用户动手编写小段代码（第 56-134 行）：

```typescript
Learning: {
  name: 'Learning',
  source: 'built-in',
  prompt: `...
## Requesting Human Contributions
In order to encourage learning, ask the human to contribute 2-10 line 
code pieces when generating 20+ lines involving:
- Design decisions (error handling, data structures)
- Business logic with multiple valid approaches  
- Key algorithms or interface definitions

### Request Format
\`\`\`
● **Learn by Doing**
**Context:** [what's built and why this decision matters]
**Your Task:** [specific function/section in file]
**Guidance:** [trade-offs and constraints to consider]
\`\`\`
  `
}
```

Learning 模式定义了详细的"动手学习"协议：
1. Claude 先搭建代码框架
2. 在关键决策点暂停并请求用户输入
3. 用 `TODO(human)` 标记用户需要填写的位置
4. 用户完成后，Claude 分享一个连接性洞察

这个设计将 AI 从"替你写代码"转变为"教你写代码"。苏格拉底式的教学对话在 AI 教育领域已有先例（如 Khan Academy 的 Khanmigo，2023 年），但 Claude Code 的创新在于将这一理念**具体化为 CLI 环境中的 `TODO(human)` 协议**——这是执行层面的工程创新，而非理念层面的首创。

## 2. 自定义样式加载

### 2.1 目录扫描

`loadOutputStylesDir.ts` 从两个位置加载自定义样式：

```typescript
export const getOutputStyleDirStyles = memoize(
  async (cwd: string): Promise<OutputStyleConfig[]> => {
    const markdownFiles = await loadMarkdownFilesForSubdir(
      'output-styles',
      cwd,
    )
    // ...
  }
)
```

扫描路径：
- 项目级：`.claude/output-styles/*.md`
- 用户级：`~/.claude/output-styles/*.md`

使用与 Skill 系统相同的 `loadMarkdownFilesForSubdir` 基础设施。

### 2.2 Frontmatter 解析

每个自定义样式是一个 Markdown 文件，支持以下 frontmatter 字段：

```yaml
---
name: My Custom Style
description: A concise and focused output style
keep-coding-instructions: true
---

You are a concise developer assistant. Never use bullet points.
Always explain trade-offs in one sentence.
```

解析逻辑（第 34-76 行）：

```typescript
const styles = markdownFiles.map(({ filePath, frontmatter, content, source }) => {
  const fileName = basename(filePath)
  const styleName = fileName.replace(/\.md$/, '')

  // 名称回退：frontmatter.name → 文件名
  const name = (frontmatter['name'] || styleName) as string

  // 描述回退：frontmatter.description → Markdown 首段
  const description =
    coerceDescriptionToString(frontmatter['description'], styleName) ??
    extractDescriptionFromMarkdown(content, `Custom ${styleName} output style`)

  // keep-coding-instructions 布尔值解析
  const keepCodingInstructionsRaw = frontmatter['keep-coding-instructions']
  const keepCodingInstructions =
    keepCodingInstructionsRaw === true || keepCodingInstructionsRaw === 'true'
      ? true
      : keepCodingInstructionsRaw === false || keepCodingInstructionsRaw === 'false'
        ? false
        : undefined

  // 警告：force-for-plugin 只对插件样式有效
  if (frontmatter['force-for-plugin'] !== undefined) {
    logForDebugging(
      `Output style "${name}" has force-for-plugin set, but this option ` +
      `only applies to plugin output styles. Ignoring.`,
      { level: 'warn' },
    )
  }

  return { name, description, prompt: content.trim(), source, keepCodingInstructions }
})
```

### 2.3 keepCodingInstructions 的含义

这个标志控制是否保留 Claude Code 默认的编码指令（即 `prompts.ts` 中的 `getSimpleDoingTasksSection()` 部分）。

源码中的实际判断逻辑（`prompts.ts` 第 564-567 行）：

```typescript
outputStyleConfig === null ||
outputStyleConfig.keepCodingInstructions === true
  ? getSimpleDoingTasksSection()
  : null,
```

这意味着三种取值的行为如下：

- `true`：保留默认编码指令，样式 prompt 作为**追加内容**
- `false`：**跳过** `getSimpleDoingTasksSection()`（即默认编码指令段），样式 prompt 成为系统提示中关于"如何执行编码任务"的唯一指导。**其他系统提示段（介绍、工具使用、语气风格、输出效率等）仍保留**——被跳过的仅是编码任务指导这一段
- `undefined`（未设置）：**行为等同于 `false`**——因为 `undefined === true` 为假，编码指令段会被跳过

> **陷阱警告**：这是一个**不安全的默认值（unsafe default）**。当用户创建自定义样式文件时，如果忘记在 frontmatter 中设置 `keep-coding-instructions: true`，该值将解析为 `undefined`，进而导致 Claude 的默认编码指令被静默替换。用户可能只是想让 Claude "用中文回复"或"代码注释更详细"，却意外地移除了 Claude 关于代码编写方式的所有内置指导。在安全工程中，默认值应该选择最安全的选项（fail-safe default）——即默认保留编码指令。Claude Code 团队选择当前行为的原因可能是为了给自定义样式提供最大的灵活度（完全接管系统提示词），但代价是增加了用户误操作的风险。
> 
> 💡 **通俗理解**：这就像手机的"恢复出厂设置"按钮放在了"更换壁纸"菜单里——用户只想换个壁纸，却可能不小心抹掉了所有设置。

## 3. 样式合并与优先级

### 3.1 五层合并

`getAllOutputStyles` 函数（第 137-175 行）按优先级合并所有来源的样式：

```typescript
export const getAllOutputStyles = memoize(async function getAllOutputStyles(
  cwd: string,
): Promise<{ [styleName: string]: OutputStyleConfig | null }> {
  const customStyles = await getOutputStyleDirStyles(cwd)
  const pluginStyles = await loadPluginOutputStyles()

  const allStyles = { ...OUTPUT_STYLE_CONFIG }  // 1. 内置样式

  const managedStyles = customStyles.filter(s => s.source === 'policySettings')
  const userStyles = customStyles.filter(s => s.source === 'userSettings')
  const projectStyles = customStyles.filter(s => s.source === 'projectSettings')

  // 优先级从低到高
  const styleGroups = [pluginStyles, userStyles, projectStyles, managedStyles]

  for (const styles of styleGroups) {
    for (const style of styles) {
      allStyles[style.name] = { ... }  // 高优先级覆盖低优先级
    }
  }

  return allStyles
})
```

优先级层级：
1. **内置样式**（最低）：default, Explanatory, Learning
2. **插件样式**：第三方插件提供
3. **用户样式**：`~/.claude/output-styles/`
4. **项目样式**：`.claude/output-styles/`
5. **企业策略样式**（最高）：管理员强制的样式

同名样式，高优先级覆盖低优先级——企业管理员可以强制替换任何用户自定义的样式。

> **📚 课程关联**：这种分层覆盖机制是《操作系统》课程中**配置文件搜索路径**的经典模式——与 Linux 的配置查找顺序（`/etc/` → `~/.config/` → `./.`）、CSS 的层叠优先级（user-agent → user → author → !important）、以及 Git 的配置层级（system → global → local）同构。在《软件工程》中，这属于**策略模式（Strategy Pattern）**与**责任链模式（Chain of Responsibility）**的组合应用。

### 3.2 插件强制样式

```typescript
export async function getOutputStyleConfig(): Promise<OutputStyleConfig | null> {
  // 检查是否有插件强制的样式
  const forcedStyles = Object.values(allStyles).filter(
    (style): style is OutputStyleConfig =>
      style !== null &&
      style.source === 'plugin' &&
      style.forceForPlugin === true,
  )

  const firstForcedStyle = forcedStyles[0]
  if (firstForcedStyle) {
    if (forcedStyles.length > 1) {
      logForDebugging(
        `Multiple plugins have forced output styles: ` +
        `${forcedStyles.map(s => s.name).join(', ')}. Using: ${firstForcedStyle.name}`,
        { level: 'warn' },
      )
    }
    return firstForcedStyle
  }

  // 否则使用用户配置的样式
  const outputStyle = settings?.outputStyle || DEFAULT_OUTPUT_STYLE_NAME
  return allStyles[outputStyle] ?? null
}
```

如果多个插件都设置了 `forceForPlugin: true`，系统只使用第一个并发出警告——这是一个潜在的冲突场景。

## 4. /output-style 命令（已废弃）

`commands/output-style/output-style.tsx` 仅有 7 行实际代码：

```typescript
export async function call(onDone: LocalJSXCommandOnDone): Promise<undefined> {
  onDone(
    '/output-style has been deprecated. Use /config to change your output style, ' +
    'or set it in your settings file. Changes take effect on the next session.',
    { display: 'system' }
  )
}
```

这个命令已被废弃，引导用户使用 `/config` 命令。注意"Changes take effect on the next session"——样式切换不会立即生效，需要新会话。

## 5. 缓存与失效

### 5.1 多层缓存

系统使用了四个 memoize 缓存点：

```typescript
// 缓存点 1：loadMarkdownFilesForSubdir（原始 markdown 文件 I/O，与 Skill 系统共享）
// 缓存点 2：getOutputStyleDirStyles（目录下 frontmatter/content 解析结果）
export const getOutputStyleDirStyles = memoize(async (cwd) => { ... })

// 缓存点 3：getAllOutputStyles（五层合并结果）
export const getAllOutputStyles = memoize(async (cwd) => { ... })

// 缓存点 4：loadPluginOutputStyles（插件样式，全局一份）
```

这四个缓存点分别由 `clearOutputStyleCaches()`（清前两项 + 插件缓存）与 `clearAllOutputStylesCache()`（清合并结果）覆盖。

### 5.2 缓存键与多项目行为

系统使用 lodash 的 `memoize` 函数，默认行为是**以第一个参数作为缓存键**。因此：

- `getOutputStyleDirStyles(cwd)` 以 `cwd` 字符串为键——不同的工作目录会产生不同的缓存条目。在 monorepo 场景下，如果用户在子项目 A 和子项目 B 之间切换，两个目录各自的样式文件会被独立缓存。
- `getAllOutputStyles(cwd)` 同样以 `cwd` 为键，确保不同项目的合并结果不会互相干扰。
- `loadPluginOutputStyles()` 无参数，全局缓存一份。

这意味着 memoize 的缓存策略是**按 cwd 隔离**的多份缓存，而非只缓存最后一次调用结果。但需要注意一个边界情况：lodash `memoize` 默认使用 `Map` 存储，缓存条目不会自动过期。如果用户在长会话中频繁切换工作目录，缓存条目会持续累积，直到 `clearOutputStyleCaches()` 被调用。在实践中这通常不是问题（会话内工作目录切换不频繁），但在长时间运行的进程中理论上存在内存泄漏的可能。

### 5.3 缓存清除

```typescript
// loadOutputStylesDir.ts
export function clearOutputStyleCaches(): void {
  getOutputStyleDirStyles.cache?.clear?.()
  loadMarkdownFilesForSubdir.cache?.clear?.()
  clearPluginOutputStyleCache()
}

// outputStyles.ts
export function clearAllOutputStylesCache(): void {
  getAllOutputStyles.cache?.clear?.()
}
```

两个函数覆盖不同缓存点，调用时需要成对使用才能完整重置：

| 函数 | 清理范围 | 未清理的缓存 |
|------|---------|-------------|
| `clearOutputStyleCaches()`（`loadOutputStylesDir.ts:94`） | `getOutputStyleDirStyles` + `loadMarkdownFilesForSubdir` + plugin cache（共 3 项） | 不清 `getAllOutputStyles`（合并结果） |
| `clearAllOutputStylesCache()`（`outputStyles.ts:177`） | 仅清 `getAllOutputStyles`（1 项） | 不清原始 markdown / frontmatter / plugin 缓存 |

**陷阱**：只调其中一个会导致缓存不一致——例如只调 `clearOutputStyleCaches()` 而不调 `clearAllOutputStylesCache()`，则合并结果仍引用旧的 per-cwd 样式集，用户看到的是"刷新了但没生效"。反之亦然。缓存清除发生在：设置变更、插件加载/卸载、会话重启——调用方通常需要两个函数都调一遍。

## 6. 与系统提示词的集成

输出样式的 `prompt` 字段最终会被注入到 Claude 的系统提示词中。这发生在 `src/constants/prompts.ts` 中（虽然具体的注入代码不在 outputStyles 模块内）。

注入方式取决于 `keepCodingInstructions`。源码中的精确逻辑（`prompts.ts` 第 564-567 行）：

```typescript
// 仅当 keepCodingInstructions 严格等于 true 时，才保留编码指令
outputStyleConfig === null ||
outputStyleConfig.keepCodingInstructions === true
  ? getSimpleDoingTasksSection()   // 保留默认编码指令
  : null,                          // 跳过编码指令
```

转化为用户可理解的规则：

```
如果 keepCodingInstructions === true:
  系统提示词 = [介绍段] + [默认编码指令] + ... + [样式 prompt]

如果 keepCodingInstructions === false 或 undefined:
  系统提示词 = [介绍段] + ... + [样式 prompt]  (编码指令段被跳过)
```

注意，样式 prompt 本身是通过 `getOutputStyleSection()` 注入到动态区域的，格式为 `# Output Style: {name}\n{prompt}`。其余系统提示词段落（工具使用说明、语气与风格、输出效率等）在所有模式下都保留，被跳过的仅是 `getSimpleDoingTasksSection()` 这一个段落——它包含了 Claude 关于如何执行编码任务的核心指导。

这意味着一个 `keepCodingInstructions: false`（或未设置 `keep-coding-instructions`）的自定义样式会移除 Claude 的编码任务指导——不仅仅是输出格式变化，Claude 处理编码任务的方式也会改变。这既是强大的灵活性（允许样式完全重新定义 Claude 的行为），也是一个需要用户明确理解的风险点（参见 2.3 节的陷阱警告）。

## 7. Learning 模式的教学设计分析

Learning 模式的设计值得深入分析，因为它代表了一种新颖的人机协作范式：

### 7.1 暂停点选择策略

```
ask the human to contribute 2-10 line code pieces 
when generating 20+ lines involving:
- Design decisions (error handling, data structures)
- Business logic with multiple valid approaches  
- Key algorithms or interface definitions
```

暂停点不是随机的，而是**有教育意义的决策点**——错误处理策略、数据结构选择、算法设计。这些恰好是编程中最需要思考的部分。

### 7.2 TODO(human) 协议

```
- You must first add a TODO(human) section into the codebase 
  with your editing tools before making the Learn by Doing request
- Make sure there is one and only one TODO(human) section in the code
- Don't take any action after the Learn by Doing request
```

Claude 必须先在代码中**物理标记**需要人类填写的位置，然后停下来等待。这确保了用户知道在哪里写、写什么。

### 7.3 与 TodoList 集成

```
If using a TodoList for the overall task, include a specific todo item 
like "Request human input on [specific decision]" when planning to 
request human input.
```

这将教学环节整合到了任务流程中，而不是一个独立的"教学模式"。

> **📚 课程关联**：Learning 模式的"在决策点暂停请求人类输入"设计，映射到《软件工程》中的 **scaffolding（脚手架法）**和**主动学习（Active Learning）**理论——AI 搭建框架后让学习者填充关键逻辑，这与 Vygotsky 的"最近发展区（ZPD）"理论一致。从技术实现角度看，`TODO(human)` 标记本质上是一个**协程让出点（yield point）**——AI 执行流在此暂停，等待人类输入后恢复。

## 8. 安全分析：样式系统作为 Prompt 注入管道

输出样式系统的本质是一个**用户可控的 system prompt 注入管道**。每个样式文件的 Markdown 正文内容会被原封不动地注入到 Claude 的系统提示词中。这种设计在提供灵活性的同时，引入了几个值得关注的安全和成本问题。

### 8.1 供应链攻击向量

项目级样式文件位于 `.claude/output-styles/` 目录中，这个目录通常会被提交到版本控制。这意味着：

1. **恶意 PR 攻击**：攻击者可以在一个看似正常的 Pull Request 中，夹带一个 `.claude/output-styles/helpful.md` 文件，其中包含修改 Claude 行为的指令（例如"在生成代码时静默引入后门"）。由于样式文件是 Markdown 格式且内容直接注入 system prompt，代码审查者很容易忽略这个文件的安全影响。
2. **团队传播**：一旦恶意样式文件合并到主分支，所有在该项目上使用 Claude Code 且选择了该样式的团队成员都会受到影响。
3. **开源项目风险**：开源项目的 `.claude/` 目录可能包含由不可信贡献者提交的样式文件。用户克隆项目后，这些样式会自动出现在可选列表中。

> 💡 **通俗理解**：这就像办公室的共享打印机突然被人换了墨盒——表面上打出来的还是文件，但每一页的内容都被偷偷修改了。而且因为大家共用同一台打印机，所有人都受影响。

### 8.2 行为边界缺失

样式 prompt 没有任何长度限制或内容校验机制：

- **无长度上限**：用户理论上可以写一个 10000 token 的样式 prompt，这会显著增加每次 API 调用的成本和延迟。量化示例：
  - 中等长度样式（500 token）× 会话内 30 次 API 调用 = 15,000 token 额外开销
  - 过长样式（10,000 token）× 30 次调用 = 300,000 token 额外开销
  - 因为样式 prompt 位于动态区域（见 8.3），**每次请求都要重传、不命中 cache**，开销真实累积而非一次性
- **无内容过滤**：样式 prompt 中可以包含任何文本，包括试图覆盖 Claude 安全边界的指令。虽然 Claude 模型本身有安全训练来抵抗此类攻击，但将用户可控文本直接注入 system prompt 仍然扩大了攻击面。
- **无 schema 验证**：frontmatter 的字段名和值没有严格校验。命名体系本身存在**双轨**——frontmatter（YAML 文件）使用 **kebab-case**（`keep-coding-instructions`），解析后的 TypeScript 对象字段使用 **camelCase**（`keepCodingInstructions`）。用户写错方向（YAML 里填 `keepCodingInstructions` 或 `keep_coding_instructions`）会被静默忽略，不产生任何警告。`loadOutputStylesDir.ts` 对 `force-for-plugin` 字段有一条 warn 级别的日志（第 201-206 行），但对其他未知字段一律沉默。

### 8.3 与 Prompt Caching 的交互

样式 prompt 被放在系统提示词的动态区域（通过 `systemPromptSection('output_style', ...)` 注册），位于 `SYSTEM_PROMPT_DYNAMIC_BOUNDARY` 标记之后。这意味着：

- 样式 prompt 变化时不会破坏静态区域的 prompt cache——这是一个正面的设计决策。
- 但样式 prompt 本身每次请求都需要发送，不会被缓存命中——如果样式 prompt 很长，这个 token 开销在每次调用中都会重复产生。

### 8.4 缓解建议

对于使用 Claude Code 的团队，建议采取以下实践：

- 在代码审查中将 `.claude/output-styles/` 目录的变更视为**安全敏感变更**，等同于 CI 配置或部署脚本的修改
- 自定义样式文件始终显式设置 `keep-coding-instructions: true`，除非有明确的理由需要替换默认编码指令
- 在 `.gitignore` 中考虑是否需要排除样式目录，或建立团队审批流程
- 对于企业部署，利用 `policySettings`（企业策略层）来强制执行安全基线样式，防止低优先级层的样式做出危险的行为修改

## 批判性分析

### 优点

1. **极简核心**：加载模块 98 行，样式定义 216 行（`wc -l` 统计）——通过将复杂性下沉到提示词层面，实现了代码层面的简洁。但需要注意，这种简洁的代价是缺少运行时校验（如 prompt 长度限制、内容安全检查），安全边界依赖于用户的自觉
2. **复用 Skill 基础设施**：使用 `loadMarkdownFilesForSubdir` 和 frontmatter 解析器，与 Skill 系统共享基础设施，减少了重复代码
3. **分层覆盖机制**：5 层优先级确保了企业合规性（管理员可以强制样式）和个人灵活性（用户可以自定义）的平衡
4. **Learning 模式的设计巧思**：将 AI 从"代劳者"转变为"教练"的理念在教育技术领域已有先例（如 Khanmigo 的苏格拉底式对话），但 Claude Code 的 TODO(human) 协议将其具体化为代码层面的交互接口，这在 AI 编程工具中较为新颖

### 不足

**架构级问题**：

1. **`keepCodingInstructions` 的不安全默认值**：当自定义样式未设置此字段时，`undefined` 的行为等同于 `false`——静默替换编码指令。这违反了 fail-safe default 原则，用户可能在不知情的情况下创建了一个"不会按预期写代码的 Claude"（详见 2.3 节）
2. **插件样式冲突处理粗糙**：多个 `forceForPlugin: true` 时取第一个。`Object.values()` 的遍历顺序在 ES2020+ 规范中是确定的（按整数键递增 → 字符串键插入顺序 → Symbol 键插入顺序），所以"取第一个"本身是可重现的——但**插件被合并进 `allStyles` 的顺序**由 `loadPluginOutputStyles()` 内部返回顺序决定，对插件作者而言这个顺序不直观、难以预测。结果是"可重现但不可解释"的冲突解决：理论上不是随机，实际上对用户等价于随机。应该有更明确的冲突策略——如插件声明 `priority` 字段或让用户选择。
3. **样式 prompt 无安全边界**：无长度限制、无内容校验、项目级样式文件构成供应链攻击向量（详见第 8 节安全分析）

**产品体验问题**：

4. **样式切换不即时生效**：`/output-style` 命令已废弃，且样式切换需要新会话——对于想要临时切换风格的用户来说，这是不便的。技术原因可能是系统提示词在会话开始时生成后，中途修改会导致 prompt cache 失效
5. **缺乏样式预览**：用户在选择样式之前无法预览效果，只能通过 description 猜测
6. **Learning 模式的实用性问题**：让用户在终端中手动编辑代码的 TODO(human) 位置，然后再回到 Claude Code 对话——这个工作流在实际使用中可能比较笨拙，尤其对于习惯了 IDE 内联编辑的用户

**扩展性问题**：

7. **样式总数有限**：内置只有 3 种（含 default），且未看到从社区加载或推荐样式的机制——与 Skill 的 MCP 加载相比，样式系统的扩展性较弱
8. **Skill 基础设施共享的耦合风险**：`loadMarkdownFilesForSubdir` 被样式系统和 Skill 系统共享。如果未来 Skill 需要更复杂的加载逻辑（如版本控制、签名验证），这种共享可能成为束缚

### 架构观察

输出样式系统展示了 AI 原生应用中"配置即提示词"的设计范式——样式不是通过代码逻辑来改变输出，而是通过修改系统提示词来引导 AI 行为。Cursor 的 `.cursor/rules/`、Windsurf 的 `.windsurfrules`、Aider 的 conventions 文件、以及 OpenAI 的 Custom Instructions 都采用了类似思路。

但更值得思考的问题是：**当提示词成为配置文件时，传统软件工程中关于配置管理的最佳实践是否适用？**

- **Schema 验证**：传统配置文件（如 `tsconfig.json`）有严格的 JSON Schema 校验，类型错误会在解析时报错。样式系统的 frontmatter 没有 schema 验证，错误的字段名被静默忽略——这在 Claude Code 当前的 3 种内置样式规模下不是问题，但如果未来开放社区样式生态，缺少校验将导致大量"为什么我的样式不生效"的问题。
- **版本控制与回滚**：样式文件可以通过 Git 版本控制，这一点做到了。但缺少样式版本号或兼容性标记——如果未来 Claude Code 升级后某些 prompt 模式不再有效，用户无法知道自己的样式是否需要更新。
- **灰度发布与 A/B 测试**：传统配置系统支持按百分比灰度发布，样式系统目前没有这个能力。对于企业用户来说，在全团队推广新样式之前进行小范围验证是合理的需求。

这些"老问题在新范式中的映射"——即 prompt 配置是否需要传统配置管理的工程化治理——是 AI 工具平台化过程中必须面对的架构决策。Claude Code 目前选择了"简洁优先"，在当前阶段是合理的，但随着样式生态的扩展，这些工程化能力可能成为必要。
