fork-discipline

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Fork 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
    if (client === 'acme')
    checks creeping into shared code
  • 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

模式说明

ModeTriggerWhat 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:
SignalPattern
clients/
or
tenants/
directory
Explicit multi-client
Multiple config files with client namesConfig-driven multi-client
packages/
with shared + per-client packages
Monorepo multi-client
Environment variables like
CLIENT_NAME
or
TENANT_ID
Runtime multi-client
Only one deployment, no client dirsSingle-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.
判断当前是否为多客户端项目,以及采用的模式:
特征模式
存在
clients/
tenants/
目录
显式多客户端
存在多个带客户端名称的配置文件配置驱动型多客户端
packages/
目录包含共享包和客户端专属包
单体仓库多客户端
存在
CLIENT_NAME
TENANT_ID
等环境变量
运行时多客户端
仅一个部署实例,无客户端目录单客户端(可能向多客户端演进)
如果是单客户端:检查项目的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
undefined
bash
undefined

Search 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 else
Look 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
undefined

Files 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 pointHow to checkWhat it enables
Config mergeDoes
config/
have a merge function?
Client overrides without replacing
Dynamic importsDoes core look for
clients/{name}/custom/
?
Client-specific routes/pages
Feature flagsAre features toggled by config, not code?Enable/disable per client
Theme tokensAre colours/styles in variables, not hardcoded?Visual customisation
Content injectionCan clients provide seed data, templates?Per-client content
Hook/event systemCan clients extend behaviour without patching?Custom business logic
Severity: Medium. Missing extension points force client code into core.
检查核心代码是否提供了无需修改即可实现客户端定制的机制:
扩展点检查方式实现能力
配置合并
config/
目录是否有合并函数?
客户端无需替换即可覆盖配置
动态导入核心代码是否会查找
clients/{name}/custom/
目录?
加载客户端特定路由/页面
功能开关功能是否通过配置而非代码控制?按客户端启用/禁用功能
主题变量颜色/样式是否使用变量而非硬编码?视觉定制
内容注入客户端是否可提供种子数据、模板?按客户端定制内容
钩子/事件系统客户端是否可扩展行为而无需修改核心代码?定制业务逻辑
严重程度:中等。缺失扩展点会迫使客户端代码混入核心代码。

Migration Number Conflicts

迁移文件编号冲突

bash
undefined
bash
undefined

List 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.md
:
markdown
undefined
写入
.jez/artifacts/fork-discipline-audit.md
markdown
undefined

Fork 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)

核心(共享)

PathPurposeClean?
src/server/API routesYes / No — [issue]
路径用途是否合规
src/server/API路由是 / 否 — [问题描述]

Client (per-deployment)

客户端(每个部署专属)

ClientConfigContentSchemaCustom
acmeconfig.tskb/0100-0120custom/routes/
客户端配置内容Schema定制功能
acmeconfig.tskb/0100-0120custom/routes/

Blurred (needs attention)

模糊边界(需要关注)

PathProblemSuggested fix
src/routes/acme-custom.tsClient code in coreMove 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

扩展点检查

PointPresent?Notes
Config mergeYes/No
Dynamic importsYes/No
Feature flagsYes/No
扩展点是否存在说明
配置合并是/否
动态导入是/否
功能开关是/否

Health Score

健康评分

[1-10] — [explanation]
[1-10] — [评分说明]

Top 3 Recommendations

三大推荐修复项

  1. [Highest impact fix]
  2. [Second priority]
  3. [Third priority]

---
  1. [最高优先级修复]
  2. [次优先级修复]
  3. [第三优先级修复]

---

Document Mode

文档生成模式

Generate a
FORK.md
for the project root that documents the boundary:
markdown
undefined
在项目根目录生成
FORK.md
文档,明确边界规则:
markdown
undefined

Fork 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

新增客户端步骤

  1. Copy
    clients/_template/
    to
    clients/new-client/
  2. Edit
    config.ts
    with client overrides
  3. Add seed data to
    content/
  4. Create migrations numbered 0100+
  5. Deploy with
    CLIENT=new-client wrangler deploy
  1. 复制
    clients/_template/
    clients/new-client/
  2. 编辑
    config.ts
    添加客户端自定义配置
  3. content/
    添加种子数据
  4. 创建编号0100+的迁移文件
  5. 执行
    CLIENT=new-client wrangler deploy
    完成部署

The Fork Test

分支测试(Fork Test)

Before modifying any file: is this core or client?
  • Core → change in
    src/
    , all clients benefit
  • Client → change in
    clients/name/
    , no other client affected
  • Can't tell → the boundary needs fixing first
修改任何文件前先问:这属于核心还是客户端?
  • 核心 → 在
    src/
    目录修改,所有客户端受益
  • 客户端 → 在
    clients/name/
    目录修改,不影响其他客户端
  • 无法判断 → 先修复边界问题

Migration Numbering

迁移文件编号规则

RangeOwner
0001-0099Core platform
0100-0199Client 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
undefined

Create 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

更新核心代码中的导入逻辑为动态发现

undefined
undefined

2. Replace Client Checks with Feature Flags

2. 用功能开关替代客户端检查

For each
if (client === ...)
in core:
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
.jez/scripts/fork-refactor.sh
that:
  • 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 countWhat to do
1Don't refactor. Just document the boundary (FORK.md) so you know where it is.
2Run 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
    if (client)
    even with one 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 — 用人类易懂的语言撰写