# 更新配置技能

通过更新 settings.json 文件修改 Claude Code 配置。

## 何时需要 Hooks（不是记忆）

如果用户希望某事响应事件自动发生，他们需要 settings.json 中配置的 **hook**。记忆/偏好无法触发自动化操作。

**以下需要 hooks：**
- "压缩前询问我要保留什么" → PreCompact hook
- "写文件后运行 prettier" → PostToolUse hook，匹配器为 Write|Edit
- "当我运行 bash 命令时记录它们" → PreToolUse hook，匹配器为 Bash
- "代码更改后始终运行测试" → PostToolUse hook

**Hook 事件：** PreToolUse、PostToolUse、PreCompact、PostCompact、Stop、Notification、SessionStart

## 关键：先读后写

**在进行更改之前始终读取现有的设置文件。** 将新设置与现有设置合并——永远不要替换整个文件。

## 关键：对模糊不清的情况使用 AskUserQuestion

当用户的请求模糊不清时，使用 AskUserQuestion 澄清：
- 修改哪个设置文件（用户/项目/本地）
- 是添加到现有数组还是替换它们
- 存在多个选项时的具体值

## 决策：/config 命令 vs 直接编辑

**对于以下简单设置，建议使用 `/config` 斜杠命令：**
- `theme`、`editorMode`、`verbose`、`model`
- `language`、`alwaysThinkingEnabled`
- `permissions.defaultMode`

**对于以下设置，直接编辑 settings.json：**
- Hooks（PreToolUse、PostToolUse 等）
- 复杂的权限规则（allow/deny 数组）
- 环境变量
- MCP 服务器配置
- 插件配置

## 工作流程

1. **澄清意图** — 如果请求模糊不清则询问
2. **读取现有文件** — 对目标设置文件使用 Read 工具
3. **仔细合并** — 保留现有设置，特别是数组
4. **编辑文件** — 使用 Edit 工具（如果文件不存在，请用户先创建）
5. **确认** — 告诉用户更改了什么

## 合并数组（重要！）

添加到权限数组或 hook 数组时，**与现有数组合并**，不要替换：

**错误**（替换现有权限）：
```json
{ "permissions": { "allow": ["Bash(npm *)"] } }
```

**正确**（保留现有 + 添加新项）：
```json
{
  "permissions": {
    "allow": [
      "Bash(git *)",      // 现有
      "Edit(.claude)",    // 现有
      "Bash(npm *)"       // 新增
    ]
  }
}
```

## 设置文件位置

根据范围选择合适的文件：

| 文件 | 范围 | Git | 用途 |
|------|-------|-----|---------|
| `~/.claude/settings.json` | 全局 | N/A | 所有项目的个人偏好 |
| `.claude/settings.json` | 项目 | 提交 | 团队级 hooks、权限、插件 |
| `.claude/settings.local.json` | 项目 | Gitignore | 此项目的个人覆盖 |

设置加载顺序：用户 → 项目 → 本地（后者覆盖前者）。

## 设置 Schema 参考

### 权限
```json
{
  "permissions": {
    "allow": ["Bash(npm *)", "Edit(.claude)", "Read"],
    "deny": ["Bash(rm -rf *)"],
    "ask": ["Write(/etc/*)"],
    "defaultMode": "default" | "plan" | "acceptEdits" | "dontAsk",
    "additionalDirectories": ["/extra/dir"]
  }
}
```

**权限规则语法：**
- 精确匹配：`"Bash(npm run test)"`
- 前缀通配符：`"Bash(git *)"` — 匹配 `git`、`git status`、`git commit` 等
- 仅工具：`"Read"` — 允许所有 Read 操作

### 环境变量
```json
{
  "env": {
    "DEBUG": "true",
    "MY_API_KEY": "value"
  }
}
```

### 模型和代理
```json
{
  "model": "sonnet",  // 或 "fable"、"opus"、"haiku"、完整模型 ID
  "agent": "agent-name",
  "alwaysThinkingEnabled": true
}
```

