先说个事儿

你大概经历过这种场面:

code review 的时候

你:"这个不能用 unwrap(),会 panic 的。"

新人:"为啥?文档里没写啊。"

你:"这...这是常识吧?"

新人(一脸无辜):"什么常识?"

修 bug 的时候

同事:"又挂了,Windows 路径的问题。"

你:"第几次了?代码里硬编码了 `/`,Windows 用的是 `\`。"

同事:"那咋办?"

你:"下次注意点..."

同事:"注意啥?"

开技术会的时候

你:"不能加这个 ProjectManager 类,我们的领域模型就四个名词。"

别人:"为啥?"

你:"因为...因为架构要简单。"

别人:"简单和加个类有啥关系?"

你看,问题出来了:

资深工程师脑子里的"为什么"、“怎么做”、“啥时候要小心”,从来没被写下来。

代码里只有"做了什么"。 README 里只有"怎么装"。 注释里零散且不成体系。

结果就是:

  • 新人只能试错学习,一遍遍踩同一个坑
  • AI 没上下文,按它自己的理解执行,结果常常不对
  • 资深工程师一走,这些经验也就跟着走了

这事儿能解决

问题的根源是:经验没显性化

解决方案是:建一个 .claude/ 目录,把这些"为什么"和"怎么做"写下来。

写下来之后:

  • AI 读了,会按规则执行
  • 新人读了,知道该怎么干
  • 经验不会因为人走而丢

.claude/ 长什么样

一个标准的目录结构:

.claude/
├─ commands/              # 命令规格:"如何执行具体任务"
│  ├─ bump.md           # 版本号 bump 流程
│  ├─ release.md        # 发布流程
│  └─ component.md      # 组件脚手架流程
├─ rules/                 # 开发规则:"什么不能做"
│  ├─ architecture.md    # 架构约束
│  ├─ security.md        # 安全要求
│  └─ paths.md           # 路径处理规则
├─ skills/                # 技能模式:"可复用的能力"
└─ settings.json         # AI 行为配置

三个文件夹的区别,说人话就是:

文件夹 啥意思 举个栗子
commands/ “怎么做某件事” 怎么 bump 版本号
rules/ “啥不能干” 别用 unwrap()
skills/ “这类事咋做” 怎么创建组件

举个文件的样子

咱们看一个 commands/bump.md 的完整例子:

---
description: Bump version across 5 files
globs: "{Cargo.toml,package.json,...}"
---


## 为什么要有这个命令

五个文件里的版本号必须一模一样。如果不匹配,会出这些问题:
- About 对话框显示的版本号不对
- 安装包没法覆盖旧版本
- README 上写的版本号和实际对不上

## 输入

$ARGUMENTS 必须是:
- `patch` — 最后一位 +1(0.0.2 → 0.0.3)
- `minor` — 中间一位 +1,最后归零(0.0.2 → 0.1.0)
- `major` — 第一位 +1,后面归零(0.1.0 → 1.0.0)

## 流程

### 第一步:读当前版本

`Cargo.toml` 里读 `[workspace.package]` 下面的 `version = "..."`
**如果五个文件版本不一致,停下来报告**。不要继续。

### 第二步:算出新版本

根据 $ARGUMENTS 计算下一个版本号。

### 第三步:写入五个文件

按顺序改这五个文件:
1. `Cargo.toml``[workspace.package] version`
2. `package.json``"version"`
3. `src-tauri/tauri.conf.json``"version"`
4. `README.md` 里的版本号声明
5. `web/src/app/(reader)/app/install/page.mdx` 的版本号声明

**不要提交** — 留着让人 review。

## 验证步骤

