释题:有章可循。Claude Code 的“规则”不止一种——指令规则告诉 Claude 该怎么做,权限规则告诉 Claude 能做什么。前者是员工手册,后者是门禁系统,两套规则协同运作,才构成完整的行为约束体系。
你好,我是黄佳。 上一讲我们学习了 Headless 模式,让 Claude Code 在无人值守的情况下嵌入 CI/CD 流水线自动运行。Headless 解决了没有人盯着的问题,但也让一个追问变得更加尖锐, **没人盯着的时候,谁来定规矩?** 这就引出了今天的主题—— **规则系统** 。 我发现一个有趣的现象:很多同学把两种“规则“搞混了。有人问“rules 目录里能不能禁止 Claude 执行 `rm` 命令?”答案是不能,那是权限规则的事;也有人问“settings.json 里能不能告诉 Claude 用 TypeScript 写代码?”答案也是不能,那是指令规则的事。 **这两种规则解决的是完全不同的问题。** 今天我们就来把 Claude Code 中所有规则的全貌讲清楚。

两种规则,两个世界

Claude Code 中的“规则”分布在两个完全不同的层面:

我用一个比喻来帮你区分它们,指令规则像公司的员工手册,写着“代码提交前必须跑测试”,员工可以遵守,也可以偷懒。权限规则像门禁系统——没有卡就进不了机房,系统不给你选择。

指令规则是 Claude 的认知约束,权限规则是客户端的行为约束。

指令规则——.claude/rules/ 完全指南

也许看完前面说的你还是有点懵,别急,咱们继续探索规则背后的工作机制。

它到底是什么?

.claude/rules/ 目录下的每个 .md 文件,本质上就是一段会被注入 System Prompt 的文本。它和 CLAUDE.md 没有本质区别——都是 Claude 在每轮 API 调用中“看到”的指令。唯一的结构化优势是,它可以按主题拆分成多个文件,还支持条件加载。

.claude/
└── rules/
    ├── typescript.md       # TypeScript 编码规范
    ├── testing.md          # 测试规范(有 paths 条件)
    ├── api-design.md       # API 设计规范(有 paths 条件)
    └── security.md         # 安全规范(全局生效)

两种加载模式

这是 rules 最重要的设计细节,也是最多人搞错的地方。

模式一:全局加载(无 paths 字段)

# security.md —— 没有 YAML 头部,或有头部但不含 paths

## 安全规范
- 不在代码中硬编码密码或 API Key
- 所有用户输入必须做 sanitize
- SQL 查询使用参数化,不拼接字符串

这种 rule 在会话启动时就加载进上下文,行为和写在 CLAUDE.md 里完全一样。拆出来的唯一好处是文件组织更清晰。

模式二:条件加载(有 paths 字段)

---
paths:
  - "src/**/*.test.ts"
  - "tests/**/*.ts"
---

# 测试规范

## 命名
- 单元测试: `*.test.ts`
- 集成测试: `*.integration.test.ts`

## 结构
使用 Arrange-Act-Assert 模式

这种 rule 在会话启动时 不加载 。只有当 Claude 读取或编辑匹配 paths 模式的文件时,才会被注入上下文。

这个设计很精妙——如果你的测试规范有 50 行,而你这次只是在改一个 CSS 样式,那这 50 行规范就不会浪费上下文空间。

但有一个关键细节, 一旦加载,就不会卸载。 paths 控制的是何时加载,不是何时生效。如果你在会话中先编辑了一个测试文件,testing.md 被加载了,然后你去改 CSS,testing.md 仍然在上下文里。

会话开始
  → 加载全局 rules(无 paths 的)
  → testing.md 未加载 ✗

用户:帮我改一下 src/utils/format.ts
  → Claude 读取 format.ts
  → paths 不匹配,testing.md 仍未加载 ✗

用户:帮我给这个函数写个测试
  → Claude 创建 src/utils/format.test.ts
  → paths 匹配!testing.md 加载 ✓

用户:再帮我改一下 CSS
  → Claude 读取 styles.css
  → testing.md 仍在上下文中 ✓(不会卸载)

什么时候该从 CLAUDE.md 拆到 rules?

