Fork Discipline 是一个用于审查和治理多客户端项目中“核心层/客户端层”边界的技能。多客户端项目应当在共享的平台代码(core)与每个部署专属的代码(client)之间保持清晰分离。该技能用于找出边界模糊之处,并给出修复方法。
核心原则
项目结构约定如下:
src/:CORE,共享平台代码,从不针对单个客户端修改。config/:DEFAULTS,基础配置、特性开关、合理的默认值。clients/:CLIENT,每个部署中一切可变的内容,包含:client-name/config:覆盖默认值的配置。client-name/content:种子数据、知识库文章、模板。client-name/schema:领域表、迁移(编号从 0100 起)。client-name/custom/:定制功能(路由、页面、工具)。
分叉测试:在修改任何文件之前,先问“这是核心还是客户端?”如果无法判断,说明边界还不够清晰。
使用时机
- 在向现有项目添加第二个或第三个客户端之前。
- 项目自然生长后边界变得模糊时。
- 当发现
if (client === 'acme')之类的判断渗入共享代码时。 - 在重大重构之前,需要理解哪些是真正共享、哪些是专属的。
- 新开发者入职需要理解架构时。
- 对多客户端项目进行定期健康检查。
模式
| 模式 | 触发语 | 产出 |
|---|---|---|
| audit | “fork discipline”、“check the boundary” | 边界地图 + 违规报告 |
| document | “write FORK.md”、“document the boundary” | 项目的 FORK.md 文件 |
| refactor | “clean up the fork”、“enforce the boundary” | 重构计划 + 迁移脚本 |
默认模式:audit。
Audit 模式
第 1 步:检测项目类型
判断是否为多客户端项目以及使用哪种模式:
| 信号 | 模式 |
|---|---|
clients/ 或 tenants/ 目录 |
显式多客户端 |
| 多个以客户端命名的配置文件 | 配置驱动多客户端 |
packages/ 中包含共享包与每客户端包 |
Monorepo 多客户端 |
环境变量如 CLIENT_NAME 或 TENANT_ID |
运行时多客户端 |
| 只有单个部署,无客户端目录 | 单客户端(可能正走向多客户端) |
若为单客户端:检查项目 CLAUDE.md 或代码库是否表明将发展为多客户端。若是,则进行就绪度审查;若确定永久单客户端,则不需要此技能。
第 2 步:绘制边界地图
扫描代码库建立边界地图,将其划分为:CORE(所有客户端共享)、CLIENT(每次部署专属)、BLURRED(需要关注)。例如 src/server/routes/acme-custom.ts 属于客户端代码混入核心,src/config/defaults.ts 中硬编码客户端域名则属于边界模糊。
第 3 步:查找违规
扫描以下反模式:
-
核心代码中的客户端名称:硬编码的客户端标识、客户端特定条件分支、共享代码中基于环境变量的客户端检查。严重度:高。核心代码中每处硬编码客户端检查,都意味着下一个客户端需要修改共享代码。
-
配置替换而非合并:客户端配置替换整个文件而非在默认值之上合并。表现为客户端配置文件大得可疑(接近默认文件大小),或定义了默认值已处理的字段。严重度:中。过时的客户端配置会漏掉新的默认值与功能。
-
散落的客户端代码:客户端专属代码位于客户端目录之外,例如路径含客户端名称却位于
src/下的文件、只服务单个客户端的路由或页面。严重度:高。 -
缺失扩展点:检查核心是否有无需修改即可定制客户端的机制,包括配置合并、动态导入、特性开关、主题令牌、内容注入、钩子/事件系统。严重度:中。缺少扩展点会迫使客户端代码进入核心。
-
迁移编号冲突:约定核心为 0001-0099,客户端领域为 0100-0199,客户端自定义为 0200+。严重度:低,直到引发冲突后变为严重。
-
特性开关 vs 客户端判断:查找行为依据客户端身份而非配置进行分支的模式。
第 4 步:生成报告
写入 .jez/artifacts/fork-discipline-audit.md,包含:项目信息(日期、模式、客户端列表)、边界地图(核心、客户端、模糊三部分表格)、违规(按高/中/低严重度分级,含 file:line、描述、修复建议)、扩展点检查表、健康评分(1-10 及说明)、前 3 条建议。
Document 模式
在项目根目录生成 FORK.md,记录边界,包含:架构说明、什么是核心(不针对客户端修改)、什么是客户端(每次部署可变)、如何添加新客户端、分叉测试、迁移编号范围表、配置合并模式。
Refactor 模式
审计之后生成强制边界的步骤:
- 将客户端代码移出核心:为每个违规创建客户端目录,用
git mv移动文件,更新核心导入以使用动态发现。 - 用特性开关替换客户端判断:将
if (clientName === 'acme')改为if (config.features.emailOutbox),并在客户端配置中启用该开关。 - 实现配置合并:若项目替换配置而非合并,提供
resolveConfig合并函数。 - 为自定义路由添加入口点:核心自动发现客户端路由。
- 生成重构脚本到
.jez/scripts/fork-refactor.sh,执行创建目录结构、移动文件、更新导入路径、生成 FORK.md。
最佳运行时机
| 客户端数量 | 做法 |
|---|---|
| 1 | 不要重构,仅记录边界(FORK.md) |
| 2 | 运行审计,修复高严重度违规,开始配置合并模式 |
| 3+ | 完整重构模式,边界必须清晰 |
纪律第 5 条:不要在第三个客户端之前抽象。1 个客户端时是猜测,2 个时是模式匹配,3+ 时才真正知道哪些可变。
提示
- 在添加新客户端之前运行,而非之后。
- 配置合并是回报最高的重构,优先做。
- 特性开关胜于
if (client),即使只有一个客户端。“这基本一样只是……”等于特性开关,而非分叉。 - FORK.md 是给团队看的,不只是给 Claude 看——按人类会阅读的方式撰写。