写完后,五个文件的版本号必须一模一样。用这个命令检查:
```bash
grep -h "version" Cargo.toml package.json src-tauri/tauri.conf.json

---

## 你看这个文件每个部分的作用

| 部分 | 啥用 | 没它会咋样 |
|------|------|-----------|
| `description` | 让 AI 能找到这个文件 | AI 找不着,不知道有这命令 |
| `globs` | 告诉 AI 这命令会动哪些文件 | AI 可能漏掉某些文件 |
| "为什么要有这个命令" | 让人理解背后的风险 | 人不理解重要性,可能敷衍 |
| "输入" | 明确命令的接口 | 不知道该传入什么参数 |
| "流程" | 一步步怎么做 | 不知道从哪开始,到哪结束 |
| "STOP if" | 啥时候该停下来 | 可能在错误的状态下继续 |
| "验证步骤" | 怎么确认做对了 | 不知道自己有没有做错 |

**重点说说"为什么要有这个命令"这部分**:

如果不写"为什么":

“修改五个文件的版本号” → 执行者不知道为啥要做这件事 → 可能觉得不重要的就省了 → 出问题了


如果写了"为什么":

“修改五个文件的版本号,因为不匹配会导致:

  1. About 对话框显示错误版本
  2. 安装包无法覆盖旧版本
  3. README 撒谎” → 执行者理解了风险 → 认真执行,不偷懒

---

## 怎么把经验"挖"出来

咱们说具体点,四个步骤:

### 第一步:捕获

啥时候该记下来?

**场景 1:code review 的时候**

你:“这个不能用 unwrap(),会 panic。”

新人:“为啥?”

你:“因为如果返回 Err,程序就崩溃了。”

→ 这时候就该记下来:在 rules/ 里写"不要用 unwrap()"


**为啥这时候要记?**

因为这是知识最"鲜活"的时候:
- 你刚发现问题
- 有具体的上下文
- 有真实的代码案例
- 你知道"为什么"和"怎么做"

不记下来,下次新人还会犯同样的错,你还得再说一遍。而且时间久了,你可能都忘了自己说过这话。

---

**场景 2:修 bug 的时候**

同事:“又是因为 Windows 路径的问题。”

你:“为啥老出这问题?”

同事:“代码里硬编码了 /,Windows 用的是 \。”

→ 这时候就该记下来:在 rules/paths.md 里写"不要硬编码路径分隔符"


**为啥这时候要记?**

因为你找到了"问题的根源":
- 知道啥会导致 bug
- 知道咋预防
- 可以写出具体的规则

不记下来,下次还可能出同样的 bug。

---

**场景 3:技术讨论的时候**

别人:“咱们加个 ProjectManager 类吧。”

你:“不行,我们领域模型就四个名词。”

别人:“为啥?”

你:“因为架构要保持简单,不要随便加名词。”

→ 这时候就该记下来:在 rules/architecture.md 里写"不要添加新的领域名词"


**为啥这时候要记?**

因为你做了一个重要的决策:
- 你知道"为什么"做这个决策
- 你有判断依据
- 后来的人需要知道这个依据

不记下来,后来的人可能不理解,想要"改进"实际上破坏了架构。

---

### 第二步:分类

捕获到的东西,该放哪儿?

| 啥东西 | 放哪儿 | 咋判断 |
|--------|--------|-------|
| 命令流程(怎么做某件具体的事) | `commands/` | 是一次性任务,有明确的开始和结束 |
| 架构约束(啥不能做) | `rules/` | 是持续要遵守的规则 |
| 可复用能力(这类事咋做) | `skills/` | 是可以应用到很多地方的模式 |

**举个反例**:

如果把"不要用 unwrap()"写成 `commands/` 的文件:
- 每次都要手动"执行"这个命令
- AI 不会自动遵守
- 没啥意义

应该写成 `rules/` 的文件:
- AI 写代码时会自动遵守
- 不需要手动触发
- 才是正确的用法

---

### 第三步:结构化

怎么写?

**不要一大段话**:

在Windows上路径分隔符是\不是/,所以代码里不要直接写/, 要用Path::join或者MAIN_SEPARATOR常量,还有要注意 有盘符的路径C:\Users...,还有UNC路径\server\share...

这一大段看着就累,找不着重点。

**要分点写**:

永远不要

  • ❌ 硬编码 / 做分隔符
  • ❌ 假设路径以 / 开头

应该

  • ✅ 用 std::path::MAIN_SEPARATOR
  • ✅ 用 Path::join

Windows 的四种路径形状

  1. /Users/… — Unix 绝对路径
  2. C:\Users\… — Windows 盘符路径
  3. \\server\share\… — Windows UNC 路径
  4. \\?\C:\… — Windows verbatim 路径

一眼就能看出重点,也容易记住。

---

### 第四步:验证

写完了不是就完了,得验证。

**让 AI 按你的规则跑一遍**,看它对不对:

你:“按 rules/null-safety.md 检查一下这段代码有没有安全问题”

AI 执行完后…

你:“有没有遗漏?”

AI:“第 15 行有个 unwrap() 在生产代码里,不符合规则。”

你:“对,这正是要抓的。”


如果 AI 没抓到问题,说明你的规则写得不够清楚,得改。

**让人按你的说明做一遍**,看他能不能顺利完成:

你:“按 commands/bump.md 的流程 bump 一下版本号”

新人执行完后…

你:“哪儿不清楚?”

新人:“第二步说’算出新版本’,没说咋算。”

你:“好,我补上。”


如果新人卡住了,说明说明写得不够清楚,得改。

---

## 从零开始的四个月计划

咱们按时间来,一步一步来。

---

### 第 1 周:搭架子

要做三件事:

**1. 创建目录结构**

```bash
mkdir -p .claude/commands
mkdir -p .claude/rules
touch .claude/settings.json