判断标准很简单,我们可以根据长度决策。

CLAUDE.md 的总长度如何?
│
├── < 200 行 → 不用拆,CLAUDE.md 一把梭
│               简单就是好,不要为了组织而组织
│
├── 200-500 行 → 考虑拆
│   │
│   └── 有没有"只和特定文件类型相关"的内容?
│       ├── 有 → 拆出来,加 paths
│       │       (如测试规范、前端规范、API 规范)
│       └── 没有 → 拆出来,不加 paths
│                 (纯粹为了文件组织清晰)
│
└── > 500 行 → 必须拆
                CLAUDE.md 太长会稀释重要信息的权重
                把领域规范拆到 rules,CLAUDE.md 只留核心约定

实战:一个全栈项目的 rules 拆分

假设你有一个 React + Express + PostgreSQL 的全栈项目,CLAUDE.md 膨胀到了 600 行(示例参考)。我们如何拆分呢?

拆分后的 CLAUDE.md(精简到 80 行以内)

# 项目概述
全栈 TypeScript 项目。前端 React 18 + Tailwind,后端 Express + Prisma + PostgreSQL。

# 命令
- `pnpm dev` — 启动前后端开发服务器
- `pnpm test` — 运行全部测试
- `pnpm lint` — ESLint + Prettier 检查
- `pnpm db:migrate` — 执行数据库迁移

# 核心约定
- 包管理器用 pnpm,不用 npm 或 yarn
- commit message 用 conventional commits 格式
- 所有 API 返回 { success: boolean, data?: T, error?: string }
- 环境变量通过 .env 管理,不硬编码

# 详细规范
领域规范见 .claude/rules/ 目录,按文件类型自动加载。

拆分出的 rules 文件

.claude/rules/frontend.md

---
paths:
  - "src/components/**"
  - "src/pages/**"
  - "src/hooks/**"
---

# 前端规范

## 组件
- 函数式组件,不用 class 组件
- Props 用 interface 定义,命名 XxxProps
- 组件文件和样式文件同名同目录

## 状态管理
- 局部状态用 useState
- 跨组件状态用 Zustand
- 服务端状态用 TanStack Query

## 样式
- Tailwind 优先,复杂样式用 CSS Modules
- 响应式断点:sm(640) md(768) lg(1024) xl(1280)

.claude/rules/backend.md

---
paths:
  - "server/**"
  - "src/api/**"
  - "prisma/**"
---

# 后端规范

## 路由
- RESTful 风格,资源名用复数
- 路由文件放 server/routes/,一个资源一个文件

## 数据库
- 所有查询通过 Prisma ORM,不写原生 SQL
- 迁移文件不手动编辑
- 关联查询用 include,不用多次查询

## 错误处理
- 业务错误抛 AppError(code, message)
- 统一在 errorHandler 中间件中捕获

.claude/rules/testing.md

---
paths:
  - "**/*.test.ts"
  - "**/*.test.tsx"
  - "**/*.spec.ts"
---

# 测试规范

## 工具
- 单元测试:Vitest
- 组件测试:Testing Library
- E2E:Playwright

## 结构
- Arrange-Act-Assert 模式
- 每个 describe 对应一个函数或组件
- Mock 外部依赖,不 mock 内部模块

## 覆盖率
- 业务逻辑 > 80%
- 工具函数 > 90%
- UI 组件关注交互,不关注快照

.claude/rules/security.md(注意,没有 paths,全局生效):

# 安全规范

- 用户输入在使用前必须 validate + sanitize
- SQL 参数化(Prisma 默认做到了)
- XSS 防护:不使用 dangerouslySetInnerHTML
- CORS 只允许白名单域名
- 敏感信息(API Key、数据库密码)只放 .env
- 认证 token 用 httpOnly cookie,不存 localStorage

拆完之后,CLAUDE.md 从 600 行变成 80 行,但规范一条都没少——只是按需加载了。当 Claude 在改前端组件时,它看到的是 CLAUDE.md + frontend.md + security.md。当它在写测试时,看到的是 CLAUDE.md + testing.md + security.md。精准、高效。

权限规则——行为管控的硬约束