### 署名（提交和 PR）
```json
{
  "attribution": {
    "commit": "自定义提交尾注文本",
    "pr": "自定义 PR 描述文本"
  }
}
```
将 `commit` 或 `pr` 设置为空字符串 `""` 以隐藏署名。

### MCP 服务器管理
```json
{
  "enableAllProjectMcpServers": true,
  "enabledMcpjsonServers": ["server1", "server2"],
  "disabledMcpjsonServers": ["blocked-server"]
}
```

### 插件
```json
{
  "enabledPlugins": {
    "formatter@anthropic-tools": true
  }
}
```
插件语法：`plugin-name@source`，其中 source 为 `claude-code-marketplace`、`claude-plugins-official` 或 `builtin`。

### 其他设置
- `language`：首选回复语言（例如 "japanese"）
- `cleanupPeriodDays`：自动清理前保留对话记录的天数（默认：30；最少 1）
- `respectGitignore`：是否遵循 .gitignore（默认：true）
- `spinnerTipsEnabled`：在 spinner 中显示提示
- `spinnerVerbs`：自定义 spinner 动词（`{ "mode": "append" | "replace", "verbs": [...] }`）
- `spinnerTipsOverride`：覆盖 spinner 提示（`{ "excludeDefault": true, "tips": ["自定义提示"] }`）
- `syntaxHighlightingDisabled`：禁用差异高亮

## Hooks 配置

Hooks 在 Claude Code 生命周期中的特定时刻运行命令。

### Hook 结构
```json
{
  "hooks": {
    "EVENT_NAME": [
      {
        "matcher": "ToolName|OtherTool",
        "hooks": [
          {
            "type": "command",
            "command": "your-command-here",
            "timeout": 60,
            "statusMessage": "Running..."
          }
        ]
      }
    ]
  }
}
```

### Hook 事件

| 事件 | 匹配器 | 用途 |
|-------|---------|---------|
| PermissionRequest | 工具名称 | 在权限提示前运行 |
| PreToolUse | 工具名称 | 在工具执行前运行，可阻止 |
| PostToolUse | 工具名称 | 在工具成功执行后运行 |
| PostToolUseFailure | 工具名称 | 在工具执行失败后运行 |
| Notification | 通知类型 | 在通知时运行 |
| Stop | - | Claude 停止时运行（包括清除、恢复、压缩） |
| PreCompact | "manual"/"auto" | 压缩前 |
| PostCompact | "manual"/"auto" | 压缩后（接收摘要） |
| UserPromptSubmit | - | 用户提交时 |
| SessionStart | - | 会话开始时 |

**常见工具匹配器：** `Bash`、`Write`、`Edit`、`Read`、`Glob`、`Grep`

### Hook 类型

**1. 命令 Hook** — 运行 shell 命令：
```json
{ "type": "command", "command": "prettier --write $FILE", "timeout": 30 }
```

**2. 提示 Hook** — 使用 LLM 评估条件：
```json
{ "type": "prompt", "prompt": "这个安全吗？$ARGUMENTS" }
```
仅适用于工具事件：PreToolUse、PostToolUse、PermissionRequest。

**3. 代理 Hook** — 使用工具运行代理：
```json
{ "type": "agent", "prompt": "验证测试通过：$ARGUMENTS" }
```
仅适用于工具事件：PreToolUse、PostToolUse、PermissionRequest。

### Hook 输入（stdin JSON）
```json
{
  "session_id": "abc123",
  "tool_name": "Write",
  "tool_input": { "file_path": "/path/to/file.txt", "content": "..." },
  "tool_response": { "success": true }  // 仅 PostToolUse
}
```

### Hook JSON 输出

Hooks 可以返回 JSON 来控制行为：

```json
{
  "systemMessage": "显示给用户的警告信息",
  "continue": false,
  "stopReason": "阻止时显示的消息",
  "suppressOutput": false,
  "decision": "block",
  "reason": "决定的解释",
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "注入回模型的上下文"
  }
}
```