2. 写第一个规则文件

选一个"最常犯的错误"作为第一个规则。

为啥从"最常犯的错误"开始?

  • 因为这是最大的痛点
  • 解决了立竿见影
  • 你能看到效果
  • 有动力继续往下写

比如你的项目老是因为 null pointer 崩溃,第一个规则就写"不要用 unwrap()"。

3. 配置 settings.json

{
  "contextProviders": {
    "claude": {
      ".claude/**": "read"
    }
  }
}

这步是告诉 AI:去 .claude/ 目录里找规则。

第 1 周结束: 你有了一个骨架,有一个规则,AI 知道去哪儿找规则了。


第 2-4 周:边干边记

这三周,养成一个习惯:每次遇到问题,都问自己一句"能不能写成规则"

三个时刻要问

  1. code review 的时候:“这个判断能不能写成规则?”
  2. 修 bug 的时候:“怎么避免这种 bug 再次发生?”
  3. 技术讨论的时候:“这个决策的依据是什么?”

为啥要"每次都问"?

因为不主动问,忙起来就忘了:

  • “这次先算了,下次再写”
  • 结果"下次"从来没来
  • 知识继续丢失

每次都问,才能形成习惯:

  • 第 1 周:得提醒自己
  • 第 2 周:开始有点感觉
  • 第 3-4 周:不自觉地就开始想"这能不能写成规则"

第 2-4 周结束: 你积累了 5-10 个规则,覆盖了项目的主要问题。


第 2-3 月:整理成体系

这时候你有了一堆规则,但可能乱七八糟。

需要按领域整理一下:

rules/
├─ architecture/
│  ├─ domain-model.md
│  └─ boundaries.md
├─ security/
│  ├─ secret-handling.md
│  └─ input-validation.md
├─ performance/
│  └─ n-plus-one-queries.md
└─ style/
   ├─ naming.md
   └─ formatting.md

为啥要整理?

不整理:

  • 找不到相关规则
  • 不知道该用哪个
  • 想要改进时不知道从哪下手

整理之后:

  • 相关的规则在一起
  • 容易找到
  • 容易维护

第 2-3 月结束: 你有一个结构化的规则库,覆盖了各个领域。


第 3 月+:自我进化

规则不是写完就完了,得持续维护。