指令规则告诉 Claude“你应该怎么做”,权限规则告诉 Claude“你被允许做什么”。

权限规则写在 .claude/settings.json.claude/settings.local.json 中,由 Claude Code 客户端在工具调用前 硬拦截 。Claude 根本看不到这些规则——它只知道某个操作被允许了或被拒绝了。

基本结构与评估逻辑

{
  "permissions": {
    "allow": [
      "Bash(npm run *)",
      "Bash(git status)",
      "Bash(git diff *)",
      "Read"
    ],
    "deny": [
      "Bash(rm -rf *)",
      "Bash(curl *)",
      "Edit(.env)"
    ]
  }
}

评估顺序:deny → ask → allow。 第一个匹配的规则胜出,deny 总是优先。就算你在 allow 里写了 Bash(rm -rf *),如果 deny 里也有这条,deny 赢。安全规则应该有最高话语权。

权限规则覆盖的工具范围

内置工具的权限控制:

  Bash(command pattern)      → 控制 Shell 命令的执行
  Read(file pattern)         → 控制文件的读取
  Edit(file pattern)         → 控制文件的编辑
  Write(file pattern)        → 控制文件的创建

  WebFetch(domain:pattern)   → 控制网页抓取的域名范围
  WebSearch                  → 控制是否允许网络搜索

  mcp__server__tool          → 控制 MCP 工具的使用
  Skill(skill-name)          → 控制 Skill 的调用
  Task(agent-name)           → 控制子代理的调用

配置层级体系

权限配置可以在四个层级设置,高优先级覆盖低优先级。

关键规则:高层级的 deny 不可被低层级覆盖。 如果组织策略禁止了 Bash(curl *),项目配置和个人配置都无法解除这个限制。这是企业级安全管控的基石。

权限规则在扩展机制中的渗透

权限规则不仅存在于 settings.json 中,它还渗透到了 Claude Code 的各个扩展机制里。

Skills 中的 allowed-tools ,Skill 被触发时只能使用白名单中的工具:

---
name: code-reviewing
description: Review code for quality and security issues
allowed-tools:
  - Read
  - Grep
  - Glob
---

Sub-Agents 中的 tools ,子代理的工具集更加严格,甚至拿不到主对话的 CLAUDE.md。

---
name: code-reviewer
tools: Read, Grep, Glob
model: sonnet
---

Hooks 中的动态拦截 ,最灵活的权限控制,可以根据动态条件决定是否放行。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "./hooks/block-dangerous.sh"
          }
        ]
      }
    ]
  }
}

这三者共同构成了 权限纵深防御体系

工具调用请求
  ↓
第一关:settings.json 的 deny 规则
  → 命中?直接拦截,不可绕过
  ↓
第二关:Hooks 的 PreToolUse 拦截
  → 脚本返回非零?拦截,可自定义逻辑
  ↓
第三关:Skill/Agent 的 allowed-tools 限制
  → 不在白名单?拦截
  ↓
第四关:settings.json 的 allow 规则 / 用户交互审批
  → 在白名单?自动放行
  → 不在任何规则中?弹窗询问用户
  ↓
工具执行

两种规则的协同——一个完整案例

让我们用一个真实场景把两种规则串起来:你的团队有一个支付服务项目,既需要编码规范(指令规则),也需要安全管控(权限规则)。

指令规则部分

CLAUDE.md(精简版,~60 行):

# 支付服务
Node.js + TypeScript + Stripe API,处理用户支付流程。

# 命令
- `pnpm dev` — 启动开发服务器
- `pnpm test` — 运行测试
- `pnpm lint` — 代码检查

# 核心约定
- 所有金额用 cents(整数),不用浮点数
- 日志必须包含 requestId,便于追踪
- 不在日志中打印卡号、CVV 等敏感信息

.claude/rules/stripe.md

---
paths:
  - "src/payments/**"
  - "src/webhooks/**"
---

# Stripe 集成规范

## Webhook 处理
- 始终验证 webhook 签名(stripe.webhooks.constructEvent)
- 幂等处理:用 event.id 去重
- 先返回 200,再异步处理业务逻辑

## 错误处理
- StripeCardError → 返回用户友好消息
- StripeRateLimitError → 指数退避重试
- 其他 Stripe 错误 → 记录日志 + 告警

