一句话总结
/init不是一个"生成器"------它是一段注入给 LLM 的 prompt,让 Claude 自己去分析你的代码库、自己写 CLAUDE.md- 它有两种模式:默认模式生成基础 CLAUDE.md,新模式(
CLAUDE_CODE_NEW_INIT=1)额外生成 skills 和 hooks - 核心设计原则:只写 Claude 猜不到的------这一条规则决定了整个命令的行为边界
源码定位
bash
src/commands/init.ts ← 命令本体(256行,20KB)
src/commands.ts ← 命令注册中心(引入 init)
src/projectOnboardingState.ts ← 控制"/init 提示"是否显示
命令类型 :prompt
这是理解 /init 的第一个关键------它的类型是 prompt,不是 local(本地执行)也不是 dialog(弹窗交互)。
typescript
const command = {
type: 'prompt', // ← 本质是向 LLM 注入一段 prompt
name: 'init',
progressMessage: 'analyzing your codebase',
source: 'builtin',
async getPromptForCommand() {
maybeMarkProjectOnboardingComplete()
return [{ type: 'text', text: 选择哪个prompt }]
},
} satisfies Command
这意味着:当你输入 /init,Claude Code 不会执行任何本地逻辑,而是把一段精心设计的 prompt 塞进对话,让 LLM 自己去调用 GlobTool、FileReadTool 等工具来分析代码库并生成文件。
一句话总结:/init = 一段 prompt + LLM 自由发挥。
两种模式:新旧之争
源码里定义了两个 prompt 常量:
typescript
const OLD_INIT_PROMPT = `Please analyze this codebase and create a CLAUDE.md file...`
const NEW_INIT_PROMPT = `Set up a minimal CLAUDE.md (and optionally skills and hooks)...`
选择逻辑:
typescript
feature('NEW_INIT') &&
(process.env.USER_TYPE === 'ant' || isEnvTruthy(process.env.CLAUDE_CODE_NEW_INIT))
? NEW_INIT_PROMPT
: OLD_INIT_PROMPT
翻译成人话:
- 默认 :所有用户走
OLD_INIT_PROMPT - 新模式 :Anthropic 内部员工(
USER_TYPE === 'ant')或手动设置CLAUDE_CODE_NEW_INIT=1的用户走NEW_INIT_PROMPT
默认模式:OLD_INIT_PROMPT 全文解读
这是大多数人实际触发的 prompt,一共不到 20 行,但每一行都是设计决策:
css
Please analyze this codebase and create a CLAUDE.md file, which will be
given to future instances of Claude Code to operate in this repository.
开场白设计:直接告诉 LLM 产出物的用途------"给未来的 Claude Code 实例看的"。这决定了写作视角:不是写给人看的文档,是写给 AI 看的指令。
「要写什么」------两条正面规则
vbnet
What to add:
1. Commands that will be commonly used, such as how to build, lint,
and run tests. Include the necessary commands to develop in this
codebase, such as how to run a single test.
2. High-level code architecture and structure so that future instances
can be productive more quickly. Focus on the "big picture" architecture
that requires reading multiple files to understand.
只要求两类信息:
- 常用命令(build/lint/test,特别是跑单个测试的方式)
- 高层架构(需要读多个文件才能理解的全局观)
「不要写什么」------六条禁止规则
这才是 prompt 的精华。Anthropic 发现,不加限制的话 LLM 会写一堆废话,所以用了六条 "Usage notes" 来约束:
| 禁止规则 | 设计意图 |
|---|---|
| 不要重复自己 | 避免 LLM 的复读机倾向 |
| 不要写显而易见的指令(如"写单测"、"不要提交密钥") | AI 本来就知道的事不用教 |
| 不要列出文件结构 | ls 一下就知道了 |
| 不要写通用开发实践 | 这些是任何项目都适用的废话 |
| 不要编造章节(如"常见开发任务") | 防止 LLM 幻觉 |
| 要整合已有的 Cursor/Copilot 规则 | 兼容其他 AI 工具的配置 |
这六条禁止规则,本质上是一个过滤器:只保留"Claude 自己推断不出来的信息"。
固定前缀
vbnet
- Be sure to prefix the file with the following text:
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working
with code in this repository.
强制输出格式一致性。
新模式:NEW_INIT_PROMPT 的 8 阶段流程
新模式的 prompt 长达 200+ 行,设计了完整的 8 阶段交互流程:
yaml
Phase 1: Ask what to set up ← 问用户要什么
Phase 2: Explore the codebase ← 派子 Agent 扫描代码
Phase 3: Fill in the gaps ← 追问代码回答不了的问题
Phase 4: Write CLAUDE.md ← 写项目级指令
Phase 5: Write CLAUDE.local.md ← 写个人级指令(gitignore)
Phase 6: Suggest and create skills ← 生成可复用技能
Phase 7: Suggest additional optimizations ← 推荐 hooks/插件
Phase 8: Summary and next steps ← 总结
Phase 1 的设计巧思
用 AskUserQuestion 工具让用户做选择题,而不是开放式提问:
vbnet
Options: "Project CLAUDE.md" | "Personal CLAUDE.local.md" | "Both"
Options: "Skills + hooks" | "Skills only" | "Hooks only" | "Neither"
为什么这么设计? 减少用户认知负荷------大多数人不知道 skills 和 hooks 是什么,给选项比解释概念更高效。
Phase 2 的核心逻辑
diff
Launch a subagent to survey the codebase, and ask it to read key files:
- manifest files (package.json, Cargo.toml, pyproject.toml...)
- README, Makefile/build configs, CI config
- existing CLAUDE.md, .claude/rules/
- AGENTS.md, .cursor/rules, .cursorrules
- .github/copilot-instructions.md, .windsurfrules, .clinerules
- .mcp.json
重点:它会读取所有竞品的 AI 配置文件(Cursor、Copilot、Windsurf、Cline),并整合进 CLAUDE.md。这是一个产品策略------降低迁移成本。
Phase 4 的写入规则
新模式的核心过滤器更极端:
Every line must pass this test: "Would removing this cause Claude to make mistakes?" If no, cut it.
然后给出了明确的 Include/Exclude 清单:
Include(写):
- Claude 猜不到的命令(非标准脚本、特殊 flag)
- 与语言默认不同的风格规则
- 测试的特殊姿势
- 仓库礼仪(分支命名、PR 规范)
- 必需的环境变量
- 非显而易见的坑
Exclude(不写):
- 文件结构(Claude 自己看得到)
- 标准语言惯例(Claude 已经知道)
- 通用建议
- 详细 API 文档(用
@path/to/import引用) - 频繁变化的信息
- 从 manifest 能推断的标准命令(如
npm test、pytest)
启动提示逻辑:projectOnboardingState.ts
当你打开一个新项目时,Claude Code 会提示"Run /init to create a CLAUDE.md"。这个逻辑在 projectOnboardingState.ts:
typescript
export function getSteps(): Step[] {
const hasClaudeMd = getFsImplementation().existsSync(
join(getCwd(), 'CLAUDE.md'),
)
const isWorkspaceDirEmpty = isDirEmpty(getCwd())
return [
{
key: 'claudemd',
text: 'Run /init to create a CLAUDE.md file...',
isComplete: hasClaudeMd, // ← 只检查根目录
isEnabled: !isWorkspaceDirEmpty,
},
]
}
注意这个 Bug :它只检查根目录的 CLAUDE.md,不检查 .claude/CLAUDE.md。所以即使你把文件放在 .claude/ 目录下(这是官方支持的位置),启动时还是会提示你运行 /init。(GitHub Issue #45377)
控制显示次数的逻辑:
typescript
export const shouldShowProjectOnboarding = memoize((): boolean => {
if (
projectConfig.hasCompletedProjectOnboarding ||
projectConfig.projectOnboardingSeenCount >= 4 || // ← 最多显示4次
process.env.IS_DEMO
) {
return false
}
return !isProjectOnboardingComplete()
})
看了 4 次还不运行 /init?那就不再烦你了。
命令注册机制
在 commands.ts 中,init 的注册方式极其简单:
typescript
import init from './commands/init.js'
// 在命令数组中直接引入
const commands = [
// ...其他命令
init,
// ...
]
所有命令共享 Command 类型接口:
typescript
type Command = {
type: 'prompt' | 'local' | 'dialog'
name: string
description: string
getPromptForCommand(): Promise<Content[]> // prompt 类型必须实现
// ...
}
三种命令类型决定了执行方式:
prompt:注入 prompt 让 LLM 自己执行(/init、/commit、/review)local:本地直接执行,不需要 LLM(/clear、/exit、/model)dialog:弹出交互对话框(/config、/permissions)
设计亮点:三个可迁移的 Pattern
1. Prompt-as-Command 模式
/init 不是代码生成器,是 prompt 注入器。这种设计让命令的"智能"完全来自 LLM 本身------你不需要写复杂的代码逻辑来分析代码库,只需要写一段好的 prompt。
可迁移到你的项目:如果你在做 AI Agent,很多命令不需要写后端逻辑,只需要设计好 prompt 然后让 LLM 调用工具自己搞定。
2. 负面约束 > 正面指令
整个 OLD_INIT_PROMPT 的精华不在"要写什么"(2 条),而在"不要写什么"(6 条)。这是 prompt engineering 的核心经验:告诉 LLM 什么不该做,比告诉它该做什么更有效。
可迁移:写 system prompt 时,花更多精力在"禁止规则"上,而不是"要求规则"上。
3. 幂等设计
css
- If there's already a CLAUDE.md, suggest improvements to it.
/init 不是一次性命令------重复运行不会覆盖,而是"改进"。这让用户可以安全地在项目演进过程中反复运行。
可迁移:设计 AI 工具的命令时,默认做成幂等的(可重复执行,结果稳定),而不是破坏性的。
实战建议
启用新模式
bash
# 单次
CLAUDE_CODE_NEW_INIT=1 claude
# 永久
echo 'export CLAUDE_CODE_NEW_INIT=1' >> ~/.zshrc
什么时候重新运行 /init
- 大型重构后(框架迁移、目录结构变更)
- 删除了已废弃模块后
- 新人入职前(确保 CLAUDE.md 是最新的)
CLAUDE.md 写得好的标准
问自己一个问题:如果删掉这一行,Claude 会犯错吗?
如果不会------删掉它。
下篇预告
第 2 篇:128K 上下文也会爆?看 Claude Code 怎么用一条命令「瘦身」对话
/compact 命令解决的是 AI Agent 最核心的工程问题:当对话越来越长,上下文窗口不够用时怎么办?我们从源码看 Anthropic 的压缩策略。
本文基于 Claude Code CLI 源码(TypeScript,反编译自 source map)分析。源码路径:src/commands/init.ts。如有更新,以最新版本为准。