Skip to content

释题:有章可循。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 应用”的关键跨越。

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