权限规则部分

.claude/settings.json(团队共享):

{
  "permissions": {
    "allow": [
      "Bash(pnpm *)",
      "Bash(git status)",
      "Bash(git diff *)",
      "Bash(git log *)",
      "Read",
      "Glob",
      "Grep"
    ],
    "deny": [
      "Bash(curl *)",
      "Bash(wget *)",
      "Read(./.env)",
      "Read(./.env.*)",
      "Edit(./.env)",
      "Edit(./.env.*)",
      "Bash(rm -rf *)",
      "Bash(* --force)",
      "Bash(stripe *)"
    ]
  }
}

注意这个 deny 列表的设计意图:

  • 禁止 curl/wget——防止 Claude 自行调用外部 API(包括 Stripe API)
  • 禁止读写 .env——保护 Stripe Secret Key 等敏感配置
  • 禁止 stripe *——防止 Claude 用 Stripe CLI 直接操作生产环境
  • 禁止 rm -rf--force——防止破坏性操作

两种规则各司其职, 指令规则告诉 Claude“处理 webhook 要验签、金额用 cents”——这是认知层面的约束,让 Claude 写出正确的代码。权限规则告诉客户端“不许读 .env、不许执行 stripe CLI”——这是行为层面的约束,从系统层面堵住安全漏洞。 即使 Claude“忘记”了指令规则中不在日志中打印卡号的要求,权限规则也能确保它无法读取 .env 中的 Stripe Key。 纵深防御,不依赖单一层面。

架构定位与最佳实践

Rules 在架构中的定位

“规则”不是架构中的一个方块,而是 渗透在每一层中的横切关注点。

  • 指令层 有指令规则(CLAUDE.md、.claude/rules/)
  • 能力层 有能力规则(Skills 的 allowed-tools、Agent 的 tools)
  • 管控层 有权限规则(settings.json、Hooks、CLI 参数)

这就像安全不是软件中的一个模块,而是贯穿所有模块的设计原则。这里我也顺便说明一下 Rules 的正确学习路径。

每一讲都在从自己的角度教 Rules 的一个切面。这一讲的价值,就是帮你把这些碎片拼成全景图。

实用模板

rules 目录的标准结构

.claude/
├── settings.json          ← 权限规则(团队共享)
├── settings.local.json    ← 个人权限覆盖(.gitignore)
└── rules/
    ├── coding.md          ← 全局编码规范(无 paths)
    ├── frontend.md        ← 前端规范(paths: src/components/**)
    ├── backend.md         ← 后端规范(paths: server/**)
    ├── testing.md         ← 测试规范(paths: **/*.test.*)
    └── security.md        ← 安全规范(无 paths,全局生效)

权限规则的安全基线 (适用于大多数团队):

{
  "permissions": {
    "allow": [
      "Read",
      "Glob",
      "Grep",
      "Bash(npm run *)",
      "Bash(pnpm *)",
      "Bash(git status)",
      "Bash(git diff *)",
      "Bash(git log *)",
      "Bash(node *)",
      "Bash(npx *)"
    ],
    "deny": [
      "Bash(rm -rf *)",
      "Bash(* --force)",
      "Bash(curl *)",
      "Bash(wget *)",
      "Read(./.env)",
      "Read(./.env.*)",
      "Edit(./.env)",
      "Edit(./.env.*)",
      "Read(~/.ssh/*)",
      "Read(~/.aws/*)"
    ]
  }
}

这个模版你要根据你的项目调整。如果用 Python 就把 pnpm 换成 pip/uv;如果需要 Claude 访问特定 API,就在 allow 中加 WebFetch(domain:your-api.com)

常见错误清单

