困惑在哪里
办公场景里我们会层层嵌套文件夹——'2026 > 第三季度 > 合同 > 最终版'很自然。所以刚开始写代码时,会以为项目也该这样一层层嵌进去。
但像 Claude Code 这样的 AI 编码工具,要求把一个项目放在一个'扁平'的文件夹里,并从该文件夹开始工作。为什么?只有一个原因:驱动软件的三大核心装置,都共享同一个边界——文件夹。
先弄清三个术语——git、harness、部署是什么
git 是代码的'版本保存工具'。它把此刻的文件状态整体拍下来(一次 commit),以便日后回退或比较。一个存储单位叫作'仓库(repository, repo)'。
harness 是为了让 AI 更好地工作而预先配置项目,就像给新员工的入职文档。其核心是名为 CLAUDE.md、AGENTS.md 的规则文档;AI 打开那个文件夹时会自动读取,从而不必每次都重复解释'这个项目是什么、别做什么'。
部署(deploy)是把做好的代码放到互联网上让他人可见,代表性服务如 Vercel、Railway。下面依次看这三者如何被一个文件夹捆在一起。
出发点:文件系统是一棵树
计算机的文件结构是一棵倒过来的树。枝条自上而下延展,一个文件夹意味着这棵树的'一整根分支'——该文件夹加上其下所有子文件夹与文件,作为一个整体。这叫封闭分支,即 subtree。
这为何重要?因为一个文件夹免费给出了'从这里到这里是一个整体'的边界。后面的 git、harness、部署都需要边界清晰的文件集合,而文件夹已经提供了这个边界。于是它们不重新造轮子,而是搭在文件夹上。
git:文件夹就是仓库
git 会在文件夹里建一个隐藏的 .git 文件夹,并把 .git 的父文件夹当作'根',追踪其下的一切。所以一个 .git = 一个仓库 = 一个文件夹。
一次 commit(快照)会原子地、即整体一次性拍下'该文件夹下每个文件的状态'。没有边界,就无法定义要拍什么。而且 git 内部所有文件位置都以相对根目录的相对路径(如 src/app.js)保存。没有共同的根——文件夹——这套相对路径体系根本无法成立。所以仓库等于一个文件夹是设计上的必然。
harness:规则锚定在文件夹根目录
AI 工具启动会话时,会自动读取工作文件夹根目录下的 CLAUDE.md、AGENTS.md。所以只要正确打开文件夹,AI 就能在'已理解项目'的状态下开始——无需每次重新解释技术栈、禁忌事项和验证命令。
规则加载是'累加'而非覆盖:全局(~/.claude/CLAUDE.md)→ 项目根 → 子文件夹,逐层向下叠加,越往下越是在上层规则上补充细节。于是文件夹结构就成了规则的作用范围,文件夹即规则之锚。CLAUDE.md 专属 Claude,AGENTS.md 则是面向 Codex、Gemini 等其他智能体的通用标准;用符号链接合为一体,无论用哪个 AI 都适用同一套规则。
部署:把 git commit 直接当作部署产物
像 Vercel 这样的部署服务会连接一个 git 仓库。向该仓库 push 时,webhook(自动信号)唤醒服务,服务拉取该 commit 时刻的仓库,从根目录构建,再放到互联网上。
这里部署没有重新造轮子。git commit 已经完美定义了'代码的某个特定状态',于是把该 commit 直接复用为部署单位(产物)。每次部署都会打上 commit 哈希(SHA),回滚就是重新部署旧的 commit。一次 push 同时完成'保存版本 + 应用规则 + 反映到网页'的原因正在于此。
用五个维度归纳
把五个维度并排放置,就能一眼看清一个文件夹为何同时担这么多角色。它们通过一个事实连成一体:每一项都需要有边界的文件集合,而文件夹提供了这个边界。
| 维度 | 单位 | 锚点 | 为什么必须是文件夹 |
|---|---|---|---|
| 文件系统 | 树的封闭分支 | 文件夹根 | 文件夹 = 有边界的 subtree,一切上层概念的立足基座。 |
| git 版本控制 | 仓库(repo) | .git/(每根一个) | commit = 根目录下整个工作树的原子快照;路径全部相对根目录。 |
| harness(AI 规则) | 规则作用域 | CLAUDE.md · AGENTS.md | AI 会话开始时自动加载根目录文档;全局→根→子文件夹,累加。 |
| 会话(cwd) | AI 工作范围 | Claude 启动位置 | 搜索、权限、影响范围以 cwd 为准。文件夹即作用域与安全边界。 |
| 部署(Vercel 等) | 部署产物 | commit SHA | push 即构建/部署该 commit 时刻的仓库——git commit 直接复用为部署单位。 |
推荐的文件夹结构
所以实务标准很简单:项目并排放置而非嵌套,在每个项目根放一个 .git 与一个 CLAUDE.md,并从该根开始 AI 工作。把三样东西对齐到一个文件夹——git 根 = 规则文档位置 = AI 起点。
反过来,在一个项目文件夹里再嵌套另一个独立项目,会形成仓库套仓库,使 git 命令搞不清针对哪个仓库,也容易在错误的深度启动 AI,从而错乱规则与搜索范围。
workspace/
├── project-A/ <- 在这里启动 Claude
│ ├── .git/ (版本控制边界)
│ ├── CLAUDE.md (harness 规则)
│ └── src/
├── project-B/ <- 在这里启动 Claude
│ ├── .git/
│ ├── CLAUDE.md
│ └── src/
└── project-C/
├── .git/
├── CLAUDE.md
└── src/workspace/
└── project-A/
├── .git/
├── CLAUDE.md
└── project-B/ <- 仓库套仓库 · cwd 混乱
├── .git/
└── CLAUDE.md那么文件夹里套文件夹绝对不行吗
别误解:在'一个项目内部'划分文件夹是正常且推荐的。像 src/、docs/、tests/ 这样把文件分门别类,尽管放手去做。问题在于把'相互独立的项目(即各自的边界)'嵌套进彼此。
看似例外的三种情况也不破坏原则。monorepo 只是在一个仓库(一个文件夹)内在逻辑上划分多个包,仓库仍是一个——部署通过 Root Directory 设置分别指向各个包。submodule 只是从仓库内部'引用'另一个仓库,各自保留独立的 .git。worktree 只是把同一段历史同时铺展到多个文件夹,但源头 .git 只有一个。这三者都是在'一个仓库 = 一个文件夹'原则之上组合,而非打破它。
一句话总结
一切都需要有边界的文件集合,而文件夹最自然地提供了这个边界。于是 git、harness、部署都搭在一个文件夹上,而不重新造轮子。在 Vibe Coding 中一旦理解'文件夹 = 边界',工具为何这样运作便迎刃而解。
