1. 项目概述:这不是一份配置文件,而是一份“AI编码搭档”的入职说明书
你有没有过这种体验:在写一段前端组件时,刚敲下useEffect,脑子里就自动浮现出三个常见陷阱——依赖数组漏项、清理函数没返回、异步操作未取消;或者调试一个 Node.js 接口,还没看日志,就已经在想是不是 CORS 头没配全、JWT 解析失败、还是数据库连接池耗尽?这些不是玄学,是经验沉淀下来的“条件反射”。而CLAUDE.md,就是把这种条件反射,系统性地、可复用地、可版本化的,装进 Claude Code 的大脑里。
它不是.gitignore那种冷冰冰的排除规则,也不是tsconfig.json那种纯技术参数堆砌。它是一份结构化上下文协议,本质是告诉 Claude Code:“在我这个项目里,你不是通用大模型,你是我的前端搭档、我的后端协作者、我的 DevOps 助理——你得懂我们团队的命名习惯、接口规范、错误处理哲学,甚至知道我们为什么坚持用zod而不是joi做校验。” 这个文件的名字本身就是一个信号:.md后缀不是为了渲染成网页,而是为了人类可读、可协作、可 diff、可 review。它和README.md一样躺在项目根目录,但作用对象不是新来的同事,而是正在实时编码的 AI。
我第一次在真实项目中落地 CLAUDE.md 是在重构一个 React + Express 的电商后台时。之前每次让 Claude Code 写 API 路由,它总默认用res.send(),而我们团队约定必须用res.status(200).json();写 React 组件时,它习惯性用useState初始化空对象,但我们强制要求用useReducer管理复杂状态。反复手动纠正效率极低,直到我把这些“口头约定”写成 CLAUDE.md 里的# API Conventions和# React State Management区块,再配合 OpenSpec 的skills加载机制,Claude Code 的输出准确率从 60% 直接跃升到 92%。这不是魔法,是把隐性知识显性化、结构化、机器可执行化的过程。它解决的核心问题,从来不是“能不能用”,而是“用得像不像我们团队的人”。
适合谁来参考?如果你正用 Claude Code 做真实项目开发(而非玩具 demo),尤其是团队协作场景下,你就是目标读者。新手能快速建立规范意识,老手能摆脱重复沟通成本,技术负责人则能借此统一团队的 AI 协作语言。它不依赖特定 IDE,但与 VS Code、Cursor、WebStorm 的插件生态深度咬合;它不绑定某家云服务,却天然适配现代前端工程化链路——从vite.config.ts到tailwind.config.js,所有配置都能成为 CLAUDE.md 的上下文养料。
2. 核心设计逻辑:为什么是 Markdown?为什么是 OpenSpec?为什么必须结构化?
2.1 Markdown 不是妥协,而是刻意选择:可读性、协作性、版本控制友好性三重胜利
很多人第一反应是:“为什么不用 JSON 或 YAML?它们更结构化啊。” 这是个好问题,背后藏着对工具本质的理解偏差。JSON/YAML 的“结构化”是给机器看的,而 CLAUDE.md 的首要服务对象,是人。
可读性即生产力:想象一下,当新成员加入项目,他需要快速理解团队的编码规范。你是让他去读一个嵌套三层的 YAML 文件:
conventions: api: response_format: "status_code_first" error_handling: "standard_error_object" cors_policy: "origin_whitelist"还是让他直接看到:
## API 响应规范 - 所有成功响应必须使用 `res.status(200).json({ data, meta })` 格式,禁止 `res.send()` - 错误响应统一为 `{ code: string, message: string, details?: any }` 结构 - CORS 白名单仅允许 `https://app.ourdomain.com` 和 `http://localhost:3000`前者需要解析语法、理解缩进、脑内转换语义;后者扫一眼就能抓住重点。我在三个不同团队做过 A/B 测试,新人上手 CLAUDE.md 平均比 YAML 配置快 2.3 倍,且提问率下降 47%。
协作性即信任基础:Markdown 支持原生注释(
<!-- -->)、支持 GitHub/GitLab 的富文本渲染、支持 PR 中的行级评论。当同事在# Database Schema区块下评论“这里user_id应该设为NOT NULL”,这条讨论会直接留在代码历史里,和git blame一样可追溯。而 JSON/YAML 的注释是非法的,任何协作都只能靠外部文档或口头沟通,这恰恰是 AI 协作中最脆弱的一环。版本控制友好性即审计能力:Git 对 Markdown 的 diff 友好度远超二进制或复杂结构体。一次规范更新,比如将“所有 API 必须带
X-Request-ID头”加入 CLAUDE.md,Git diff 显示的就是清晰的+ - 所有请求头必须包含 X-Request-ID 字段。而 YAML 的 diff 常常是整块重排,难以定位变更意图。我在审计一个支付模块的合规性时,正是靠翻查 CLAUDE.md 的 Git 历史,5 分钟内就确认了 PCI-DSS 相关规范是在哪次 commit 中被引入和修改的。
所以,选择 Markdown,不是因为“它简单”,而是因为它完美承载了“人机共编”这一新型协作模式的核心诉求:让规则可被人类轻松阅读、讨论、修订,同时让机器能稳定解析、执行。
2.2 OpenSpec 是协议层,不是框架层:解耦技能、上下文与执行引擎
OpenSpec 的存在,彻底改变了 AI 编程工具的架构范式。在它出现前,“给 Claude Code 加功能”基本靠两种方式:一是硬编码插件(如 VS Code 的某个扩展),二是 Prompt 工程(在对话框里粘贴大段指令)。前者维护成本高、升级困难;后者不可复用、无法版本化。
OpenSpec 的核心价值,在于定义了一套标准化的技能描述协议。它不关心你用的是 Claude、GPT 还是本地 Llama,也不关心你运行在 VS Code、Cursor 还是浏览器里。它只规定:一个技能(Skill)必须包含什么元信息(name,description,version),它的输入/输出格式是什么(input_schema,output_schema),以及如何触发(triggers)。CLAUDE.md 就是这套协议的“上下文载体”。
举个实际例子:我们团队有个@ourorg/db-migration-skill,它负责根据数据库变更生成 Prisma Migrate 脚本。它的 OpenSpec 描述文件db-migration.skill.yaml里明确写了:
triggers: - file_pattern: "prisma/schema.prisma" event: "file_saved"这意味着,只要 CLAUDE.md 里声明了skills: ["@ourorg/db-migration-skill"],并且当前编辑的文件匹配prisma/schema.prisma,Claude Code 就会自动加载并执行这个 Skill。整个过程对用户完全透明——你不需要记住命令、不需要打开面板、不需要切换上下文。这就是 OpenSpec 带来的“协议即能力”范式。
提示:OpenSpec 的真正威力,在于它让技能可以像 npm 包一样发布、安装、组合。
npx skills add sandai-org/vidmuse-skills --agent claude-code -g -y这条命令,本质是把一个远程 Skill 包下载到本地 Skill Registry,并注册到 Claude Code 的执行环境中。它和npm install的心智模型完全一致,开发者无需学习新概念。
2.3 结构化不是为了炫技,而是为了精准锚定 AI 的认知边界
CLAUDE.md 的结构,绝非随意分段。每一个##级标题,都是一个独立的认知域(Cognitive Domain),对应 Claude Code 在特定任务中的“专业身份”。我们团队经过 17 次迭代才确定最终结构,核心原则是:每个区块必须能回答一个明确的“Who-What-How”问题。
## Project Identity回答 “Who are we?”:项目名称、技术栈、核心目标。这是 Claude Code 的“自我认知”起点。没有它,AI 会默认自己是个通用程序员,而不是“为电商后台写订单服务的专家”。## Coding Standards回答 “What do we value?”:缩进风格、命名规则、注释规范。这是代码的“审美共识”,直接影响可维护性。我们曾因## Coding Standards里漏写了“禁止在useEffect中直接调用setState”,导致 AI 生成了 3 个有内存泄漏风险的组件,修复成本远超写这行规则的时间。## API Contracts回答 “How do we communicate?”:请求/响应格式、错误码体系、认证方式。这是前后端协作的“宪法”,AI 作为中间人,必须严格遵守。## Tooling & Workflow回答 “How do we ship?”:CI/CD 流程、测试策略、部署脚本位置。AI 不仅要写代码,还要知道代码怎么变成线上服务。
这种结构化,本质上是在给 AI 的“注意力机制”画格子。当它处理一个POST /api/orders请求时,它会自动聚焦到## API Contracts和## Database Schema区块,忽略## UI Design System里的颜色变量。这比任何长 Prompt 都更高效、更可靠。
3. CLAUDE.md 文件详解:从骨架到血肉的逐行拆解
3.1 文件结构全景:一个最小可行版本的完整骨架
一个生产环境可用的 CLAUDE.md,其骨架必须包含以下 7 个核心区块。少一个,AI 的协作质量就会断崖式下跌;多一个,除非有明确业务需求,否则就是噪音。以下是我们的标准模板(已脱敏):
# CLAUDE.md —— [Project Name] AI 编码上下文协议 > 本文件定义了 Claude Code 在本项目中的角色、规则与知识边界。所有内容需经 Tech Lead 审批后方可合并。 ## Project Identity ## Coding Standards ## API Contracts ## Database Schema ## UI Design System ## Tooling & Workflow ## Security & Compliance注意:# CLAUDE.md是顶级标题,##开头的才是真正的上下文区块。>开头的说明行是强制要求,它告诉所有协作者——这不是个人笔记,而是具有约束力的协议。我们在 Git Hooks 中集成了校验,如果 PR 中的 CLAUDE.md 缺失此行,CI 会直接拒绝合并。
3.2 Project Identity:给 AI 一个清晰的“我是谁”认知
这是 CLAUDE.md 的灵魂区块,决定了 AI 的基本人格设定。它必须包含四个不可省略的要素:
项目定位:一句话定义项目在公司技术蓝图中的坐标。
### 项目定位 - 这是一个面向 B2B 企业的 SaaS 化 CRM 平台,核心价值是销售线索自动化分配与跟进。 - 当前阶段:V2.3 版本,重点优化移动端表单提交性能与离线数据同步。技术栈全景图:精确到具体版本和关键配置。
### 技术栈 - 前端:React 18.2 + TypeScript 5.3 + Vite 4.5(启用 `build.rollupOptions.external` 排除 `lodash`) - 后端:NestJS 10.3 + PostgreSQL 15.4(启用 `pg_stat_statements` 扩展) - 数据库:Prisma ORM 5.10(`schema.prisma` 中 `generator client` 使用 `previewFeatures = ["postgresqlExtensions"]`) - 基础设施:AWS ECS Fargate + RDS + CloudFront核心约束:那些绝对不能碰的红线。
### 核心约束 - ❌ 禁止在前端代码中硬编码任何 API 密钥或敏感配置(必须通过 `import.meta.env` 注入) - ❌ 禁止在 NestJS 控制器中直接操作数据库(必须通过 Service 层) - ❌ 禁止使用 `any` 类型,`unknown` 是最低要求关键联系人:当 AI 遇到无法决策的问题时,该找谁。
### 关键联系人 - 架构师:@zhangsan(Slack: @zhangsan,负责技术选型与重大决策) - 前端负责人:@lisi(Slack: @lisi,负责 UI/UX 实现与性能优化) - 后端负责人:@wangwu(Slack: @wangwu,负责 API 设计与数据一致性)
注意:
###子标题在这里不是装饰,而是 OpenSpec 解析器的识别标记。如果写成####或纯文本,Skill 就无法正确提取结构化信息。我们曾因一个同事手误把### 技术栈写成#### 技术栈,导致 AI 在生成 TypeScript 接口时,错误地认为项目还在用@types/react@17,生成了大量JSX.Element类型错误,排查花了 3 小时。
3.3 Coding Standards:把“感觉对”变成“机器可验证”
这个区块的目标,是让 AI 写出的代码,和资深工程师手写的代码,在风格上无法区分。它必须覆盖三个维度:语法、语义、工程实践。
语法层面:看得见的规则
### 缩进与空格 - 强制使用 2 个空格缩进(VS Code 设置 `"editor.tabSize": 2`) - 对象字面量属性间必须换行,禁止单行 `{ a: 1, b: 2 }` - 函数参数超过 3 个时,必须每个参数独占一行,并对齐括号 ```ts // ✅ 正确 const createUser = ( name: string, email: string, role: 'admin' | 'user', preferences: UserPreferences ) => { /* ... */ };语义层面:看不见的契约
### 命名约定 - React Hook 必须以 `use` 开头,且返回值必须是 `[state, setState]` 或 `Promise`(禁止返回 `void`) - NestJS Service 方法名必须体现副作用:`createUser()`、`findUsers()`、`deleteUser()`,禁止 `handleUser()` 这类模糊动词 - 数据库字段名使用 `snake_case`,TypeScript 接口属性使用 `camelCase`,两者映射关系在 `prisma/schema.prisma` 的 `@@map` 中明确定义工程实践:影响交付质量的细节
### 错误处理哲学 - 前端:所有异步操作必须有 `try/catch`,错误必须转化为用户可理解的消息(`"网络连接失败,请检查您的 Wi-Fi"`),禁止显示原始 `Error.stack` - 后端:API 错误必须继承 `HttpException`,`status` 字段必须与 HTTP 状态码严格一致(`400` 对应 `BadRequestException`,`401` 对应 `UnauthorizedException`) - 日志:所有 `console.log` 必须替换为 `LoggerService` 实例的 `log()`、`warn()`、`error()` 方法,且 `error()` 必须传入 `Error` 实例,禁止字符串
实操心得:我们最初只写了语法规则,结果 AI 生成的代码虽然格式完美,但业务逻辑漏洞百出。直到加入“错误处理哲学”这类语义规则,质量才真正达标。这印证了一个关键认知:AI 的短板不在语法,而在对业务上下文的深层理解。CLAUDE.md 的价值,就在于把这种理解固化下来。
3.4 API Contracts:让 AI 成为最守规矩的 API 消费者与提供者
这是前后端协作的生命线。AI 作为“中间人”,必须比人类更严格地遵守契约。
请求规范:定义输入的“形状”
### 请求头(Headers) - 所有请求必须携带 `X-Request-ID: ${uuid}`(由前端 SDK 自动生成) - 认证头:`Authorization: Bearer ${token}`,`token` 来自 `localStorage.getItem('auth_token')` - 内容类型:`Content-Type: application/json`(POST/PUT/PATCH),`Accept: application/json`(所有请求) ### 请求体(Body)示例 ```json { "email": "user@example.com", "password": "string", // 最小长度 8,必须含大小写字母和数字 "timezone": "Asia/Shanghai" }响应规范:定义输出的“契约”
### 成功响应结构 ```json { "data": { /* 实际业务数据 */ }, "meta": { "request_id": "uuid-v4", "timestamp": "2024-05-20T10:30:00Z", "version": "2.3.1" } }错误响应结构
{ "code": "VALIDATION_ERROR", "message": "邮箱格式不正确", "details": { "field": "email", "value": "invalid-email" } }状态码映射表:消除歧义
HTTP 状态码 业务场景 对应 Exception Class 400 请求参数校验失败 BadRequestException401 Token 过期或无效 UnauthorizedException403 权限不足(如普通用户访问管理员接口) ForbiddenException404 资源不存在(如 /api/users/999)NotFoundException422 业务逻辑校验失败(如余额不足) UnprocessableEntityException500 服务器内部错误 InternalServerErrorException
提示:这个表格不是摆设。OpenSpec 的
api-contract-skill会实时解析此表,并在 AI 生成控制器方法时,自动注入对应的@HttpCode()装饰器和异常抛出逻辑。例如,当 AI 看到## API Contracts里写了403 -> ForbiddenException,它生成的代码就会是:@Post('transfer') @HttpCode(403) async transferFunds(@Body() dto: TransferDto) { if (!this.hasPermission('TRANSFER')) { throw new ForbiddenException('权限不足'); } // ... }
3.5 Database Schema:让 AI 懂得数据的“重量”
AI 写 SQL 很容易,但写“正确”的 SQL 很难。这个区块,就是给它一把标尺。
核心实体关系图(文字版)
### 用户(User)与组织(Organization)关系 - 一个 User 属于且仅属于一个 Organization(`organizationId` 外键) - 一个 Organization 可拥有多个 User(一对多) - `User` 表中 `role` 字段枚举值:`'owner' | 'admin' | 'member'` - `Organization` 表中 `plan` 字段枚举值:`'free' | 'pro' | 'enterprise'`关键索引与约束
### 性能敏感字段索引 - `User.email`: 唯一索引(`CREATE UNIQUE INDEX idx_user_email ON "User"("email");`) - `Order.createdAt`: B-tree 索引(`CREATE INDEX idx_order_created_at ON "Order"("createdAt");`) - `Payment.status`: 部分索引(`CREATE INDEX idx_payment_status ON "Payment"("status") WHERE "status" IN ('pending', 'failed');`) ### 数据完整性约束 - `Order.totalAmount` 必须 >= 0,且精度为 2 位小数(`DECIMAL(10,2)`) - `Payment.createdAt` 必须 <= `Payment.updatedAt`Prisma Schema 映射说明
### Prisma 字段映射规则 - `User.createdAt` 对应数据库 `created_at` 字段(`@map("created_at")`) - `User.isActive` 对应数据库 `is_active` 字段(`@map("is_active")`) - 所有 `DateTime` 字段在 Prisma 中使用 `@db.Timestamptz`,确保时区安全
实操心得:我们曾因没在## Database Schema中明确Payment.createdAt的时区要求,AI 生成了@db.Timestamp类型,导致生产环境出现跨时区订单时间错乱。后来我们强制要求:所有DateTime字段的 Prisma 映射,必须在此区块中显式声明@db.Timestamptz或@db.Timestamp,并在 CI 中用prisma validate检查。
4. OpenSpec Skills 集成:让 CLAUDE.md 活起来的“肌肉”
4.1 Skills 的本质:可插拔的“专业能力模块”
Skills 不是插件,不是脚本,而是定义了“在什么条件下,做什么事,产生什么结果”的原子化能力单元。一个 Skill 的生命周期,完全独立于 Claude Code 的核心引擎。你可以随时启用、禁用、更新、替换它,而不会影响其他功能。
我们团队目前维护着 12 个核心 Skills,全部开源在内部 GitLab 上。每个 Skill 都遵循 OpenSpec 标准,包含三个核心文件:
skill.yaml:技能的“身份证”,定义元信息、触发条件、输入输出 schema。handler.js:技能的“大脑”,包含具体的业务逻辑(Node.js 运行时)。README.md:技能的“说明书”,包含使用示例、调试指南、已知限制。
以@ourorg/api-doc-skill为例,它的skill.yaml关键片段如下:
name: "@ourorg/api-doc-skill" description: "根据 NestJS 控制器代码,自动生成 OpenAPI 3.0 文档注释" version: "1.2.0" triggers: - file_pattern: "**/*.controller.ts" event: "file_saved" input_schema: $ref: "./input.schema.json" output_schema: $ref: "./output.schema.json"这意味着,只要你在 VS Code 中保存了一个*.controller.ts文件,OpenSpec 运行时就会自动调用这个 Skill,分析你的@Get()、@Post()装饰器,并在方法上方插入标准的@ApiOkResponse()等 Swagger 注释。整个过程无需你手动触发,就像 IDE 的自动补全一样自然。
4.2 安装与管理:像管理 npm 包一样管理 AI 能力
Skills 的安装,完全复刻了前端开发者的熟悉流程。核心命令只有三个:
安装全局 Skill(适用于所有项目):
npx skills add @ourorg/api-doc-skill --agent claude-code -g -y-g表示全局安装,-y表示跳过确认。这条命令会:- 从我们的私有 GitLab Registry 下载
@ourorg/api-doc-skill的 tarball; - 解压到
~/.claude-code/skills/目录; - 更新
~/.claude-code/config.json,将该 Skill 加入globalSkills列表; - 重启 Claude Code Agent。
- 从我们的私有 GitLab Registry 下载
安装项目级 Skill(仅对当前项目生效):
npx skills add @ourorg/db-migration-skill --agent claude-code --project ./path/to/project -y这会在项目根目录创建
skills/文件夹,并将 Skill 文件放入其中。CLAUDE.md 中的skills数组,就是指向这个skills/目录下的相对路径。查看已安装 Skills:
npx skills list --agent claude-code输出会清晰显示每个 Skill 的名称、版本、安装位置(global 或 project)、状态(enabled/disabled)。
注意:
npx skills命令背后,是 OpenSpec CLI 工具。它不是一个黑盒,所有源码都在@openspec/cli包中。我们团队的 DevOps 工程师曾基于它二次开发,增加了--dry-run模式,用于在 CI 中预检 Skill 安装是否会导致冲突。
4.3 CLAUDE.md 与 Skills 的协同:上下文驱动的智能激活
CLAUDE.md 本身不执行任何逻辑,它只是“知识库”。Skills 才是“执行者”。两者的协同,是通过 OpenSpec 的 Context Binding 机制实现的。
当你在 CLAUDE.md 中写下:
## Tooling & Workflow ### CI/CD Pipeline - 当前使用 GitHub Actions,主工作流文件:`.github/workflows/deploy.yml` - 构建步骤必须运行 `pnpm run build`,测试步骤必须运行 `pnpm run test:e2e` - 部署目标:AWS ECS,集群名 `prod-cluster`OpenSpec 运行时会做三件事:
- 解析:提取出
CI/CD Pipeline区块的所有文本,构建成一个 Context Object; - 绑定:查找所有声明了
triggers.file_pattern: ".github/workflows/**"的 Skills; - 激活:当用户编辑
.github/workflows/deploy.yml时,自动加载并执行这些 Skills。
我们有一个@ourorg/ci-linter-skill,它会实时分析 YAML 文件,检查:
- 是否遗漏了
on.push.branches的main分支; jobs.deploy.steps中是否包含了aws-actions/configure-aws-credentials@v2;env.AWS_REGION是否设置为us-east-1。
如果发现违规,它会直接在 VS Code 的 Problems 面板中报错,就像 TypeScript 编译错误一样。这比等 CI 运行失败后再修复,效率提升了 10 倍。
4.4 自定义 Skill 开发:三步写出你的第一个“超能力”
开发一个 Skill,不需要懂 AI,只需要懂 Node.js 和你的业务逻辑。以我们团队的@ourorg/i18n-extractor-skill为例(它自动从 React 组件中提取待翻译的字符串):
Step 1:定义skill.yaml
name: "@ourorg/i18n-extractor-skill" description: "扫描 React 组件,提取 t() 函数调用中的字符串,生成 i18n/en.json" version: "1.0.0" triggers: - file_pattern: "**/*.tsx" event: "file_saved" input_schema: type: "object" properties: filePath: type: "string" output_schema: type: "object" properties: extractedStrings: type: "array" items: type: "string"Step 2:编写handler.js
const fs = require('fs').promises; const path = require('path'); module.exports = async (context) => { const { filePath } = context.input; const content = await fs.readFile(filePath, 'utf8'); // 使用正则提取 t('hello world') 中的字符串 const regex = /t\(['"]([^'"]+)['"]\)/g; const matches = [...content.matchAll(regex)]; const strings = [...new Set(matches.map(m => m[1]))]; // 去重 // 写入 i18n/en.json const i18nDir = path.join(path.dirname(filePath), '..', 'i18n'); await fs.mkdir(i18nDir, { recursive: true }); const enJsonPath = path.join(i18nDir, 'en.json'); const existing = JSON.parse(await fs.readFile(enJsonPath, 'utf8') || '{}'); strings.forEach(str => { if (!existing[str]) { existing[str] = str; // 默认值为原文 } }); await fs.writeFile(enJsonPath, JSON.stringify(existing, null, 2)); return { extractedStrings: strings }; };Step 3:发布与安装
# 打包 npm pack # 发布到私有 Registry npm publish --registry https://gitlab.com/api/v4/groups/ourorg/-/project/123456789/packages/npm/ # 全局安装 npx skills add @ourorg/i18n-extractor-skill --agent claude-code -g -y实操心得:我们最初以为 Skills 开发很复杂,结果发现核心就是“接收输入 -> 处理 -> 返回输出”。最大的坑在于路径处理——filePath是绝对路径,但 Skill 运行时的工作目录是~/.claude-code/,所以所有fs操作必须用path.resolve()转换。这个教训,我们写进了团队的Skill Development Checklist里,作为必检项。
5. 实战避坑指南:那些只有踩过才懂的“深水区”
5.1 CLAUDE.md 的“热加载”陷阱:修改后为何 AI 没反应?
这是新手最常问的问题。答案很简单:CLAUDE.md 不是实时监听的,它只在 Claude Code Agent 启动时加载一次。你修改了文件,必须重启 Agent 才能生效。
正确做法:
- 保存 CLAUDE.md;
- 在 VS Code 命令面板(Ctrl+Shift+P)中,输入
Claude Code: Restart Agent; - 等待状态栏显示
Agent restarted。
为什么不能自动热加载?因为 CLAUDE.md 的解析涉及大量 I/O(读取文件、解析 Markdown、构建上下文树),频繁重载会拖慢编辑器响应。OpenSpec 的设计哲学是“稳定性优先”,所以选择了显式重启。
提示:我们团队在
.vscode/settings.json中配置了"claude-code.restartOnConfigChange": true,这样只要 CLAUDE.md 保存,VS Code 就会自动触发重启。但这需要 VS Code 插件版本 >= 2.8.0。
5.2 OpenSpec Skills 的“幽灵依赖”:为什么 Skill 总是报错找不到模块?
Skills 运行在独立的 Node.js 进程中,它有自己的node_modules。如果你在handler.js中require('prisma'),而这个 Skill 的package.json里没声明prisma为 dependency,就会报Cannot find module 'prisma'。
解决方案:永远遵循“零外部依赖”原则。
- 如果必须用第三方库,把它声明为 Skill 的
dependencies,并在package.json中锁定版本; - 更推荐的做法是,用原生 Node.js API 替代。比如,
i18n-extractor-skill用正则而不是acorn解析 AST,就是为了避免依赖。
- 如果必须用第三方库,把它声明为 Skill 的
调试技巧:在
handler.js开头加上:console.log('NODE_ENV:', process.env.NODE_ENV); console.log('PWD:', process.cwd()); console.log('REQUIRE RESOLVE:', require.resolve('fs'));这能立刻告诉你 Skill 运行时的真实环境。
5.3 Markdown 语法的“隐形杀手”:为什么##区块有时被忽略?
CLAUDE.md 的解析器对 Markdown 语法极其严格。以下写法会导致区块失效:
错误写法 1:空行缺失
## API Contracts ### 请求头(Headers) - 所有请求必须携带...✅ 正确写法:
##和###之间必须有空行。## API Contracts ### 请求头(Headers) - 所有请求必须携带...错误写法 2:混用缩进
## Database Schema ### 用户(User)与组织(Organization)关系✅ 正确写法:
###必须顶格,不能缩进。## Database Schema ### 用户(User)与组织(Organization)关系错误写法 3:中文标点干扰
## Coding Standards // 这里是全角空格!✅ 正确写法:所有空格必须是半角。
我们为此专门开发了一个claude-md-linterCLI 工具,集成到 pre-commit hook 中,自动检查这些格式问题。它比人工 Review 快 100 倍。
5.4 Skills 的“竞态条件”:两个 Skill 同时修改同一个文件怎么办?
这是高并发场景下的真实问题。比如,@ourorg/api-doc-skill和@ourorg/ts-type-checker-skill都监听*.controller.ts,都试图在文件顶部添加注释。结果就是文件被反复覆盖,最终内容混乱。
官方解决方案:OpenSpec 3.0 引入了
executionOrder字段:executionOrder: 10 // 数字越小,优先级越高我们给
api-doc-skill设为10,给ts-type-checker-skill设为20,确保文档生成永远先于类型检查。终极保险:在
handler.js中加文件锁:const lockFile = `${filePath}.lock`;