**字段：**
- `systemMessage` — 向用户显示消息（所有 hooks）
- `continue` — 设置为 `false` 以阻止/停止（默认：true）
- `stopReason` — `continue` 为 false 时显示的消息
- `suppressOutput` — 从转录中隐藏 stdout（默认：false）
- `decision` — "block" 用于 PostToolUse/Stop/UserPromptSubmit hooks（PreToolUse 已弃用，改用 hookSpecificOutput.permissionDecision）
- `reason` — 决定的解释
- `hookSpecificOutput` — 事件特定输出（必须包含 `hookEventName`）：
  - `additionalContext` — 注入到模型上下文的文本
  - `permissionDecision` — "allow"、"deny" 或 "ask"（仅 PreToolUse）
  - `permissionDecisionReason` — 权限决定的原因（仅 PreToolUse）
  - `updatedInput` — 修改后的工具输入（仅 PreToolUse）

### 常见模式

**写后自动格式化：**
```json
{
  "hooks": {
    "PostToolUse": [{
      "matcher": "Write|Edit",
      "hooks": [{
        "type": "command",
        "command": "jq -r '.tool_response.filePath // .tool_input.file_path' | { read -r f; prettier --write \"$f\"; } 2>/dev/null || true"
      }]
    }]
  }
}
```

**记录所有 bash 命令：**
```json
{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [{
        "type": "command",
        "command": "jq -r '.tool_input.command' >> ~/.claude/bash-log.txt"
      }]
    }]
  }
}
```

**向用户显示消息的 Stop hook：**

命令必须输出带有 `systemMessage` 字段的 JSON：
```bash
# 输出：{"systemMessage": "会话完成！"}
echo '{"systemMessage": "会话完成！"}'
```

**代码更改后运行测试：**
```json
{
  "hooks": {
    "PostToolUse": [{
      "matcher": "Write|Edit",
      "hooks": [{
        "type": "command",
        "command": "jq -r '.tool_input.file_path // .tool_response.filePath' | grep -E '\\.(ts|js)$' && npm test || true"
      }]
    }]
  }
}
```

## 构建 Hook（带验证）

给定一个事件、匹配器、目标文件和期望的行为，遵循此流程。每一步捕获不同类型的失败——一个静默执行无任何效果的 hook 比没有 hook 更糟。

1. **去重检查。** 读取目标文件。如果相同的事件+匹配器 hook 已存在，显示现有命令并询问：保留、替换还是并排添加。

2. **为此项目构建命令——不要假设。** Hook 在 stdin 上接收 JSON。构建一个可以：
   - 安全提取任何所需负载的命令——使用 `jq -r` 将其导入带引号的变量或 `{ read -r f; ... "$f"; }`，而非未加引号的 `| xargs`（会在空格处分隔）
   - 以此项目运行它的方式调用底层工具（npx/bunx/yarn/pnpm？Makefile 目标？全局安装？）
   - 跳过该工具不处理的输入（格式化器通常有 `--ignore-unknown`；如果没有，按扩展名保护）
   - 保持原始状态——暂不添加 `|| true`，不禁用 stderr。你将在 pipe 测试通过后包装它。

3. **管道测试原始命令。** 合成 hook 将接收的 stdin 负载，直接通过管道传入：
   - `Pre|PostToolUse` 对 `Write|Edit`：`echo '{"tool_name":"Edit","tool_input":{"file_path":"<a real file from this repo>"}}' | <cmd>`
   - `Pre|PostToolUse` 对 `Bash`：`echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | <cmd>`
   - `Stop`/`UserPromptSubmit`/`SessionStart`：大多数命令不读取 stdin，所以 `echo '{}' | <cmd>` 就可以

检查退出代码和副作用（文件是否确实被格式化，测试是否确实运行了）。如果失败，你会得到一个真实的错误——修复它（错误的包管理器？工具未安装？jq 路径错误？）并重新测试。一旦工作正常，用 `2>/dev/null || true` 包装（除非用户想要阻塞检查）。

4. **写入 JSON。** 合并到目标文件（模式形状见上方的"Hook 结构"部分）。如果这是首次创建 `.claude/settings.local.json`，将其添加到 .gitignore——Write 工具不会自动执行 gitignore。

5. **一次验证语法和模式：**