三件事

  1. 加验证机制

    • git pre-commit hook 检查代码
    • CI 运行 lint 规则
    • 自动化检查
  2. 建立反馈循环

    • 收集违反规则的案例
    • 更新规则
    • 补充遗漏的地方
  3. 持续迭代

    • 定期审查规则
    • 删除过时的
    • 合并重复的

为啥要验证?

因为写了规则不等于被遵守:

  • 可能 AI 不知道规则
  • 可能人不知道规则
  • 可能规则已经过时了

验证才能确保规则真的在起作用。


常见的坑

给你说说新手最容易犯的三个错误。


错误 1:写得太抽象

❌ 不好:
保持代码简洁和可读。

问题:
- 啥叫"简洁"?
- 啥叫"可读"?
- 没人说得清楚

✅ 好:
# 函数长度规则
函数不应超过 50 行。

原因:
- 超过 50 行难以理解
- 通常意味着做了太多事
- 应该拆分

例外:
- 测试代码可以超过 50 行
- 数据生成代码可以超过 50 行

错误 2:写得太死板

❌ 不好:
永远不要写超过 20 行的函数。

问题:
- 太死了
- 有时候确实需要长函数
- 强行拆分反而更乱

✅ 好:
# 函数长度建议
函数通常不应超过 50 行。

如果超过:
- 考虑能不能拆分
- 如果拆分更复杂,保持现状
- 用注释说明为啥这么长

错误 3:写完就不管了

❌ 不好:
- 写了规则
- 从不检查
- 从不更新

结果:
- 规则可能过时
- 规则可能不再适用
- 规则变成摆设

✅ 好:
- 写了规则
- 定期检查
- 根据反馈更新
- 删除过时的

一个完整的例子

咱们从头到尾走一遍。

第 1 步:发现问题

项目又因为 null pointer 崩溃了

第 2 步:捕获

为啥会崩溃?
因为代码用了 unwrap(),没检查 None/Err

第 3 步:分类

这是啥?是架构约束
放哪儿?放 rules/

第 4 步:结构化

写成 rules/null-safety.md

---
description: Null safety rules for Rust code
globs: "**/*.rs"
---


## 永远不要

❌ **永远不要使用 `.unwrap()` 在生产代码中**

**原因**:unwrap 会在值是 None/Err 时 panic,导致整个程序崩溃。

## 应该

✅ **使用 `?` 操作符或显式处理**

```rust
// 好:传播错误
let user = get_user(id)?;

// 好:显式处理
let user = match get_user(id) {
    Ok(u) => u,
    Err(e) => return Err(e),
};

// 好:提供默认值
let user = get_user(id).unwrap_or_default();

唯一例外

只在测试代码中使用 unwrap,且必须在注释中说明为什么不会 panic:

#[test]
fn test_something() {
    let user = get_user(1).unwrap();
    // 安全:测试数据库中 id=1 总是存在
}

验证

在 code review 时:

  • 搜索所有 .unwrap() 调用
  • 确认每个要么在测试中
  • 要么有注释说明为啥不会 panic

**第 5 步:验证**

你:“AI,按 null-safety.md 检查一下这段代码”

AI:“第 23 行有个 unwrap(),不符合规则”

你:“对,这地方得改”


**结束**。一个完整的规则就诞生了。

---

## 你现在可以开始了

别想太多,从最简单的开始:

1. **创建 `.claude/` 目录**
2. **写下"项目里最常犯的错误"**
3. **按模板整理**
4. **让 AI 按规则检查一遍代码**

四个月之后,你会有一套自己的知识系统。

---

## 最后总结一下

`.claude/` 知识系统的核心思想一句话:

**把专家的直觉变成明确的规则,让 AI(和新人)能够像专家一样思考和行动。**

四个步骤:
- **捕获**:从专家脑子中提取知识
- **分类**:让知识易于查找
- **结构化**:让知识易于理解
- **验证**:确保知识正确

缺了哪一步都不行。

**你现在可以从写下"项目中总是犯的错误"开始。**

一件一件来,别着急。