fork-discipline
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFork Discipline
Fork Discipline规范
Audit the core/client boundary in multi-client codebases. Every multi-client project should have a clean separation between shared platform code (core) and per-deployment code (client). This skill finds where that boundary is blurred and shows you how to fix it.
审计多客户端代码库中的core/client边界。每个多客户端项目都应在共享平台代码(core)和每个部署专属代码(client)之间保持清晰的分离。本技能可检测边界模糊的位置,并提供修复方案。
The Principle
核心原则
project/
src/ ← CORE: shared platform code. Never modified per client.
config/ ← DEFAULTS: base config, feature flags, sensible defaults.
clients/
client-name/ ← CLIENT: everything that varies per deployment.
config ← overrides merged over defaults
content ← seed data, KB articles, templates
schema ← domain tables, migrations (numbered 0100+)
custom/ ← bespoke features (routes, pages, tools)The fork test: Before modifying any file, ask "is this core or client?" If you can't tell, the boundary isn't clean enough.
project/
src/ ← CORE: 共享平台代码,绝不能针对单个客户端修改。
config/ ← DEFAULTS: 基础配置、功能开关、合理默认值。
clients/
client-name/ ← CLIENT: 每个部署的专属内容。
config ← 覆盖默认配置的自定义设置
content ← 种子数据、知识库文章、模板
schema ← 领域表、迁移文件(编号0100+)
custom/ ← 定制功能(路由、页面、工具)分支测试(Fork Test):修改任何文件前,先问自己“这属于core还是client?”如果无法判断,说明边界不够清晰。
When to Use
适用场景
- Before adding a second or third client to an existing project
- After a project has grown organically and the boundaries are fuzzy
- When you notice checks creeping into shared code
if (client === 'acme') - Before a major refactor to understand what's actually shared vs specific
- When onboarding a new developer who needs to understand the architecture
- Periodic health check on multi-client projects
- 在现有项目中添加第二个或第三个客户端之前
- 项目自然增长后边界变得模糊时
- 注意到这类检查逐渐出现在共享代码中时
if (client === 'acme') - 大型重构前,用于理清哪些是共享代码、哪些是客户端特定代码时
- 新开发人员入职,需要理解项目架构时
- 多客户端项目的定期健康检查
Modes
模式说明
| Mode | Trigger | What it produces |
|---|---|---|
| audit | "fork discipline", "check the boundary" | Boundary map + violation report |
| document | "write FORK.md", "document the boundary" | FORK.md file for the project |
| refactor | "clean up the fork", "enforce the boundary" | Refactoring plan + migration scripts |
Default: audit
| 模式 | 触发词 | 生成内容 |
|---|---|---|
| 审计 | "fork discipline", "check the boundary" | 边界映射图 + 违规报告 |
| 文档生成 | "write FORK.md", "document the boundary" | 项目的FORK.md文件 |
| 重构 | "clean up the fork", "enforce the boundary" | 重构计划 + 迁移脚本 |
默认模式:审计
Audit Mode
审计模式
Step 1: Detect Project Type
步骤1:检测项目类型
Determine if this is a multi-client project and what pattern it uses:
| Signal | Pattern |
|---|---|
| Explicit multi-client |
| Multiple config files with client names | Config-driven multi-client |
| Monorepo multi-client |
Environment variables like | Runtime multi-client |
| Only one deployment, no client dirs | Single-client (may be heading multi-client) |
If single-client: check if the project CLAUDE.md or codebase suggests it will become multi-client. If so, audit for readiness. If genuinely single-client forever, this skill isn't needed.
判断当前是否为多客户端项目,以及采用的模式:
| 特征 | 模式 |
|---|---|
存在 | 显式多客户端 |
| 存在多个带客户端名称的配置文件 | 配置驱动型多客户端 |
| 单体仓库多客户端 |
存在 | 运行时多客户端 |
| 仅一个部署实例,无客户端目录 | 单客户端(可能向多客户端演进) |
如果是单客户端:检查项目的CLAUDE.md或代码库是否有向多客户端演进的迹象。如果有,审计其就绪情况;如果确定永远是单客户端,则无需本技能。
Step 2: Map the Boundary
步骤2:绘制边界映射图
Build a boundary map by scanning the codebase:
CORE (shared by all clients):
src/server/ → API routes, middleware, auth
src/client/ → React components, hooks, pages
src/db/schema.ts → Shared database schema
migrations/0001-0050 → Core migrations
CLIENT (per-deployment):
clients/acme/config.ts → Client overrides
clients/acme/kb/ → Knowledge base articles
clients/acme/seed.sql → Seed data
migrations/0100+ → Client schema extensions
BLURRED (needs attention):
src/server/routes/acme-custom.ts → Client code in core!
src/config/defaults.ts line 47 → Hardcoded client domain通过扫描代码库构建边界映射图:
CORE(所有客户端共享):
src/server/ → API路由、中间件、认证
src/client/ → React组件、钩子、页面
src/db/schema.ts → 共享数据库 schema
migrations/0001-0050 → 核心迁移文件
CLIENT(每个部署专属):
clients/acme/config.ts → 客户端自定义配置
clients/acme/kb/ → 知识库文章
clients/acme/seed.sql → 种子数据
migrations/0100+ → 客户端 schema 扩展
BLURRED(需要关注):
src/server/routes/acme-custom.ts → 客户端代码混入核心!
src/config/defaults.ts 第47行 → 硬编码客户端域名Step 3: Find Violations
步骤3:查找违规问题
Scan for these specific anti-patterns:
扫描以下特定的反模式:
Client Names in Core Code
核心代码中出现客户端名称
bash
undefinedbash
undefinedSearch for hardcoded client identifiers in shared code
在共享代码中搜索硬编码的客户端标识符
grep -rn "acme|smith|client_name_here" src/ --include=".ts" --include=".tsx"
grep -rn "acme|smith|client_name_here" src/ --include=".ts" --include=".tsx"
Search for client-specific conditionals
搜索客户端特定的条件判断
grep -rn "if.client.===|switch.client|case.['"]acme" src/ --include=".ts" --include=".tsx"
grep -rn "if.client.===|switch.client|case.['"]acme" src/ --include=".ts" --include=".tsx"
Search for environment-based client checks in shared code
在共享代码中搜索基于环境变量的客户端检查
grep -rn "CLIENT_NAME|TENANT_ID|process.env.CLIENT" src/ --include=".ts" --include="*.tsx"
**Severity**: High. Every hardcoded client check in core code means the next client requires modifying shared code.grep -rn "CLIENT_NAME|TENANT_ID|process.env.CLIENT" src/ --include=".ts" --include="*.tsx"
**严重程度**:高。核心代码中每一处硬编码客户端检查,都意味着新增客户端时需要修改共享代码。Config Replacement Instead of Merge
配置文件替换而非合并
Check if client configs replace entire files or merge over defaults:
typescript
// BAD — client config is a complete replacement
// clients/acme/config.ts
export default {
theme: { primary: '#1E40AF' },
features: { emailOutbox: true },
// Missing all other defaults — they're lost
}
// GOOD — client config is a delta merged over defaults
// clients/acme/config.ts
export default {
theme: { primary: '#1E40AF' }, // Only overrides what's different
}
// config/defaults.ts has everything elseLook for: client config files that are suspiciously large (close to the size of the defaults file), or client configs that define fields the defaults already handle.
Severity: Medium. Stale client configs miss new defaults and features.
检查客户端配置是替换整个文件还是在默认配置基础上合并:
typescript
// 错误示例 — 客户端配置完全替换默认配置
// clients/acme/config.ts
export default {
theme: { primary: '#1E40AF' },
features: { emailOutbox: true },
// 缺失所有其他默认配置 — 这些配置会丢失
}
// 正确示例 — 客户端配置是在默认配置基础上的增量修改
// clients/acme/config.ts
export default {
theme: { primary: '#1E40AF' }, // 仅覆盖需要修改的内容
}
// config/defaults.ts包含所有其他默认配置检查点:客户端配置文件是否过大(接近默认配置文件的大小),或者客户端配置中包含默认配置已有的字段。
严重程度:中等。过时的客户端配置会错过新的默认配置和功能。
Scattered Client Code
客户端代码分散
Check if client-specific code lives outside the client directory:
bash
undefined检查客户端特定代码是否存在于客户端目录之外:
bash
undefinedFiles with client names in their path but inside src/
查找src/目录中路径包含客户端名称的文件
find src/ -name "acme" -o -name "smith" -o -name "client-name"
find src/ -name "acme" -o -name "smith" -o -name "client-name"
Routes or pages that serve a single client
查找仅服务于单个客户端的路由或页面
grep -rn "// only for|// acme only|// client-specific" src/ --include=".ts" --include=".tsx"
**Severity**: High. Client code in `src/` means core is not truly shared.grep -rn "// only for|// acme only|// client-specific" src/ --include=".ts" --include=".tsx"
**严重程度**:高。`src/`目录中的客户端代码意味着核心代码并非真正共享。Missing Extension Points
缺失扩展点
Check if core has mechanisms for client customisation without modification:
| Extension point | How to check | What it enables |
|---|---|---|
| Config merge | Does | Client overrides without replacing |
| Dynamic imports | Does core look for | Client-specific routes/pages |
| Feature flags | Are features toggled by config, not code? | Enable/disable per client |
| Theme tokens | Are colours/styles in variables, not hardcoded? | Visual customisation |
| Content injection | Can clients provide seed data, templates? | Per-client content |
| Hook/event system | Can clients extend behaviour without patching? | Custom business logic |
Severity: Medium. Missing extension points force client code into core.
检查核心代码是否提供了无需修改即可实现客户端定制的机制:
| 扩展点 | 检查方式 | 实现能力 |
|---|---|---|
| 配置合并 | | 客户端无需替换即可覆盖配置 |
| 动态导入 | 核心代码是否会查找 | 加载客户端特定路由/页面 |
| 功能开关 | 功能是否通过配置而非代码控制? | 按客户端启用/禁用功能 |
| 主题变量 | 颜色/样式是否使用变量而非硬编码? | 视觉定制 |
| 内容注入 | 客户端是否可提供种子数据、模板? | 按客户端定制内容 |
| 钩子/事件系统 | 客户端是否可扩展行为而无需修改核心代码? | 定制业务逻辑 |
严重程度:中等。缺失扩展点会迫使客户端代码混入核心代码。
Migration Number Conflicts
迁移文件编号冲突
bash
undefinedbash
undefinedList all migration files with their numbers
列出所有迁移文件及其编号
ls migrations/ | sort | head -20
ls migrations/ | sort | head -20
Check if client migrations are in the reserved ranges
检查客户端迁移文件是否在预留范围内
Core: 0001-0099, Client domain: 0100-0199, Client custom: 0200+
核心:0001-0099,客户端领域:0100-0199,客户端定制:0200+
**Severity**: Low until it causes a conflict, then Critical.
**严重程度**:低,直到引发冲突后变为极高。Feature Flags vs Client Checks
功能开关 vs 客户端检查
typescript
// BAD — client name check
if (clientName === 'acme') {
showEmailOutbox = true;
}
// GOOD — feature flag in config
if (config.features.emailOutbox) {
showEmailOutbox = true;
}Search for patterns where behaviour branches on client identity instead of configuration.
typescript
// 错误示例 — 检查客户端名称
if (clientName === 'acme') {
showEmailOutbox = true;
}
// 正确示例 — 配置中的功能开关
if (config.features.emailOutbox) {
showEmailOutbox = true;
}搜索行为基于客户端身份而非配置分支的模式。
Step 4: Produce the Report
步骤4:生成报告
Write to :
.jez/artifacts/fork-discipline-audit.mdmarkdown
undefined写入:
.jez/artifacts/fork-discipline-audit.mdmarkdown
undefinedFork Discipline Audit: [Project Name]
Fork Discipline审计报告: [项目名称]
Date: YYYY-MM-DD
Pattern: [explicit multi-client / config-driven / monorepo / single-heading-multi]
Clients: [list of client deployments]
日期: YYYY-MM-DD
模式: [显式多客户端 / 配置驱动型 / 单体仓库 / 单客户端向多客户端演进]
客户端列表: [所有部署的客户端]
Boundary Map
边界映射图
Core (shared)
核心(共享)
| Path | Purpose | Clean? |
|---|---|---|
| src/server/ | API routes | Yes / No — [issue] |
| 路径 | 用途 | 是否合规 |
|---|---|---|
| src/server/ | API路由 | 是 / 否 — [问题描述] |
Client (per-deployment)
客户端(每个部署专属)
| Client | Config | Content | Schema | Custom |
|---|---|---|---|---|
| acme | config.ts | kb/ | 0100-0120 | custom/routes/ |
| 客户端 | 配置 | 内容 | Schema | 定制功能 |
|---|---|---|---|---|
| acme | config.ts | kb/ | 0100-0120 | custom/routes/ |
Blurred (needs attention)
模糊边界(需要关注)
| Path | Problem | Suggested fix |
|---|---|---|
| src/routes/acme-custom.ts | Client code in core | Move to clients/acme/custom/ |
| 路径 | 问题 | 建议修复方案 |
|---|---|---|
| src/routes/acme-custom.ts | 客户端代码混入核心 | 迁移至clients/acme/custom/ |
Violations
违规问题
High Severity
高严重程度
[List with file:line, description, fix]
[列出文件:行号、问题描述、修复方案]
Medium Severity
中严重程度
[List with file:line, description, fix]
[列出文件:行号、问题描述、修复方案]
Low Severity
低严重程度
[List]
[列表]
Extension Points
扩展点检查
| Point | Present? | Notes |
|---|---|---|
| Config merge | Yes/No | |
| Dynamic imports | Yes/No | |
| Feature flags | Yes/No |
| 扩展点 | 是否存在 | 说明 |
|---|---|---|
| 配置合并 | 是/否 | |
| 动态导入 | 是/否 | |
| 功能开关 | 是/否 |
Health Score
健康评分
[1-10] — [explanation]
[1-10] — [评分说明]
Top 3 Recommendations
三大推荐修复项
- [Highest impact fix]
- [Second priority]
- [Third priority]
---- [最高优先级修复]
- [次优先级修复]
- [第三优先级修复]
---Document Mode
文档生成模式
Generate a for the project root that documents the boundary:
FORK.mdmarkdown
undefined在项目根目录生成文档,明确边界规则:
FORK.mdmarkdown
undefinedFork Discipline
Fork Discipline规范
Architecture
架构说明
This project serves multiple clients from a shared codebase.
本项目通过共享代码库为多个客户端提供服务。
What's Core (don't modify per client)
核心代码(不得针对单个客户端修改)
[List of directories and their purpose]
[列出目录及其用途]
What's Client (varies per deployment)
客户端代码(按部署定制)
[Client directory structure with explanation]
[客户端目录结构及说明]
How to Add a New Client
新增客户端步骤
- Copy to
clients/_template/clients/new-client/ - Edit with client overrides
config.ts - Add seed data to
content/ - Create migrations numbered 0100+
- Deploy with
CLIENT=new-client wrangler deploy
- 复制至
clients/_template/clients/new-client/ - 编辑添加客户端自定义配置
config.ts - 向添加种子数据
content/ - 创建编号0100+的迁移文件
- 执行完成部署
CLIENT=new-client wrangler deploy
The Fork Test
分支测试(Fork Test)
Before modifying any file: is this core or client?
- Core → change in , all clients benefit
src/ - Client → change in , no other client affected
clients/name/ - Can't tell → the boundary needs fixing first
修改任何文件前先问:这属于核心还是客户端?
- 核心 → 在目录修改,所有客户端受益
src/ - 客户端 → 在目录修改,不影响其他客户端
clients/name/ - 无法判断 → 先修复边界问题
Migration Numbering
迁移文件编号规则
| Range | Owner |
|---|---|
| 0001-0099 | Core platform |
| 0100-0199 | Client domain schema |
| 0200+ | Client custom features |
| 范围 | 归属 |
|---|---|
| 0001-0099 | 核心平台 |
| 0100-0199 | 客户端领域schema |
| 0200+ | 客户端定制功能 |
Config Merge Pattern
配置合并模式
Client configs are shallow-merged over defaults:
[Show the actual merge code from the project]
---客户端配置会浅合并到默认配置之上:
[展示项目中实际的合并代码]
---Refactor Mode
重构模式
After an audit, generate the concrete steps to enforce the boundary:
审计完成后,生成明确的边界规范执行步骤:
1. Move Client Code Out of Core
1. 将客户端代码移出核心目录
For each violation where client code lives in :
src/bash
undefined针对每一处客户端代码混入核心目录的违规问题:
bash
undefinedCreate client directory if it doesn't exist
若客户端目录不存在则创建
mkdir -p clients/acme/custom/routes
mkdir -p clients/acme/custom/routes
Move the file
移动文件
git mv src/routes/acme-custom.ts clients/acme/custom/routes/
git mv src/routes/acme-custom.ts clients/acme/custom/routes/
Update imports in core to use dynamic discovery
更新核心代码中的导入逻辑为动态发现
undefinedundefined2. Replace Client Checks with Feature Flags
2. 用功能开关替代客户端检查
For each in core:
if (client === ...)typescript
// Before (in src/)
if (clientName === 'acme') {
app.route('/email-outbox', emailRoutes);
}
// After (in src/) — feature flag
if (config.features.emailOutbox) {
app.route('/email-outbox', emailRoutes);
}
// After (in clients/acme/config.ts) — client enables it
export default {
features: { emailOutbox: true }
}针对核心代码中每一处:
if (client === ...)typescript
// 修改前(在src/目录)
if (clientName === 'acme') {
app.route('/email-outbox', emailRoutes);
}
// 修改后(在src/目录)— 使用功能开关
if (config.features.emailOutbox) {
app.route('/email-outbox', emailRoutes);
}
// 修改后(在clients/acme/config.ts)— 客户端启用该功能
export default {
features: { emailOutbox: true }
}3. Implement Config Merge
3. 实现配置合并逻辑
If the project replaces configs instead of merging:
typescript
// config/resolve.ts
import defaults from './defaults';
export function resolveConfig(clientConfig: Partial<Config>): Config {
return {
...defaults,
...clientConfig,
features: { ...defaults.features, ...clientConfig.features },
theme: { ...defaults.theme, ...clientConfig.theme },
};
}若项目当前采用配置替换而非合并:
typescript
// config/resolve.ts
import defaults from './defaults';
export function resolveConfig(clientConfig: Partial<Config>): Config {
return {
...defaults,
...clientConfig,
features: { ...defaults.features, ...clientConfig.features },
theme: { ...defaults.theme, ...clientConfig.theme },
};
}4. Add Extension Point for Custom Routes
4. 添加定制路由扩展点
If clients need custom routes but currently modify core:
typescript
// src/server/index.ts — auto-discover client routes
const clientRoutes = await import(`../../clients/${clientName}/custom/routes`)
.catch(() => null);
if (clientRoutes?.default) {
app.route('/custom', clientRoutes.default);
}若客户端需要定制路由但当前需修改核心代码:
typescript
// src/server/index.ts — 自动发现客户端路由
const clientRoutes = await import(`../../clients/${clientName}/custom/routes`)
.catch(() => null);
if (clientRoutes?.default) {
app.route('/custom', clientRoutes.default);
}5. Generate the Refactoring Script
5. 生成重构脚本
Write a script to that:
.jez/scripts/fork-refactor.sh- Creates the client directory structure
- Moves identified files
- Updates import paths
- Generates the FORK.md
将脚本写入,包含:
.jez/scripts/fork-refactor.sh- 创建客户端目录结构
- 移动已识别的文件
- 更新导入路径
- 生成FORK.md文档
The Right Time to Run This
最佳执行时机
| Client count | What to do |
|---|---|
| 1 | Don't refactor. Just document the boundary (FORK.md) so you know where it is. |
| 2 | Run the audit. Fix high-severity violations. Start the config merge pattern. |
| 3+ | Full refactor mode. The boundary must be clean — you now have proof of what varies. |
Rule 5 from the discipline: Don't abstract until client #3. With 1 client you're guessing. With 2 you're pattern-matching. With 3+ you know what actually varies.
| 客户端数量 | 执行操作 |
|---|---|
| 1 | 无需重构。只需生成FORK.md文档明确边界即可。 |
| 2 | 运行审计。修复高严重程度违规问题。开始采用配置合并模式。 |
| 3+ | 启用完整重构模式。边界必须清晰 — 此时你已明确哪些内容是定制化的。 |
规范第五条:直到第3个客户端出现再进行抽象。1个客户端时你只是猜测,2个客户端时你在匹配模式,3个及以上客户端时你才真正了解哪些内容是定制化的。
Tips
小贴士
- Run this before adding a new client, not after
- The boundary map is the most valuable output — print it, put it on the wall
- Config merge is the single highest-ROI refactor — do it first
- Feature flags are better than even with one client
if (client) - If you find yourself saying "this is mostly the same for all clients except..." that's a feature flag, not a fork
- The FORK.md is for the team, not just for Claude — write it like a human will read it
- 在添加新客户端前运行本技能,而非之后
- 边界映射图是最有价值的输出 — 打印出来贴在墙上
- 配置合并是投资回报率最高的重构操作 — 优先执行
- 即使只有一个客户端,功能开关也比检查更好
if (client) - 当你发现自己说“这对所有客户端基本相同,除了...”时,这应该是一个功能开关,而非分支
- FORK.md是写给团队看的,不只是给Claude — 用人类易懂的语言撰写