`jq -e '.hooks.<event>[] | select(.matcher == "<matcher>") | .hooks[] | select(.type == "command") | .command' <target-file>`

退出 0 并打印你的命令 = 正确。退出 4 = 匹配器不匹配。退出 5 = JSON 格式错误或嵌套错误。一个损坏的 settings.json 会静默禁用该文件中的所有设置——也要修复任何预先存在的格式错误。

6. **证明 hook 会在实际中触发**——仅对 `Pre|PostToolUse` 且匹配器可在轮次中触发的情况（`Write|Edit` 通过 Edit，`Bash` 通过 Bash）。`Stop`/`UserPromptSubmit`/`SessionStart` 在这个轮次之外触发——跳到第 7 步。

对 `PostToolUse`/`Write|Edit` 上的**格式化器**：通过 Edit 引入一个可检测的违规（连续两个空行、糟糕的缩进、遗漏的分号——该格式化器可以修复；不是尾随空格，Edit 在写入前会去除），重新读取，确认 hook **修复**了它。对于**其他情况**：在 settings.json 的命令前临时加上 `echo "$(date) hook fired" >> /tmp/claude-hook-check.txt; `，触发匹配的工具（`Write|Edit` 用 Edit，`Bash` 用无害的 `true`），读取哨兵文件。

**始终清理**——复原违规、去除哨兵前缀——无论证明通过还是失败。

**如果证明失败但管道测试通过且 `jq -e` 通过**：设置监控器未监控 `.claude/`——它只监控此会话开始时已有设置文件的目录。Hook 已正确写入。告诉用户打开 `/hooks` 一次（重新加载配置）或重新启动——你不能自己做；`/hooks` 是用户 UI 菜单，打开它会结束此轮次。

7. **交接。** 告诉用户 hook 已生效（或需要 `/hooks`/重新启动，根据监控器的注意事项）。指出 `/hooks` 以便以后再审查、编辑或禁用它。UI 仅在 hook 出错或运行缓慢时显示"Ran N hooks"——静默成功默认是不可见的。

## 示例工作流

### 添加 Hook

用户："Claude 写文件后格式化我的代码"

1. **澄清**：哪个格式化器？（prettier、gofmt 等）
2. **读取**：`.claude/settings.json`（如果缺失则创建）
3. **合并**：添加到现有 hooks，不要替换
4. **结果**：
```json
{
  "hooks": {
    "PostToolUse": [{
      "matcher": "Write|Edit",
      "hooks": [{
        "type": "command",
        "command": "jq -r '.tool_response.filePath // .tool_input.file_path' | { read -r f; prettier --write \"$f\"; } 2>/dev/null || true"
      }]
    }]
  }
}
```

### 添加权限

用户："允许 npm 命令而不提示"

1. **读取**：现有权限
2. **合并**：将 `Bash(npm *)` 添加到 allow 数组
3. **结果**：与现有 allow 组合

### 环境变量

用户："设置 DEBUG=true"

1. **决定**：用户设置（全局）还是项目设置？
2. **读取**：目标文件
3. **合并**：添加到 env 对象
```json
{ "env": { "DEBUG": "true" } }
```

## 常见错误需要避免

1. **替换而不是合并** — 始终保留现有设置
2. **错误文件** — 如果范围不明确则询问用户
3. **无效的 JSON** — 更改后验证语法
4. **忘记先读取** — 始终在写入前读取

## Hook 故障排除

如果 hook 没有运行：
1. **检查设置文件** — 读取 ~/.claude/settings.json 或 .claude/settings.json
2. **验证 JSON 语法** — 无效的 JSON 会静默失败
3. **检查匹配器** — 它与工具名称匹配吗？（例如，"Bash"、"Write"、"Edit"）
4. **检查 hook 类型** — 是"command"、"prompt"还是"agent"？
5. **测试命令** — 手动运行 hook 命令看它是否有效
6. **使用 --debug** — 运行 `claude --debug` 以查看 hook 执行日志

## 完整设置 JSON Schema

重要：除非明确指示，否则不要更新 env。

## 完整设置 JSON Schema