错误 1: 在 rules/*.md 中写权限控制
  ❌ .claude/rules/security.md 里写:"禁止执行 rm -rf 命令"
  → Claude 可能遵守,也可能忘记。这是软约束。
  ✅ 在 settings.json 的 deny 中写:Bash(rm -rf *)
  → 客户端硬拦截,Claude 连尝试的机会都没有。

错误 2: 在 settings.json 中写编码规范
  ❌ settings.json 无法表达"用 TypeScript 写代码"这样的指令
  → 它只能控制 allow/deny,不能传递知识
  ✅ 在 CLAUDE.md 或 rules/*.md 中写编码规范

错误 3: rules 文件之间互相矛盾
  ❌ coding.md 说"缩进用 2 空格",frontend.md 说"缩进用 4 空格"
  → Claude 会困惑,行为不可预测
  ✅ 全局规范放 coding.md,领域规范只写领域特有的

错误 4: paths 写得太宽或太窄
  ❌ paths: ["**/*"] → 等于没写 paths,不如去掉
  ❌ paths: ["src/components/UserProfile.tsx"] → 太窄,基本不会触发
  ✅ paths: ["src/components/**"] → 合理粒度

错误 5: 以为子代理能继承 rules
  ❌ 期望子代理自动遵守 .claude/rules/ 中的规范
  → 子代理看不到主对话的任何记忆文件
  ✅ 把关键规范写进子代理的 Markdown body 或通过 Skills 注入

总结一下

这一讲我们把 Claude Code 中所有的“规则“做了一次全景梳理。

两种规则,两个世界 ,明白两个规则的本质是用对它们的前提。指令规则(CLAUDE.md、.claude/rules/)是认知约束,注入 System Prompt 让 Claude 知道该怎么做;权限规则(settings.json permissions)是行为约束,由客户端硬拦截让 Claude 做不了不该做的事。

指令规则有两种加载模式 ,无 paths 字段的全局加载,有 paths 字段的条件加载。条件加载省 token 又精准,但加载后不会卸载。CLAUDE.md 超过 200 行就考虑拆分,超过 500 行必须拆分。3-5 个 rules 文件是最佳规模。

权限规则重点掌握它的纵深防御设计, settings.json deny → Hooks PreToolUse → Skill/Agent allowed-tools → settings.json allow / 用户审批,四层拦截逐级递进。高层级 deny 不可被低层级覆盖。

Rules 不是独立组件,是横切关注点 ,它渗透在 Memory、Tools、Skills、SubAgents、Hooks 每一讲中。指令规则让 Claude 做对,权限规则让 Claude 做不了错。两者协同,才是完整的规则体系。

到这里,我们已经完整学习了 Claude Code 的所有核心扩展机制——Memory(记忆)、SubAgents(分工)、Skills(领域能力)、Commands(标准流程)、Hooks(安全防护)、MCP(外部连接)、Tools(工具基础)、Rules(行为约束)。所有这些都有一个共同前提:需要人在终端前交互。下一讲,我们将打破这个前提。

思考题

  • 你的项目目前有 .claude/rules/ 目录吗?如果没有,试着评估一下你的 CLAUDE.md 长度——是否已经到了需要拆分的程度?如果要拆,你会拆出哪几个 rule 文件?
  • 假设你的团队中有实习生使用 Claude Code,你会在 settings.json 的 deny 列表中增加哪些规则?与正式员工的配置有什么不同?
  • 指令规则是“软约束”——Claude 可能不遵守。如果你有一条 绝对不能违反 的规则(比如“永远不要删除生产数据库的表”),你会只写在 .claude/rules/ 中吗?还是会在权限规则和 Hooks 中也加上对应的硬约束?请设计一个完整的防护方案。
  • 这节课我们强调“rules 不是独立组件,是横切关注点”。在传统软件工程中,也有一个著名的横切关注点——日志(Logging)。它和 Rules 有什么相似之处?这种相似性能给你什么设计启发?

下一讲预告

到目前为止,我们学习的所有功能都是通过 配置文件和命令行 驱动 Claude Code。但有时候,你需要更精细的控制——用代码来驱动 AI。

第 21 讲,我们将学习 Agent SDK 基础 ——用 Python/TypeScript 编程驱动 Claude 执行任务。你将学会如何在代码中创建 Claude 会话、发送消息、处理响应,以及如何通过编程接口调用 Claude Code 的全部能力。Agent SDK 是从“使用 AI 工具”到“构建 AI 应用”的关键跨越。

欢迎你在留言区参与讨论,如果这节课对你有启发,别忘了分享给身边更多朋友。