重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
npx skills add https://github.com/jezweb/claude-skills --skill fork-discipline在多客户端代码库中审计核心/客户端边界。每个多客户端项目都应在共享平台代码(核心)和每个部署的代码(客户端)之间有清晰的分离。此技能旨在发现边界模糊之处,并展示如何修复。
project/
src/ ← 核心:共享平台代码。绝不因客户端而修改。
config/ ← 默认配置:基础配置、功能开关、合理的默认值。
clients/
client-name/ ← 客户端:每个部署中所有可变的部分。
config ← 覆盖默认配置的配置
content ← 种子数据、知识库文章、模板
schema ← 领域表、迁移(编号 0100+)
custom/ ← 定制功能(路由、页面、工具)
分支测试:在修改任何文件之前,先问“这是核心还是客户端?”如果你无法判断,说明边界不够清晰。
if (client === 'acme') 这类检查逐渐渗入共享代码时| 模式 | 触发词 | 产出内容 |
|---|---|---|
| 审计 | "fork discipline", "check the boundary" |
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| 边界图 + 违规报告 |
| 文档化 | "write FORK.md", "document the boundary" | 项目的 FORK.md 文件 |
| 重构 | "clean up the fork", "enforce the boundary" | 重构计划 + 迁移脚本 |
默认模式:审计
确定这是否是一个多客户端项目以及它使用的模式:
| 信号 | 模式 |
|---|---|
clients/ 或 tenants/ 目录 | 显式多客户端 |
| 多个包含客户端名称的配置文件 | 配置驱动型多客户端 |
包含共享包和每个客户端包的 packages/ | 单体仓库多客户端 |
类似 CLIENT_NAME 或 TENANT_ID 的环境变量 | 运行时多客户端 |
| 只有一个部署,没有客户端目录 | 单客户端(可能正在向多客户端发展) |
如果是单客户端:检查项目的 CLAUDE.md 或代码库是否暗示它将变为多客户端。如果是,则审计其准备情况。如果确实是永远的单客户端,则不需要此技能。
通过扫描代码库构建边界图:
CORE (所有客户端共享):
src/server/ → API 路由、中间件、认证
src/client/ → React 组件、钩子、页面
src/db/schema.ts → 共享数据库模式
migrations/0001-0050 → 核心迁移
CLIENT (每个部署):
clients/acme/config.ts → 客户端覆盖配置
clients/acme/kb/ → 知识库文章
clients/acme/seed.sql → 种子数据
migrations/0100+ → 客户端模式扩展
BLURRED (需要注意):
src/server/routes/acme-custom.ts → 客户端代码在核心中!
src/config/defaults.ts 第 47 行 → 硬编码的客户端域名
扫描以下特定的反模式:
# 在共享代码中搜索硬编码的客户端标识符
grep -rn "acme\|smith\|client_name_here" src/ --include="*.ts" --include="*.tsx"
# 搜索特定客户端的条件语句
grep -rn "if.*client.*===\|switch.*client\|case.*['\"]acme" src/ --include="*.ts" --include="*.tsx"
# 在共享代码中搜索基于环境的客户端检查
grep -rn "CLIENT_NAME\|TENANT_ID\|process.env.*CLIENT" src/ --include="*.ts" --include="*.tsx"
严重性:高。核心代码中的每一个硬编码客户端检查都意味着添加下一个客户端时需要修改共享代码。
检查客户端配置是替换整个文件还是覆盖默认配置:
// 错误 — 客户端配置是完全替换
// clients/acme/config.ts
export default {
theme: { primary: '#1E40AF' },
features: { emailOutbox: true },
// 缺少所有其他默认值 — 它们丢失了
}
// 正确 — 客户端配置是覆盖默认配置的增量
// clients/acme/config.ts
export default {
theme: { primary: '#1E40AF' }, // 只覆盖不同的部分
}
// config/defaults.ts 包含其他所有内容
查找:客户端配置文件异常大(接近默认文件大小),或者客户端配置定义了默认配置已经处理的字段。
严重性:中。过时的客户端配置会错过新的默认值和功能。
检查客户端特定代码是否存在于客户端目录之外:
# 路径中包含客户端名称但位于 src/ 内的文件
find src/ -name "*acme*" -o -name "*smith*" -o -name "*client-name*"
# 服务于单个客户端的路由或页面
grep -rn "// only for\|// acme only\|// client-specific" src/ --include="*.ts" --include="*.tsx"
严重性:高。src/ 中的客户端代码意味着核心并非真正共享。
检查核心是否具有无需修改即可实现客户端自定义的机制:
| 扩展点 | 如何检查 | 它支持什么 |
|---|---|---|
| 配置合并 | config/ 是否有合并函数? | 客户端覆盖配置而无需替换 |
| 动态导入 | 核心是否查找 clients/{name}/custom/? | 客户端特定路由/页面 |
| 功能开关 | 功能是否由配置而非代码切换? | 按客户端启用/禁用 |
| 主题令牌 | 颜色/样式是否在变量中而非硬编码? | 视觉自定义 |
| 内容注入 | 客户端能否提供种子数据、模板? | 每个客户端的内容 |
| 钩子/事件系统 | 客户端能否在不打补丁的情况下扩展行为? | 自定义业务逻辑 |
严重性:中。缺少扩展点会迫使客户端代码进入核心。
# 列出所有迁移文件及其编号
ls migrations/ | sort | head -20
# 检查客户端迁移是否在保留范围内
# 核心:0001-0099,客户端领域:0100-0199,客户端自定义:0200+
严重性:低,直到发生冲突时变为严重。
// 错误 — 客户端名称检查
if (clientName === 'acme') {
showEmailOutbox = true;
}
// 正确 — 配置中的功能开关
if (config.features.emailOutbox) {
showEmailOutbox = true;
}
搜索行为基于客户端身份而非配置进行分支的模式。
写入 .jez/artifacts/fork-discipline-audit.md:
# 分支规范审计:[项目名称]
**日期**: YYYY-MM-DD
**模式**: [显式多客户端 / 配置驱动型 / 单体仓库 / 单客户端-向多客户端发展]
**客户端**: [客户端部署列表]
## 边界图
### 核心(共享)
| 路径 | 用途 | 是否清晰? |
|------|---------|--------|
| src/server/ | API 路由 | 是 / 否 — [问题] |
### 客户端(每个部署)
| 客户端 | 配置 | 内容 | 模式 | 自定义 |
|--------|--------|---------|--------|--------|
| acme | config.ts | kb/ | 0100-0120 | custom/routes/ |
### 模糊(需要注意)
| 路径 | 问题 | 建议修复方案 |
|------|---------|--------------|
| src/routes/acme-custom.ts | 核心中的客户端代码 | 移动到 clients/acme/custom/ |
## 违规情况
### 高严重性
[列出文件:行号、描述、修复方案]
### 中严重性
[列出文件:行号、描述、修复方案]
### 低严重性
[列出]
## 扩展点
| 点 | 是否存在? | 备注 |
|-------|----------|-------|
| 配置合并 | 是/否 | |
| 动态导入 | 是/否 | |
| 功能开关 | 是/否 | |
## 健康评分
[1-10] — [解释]
## 前 3 条建议
1. [影响最大的修复]
2. [第二优先级]
3. [第三优先级]
在项目根目录生成一个 FORK.md 文件来记录边界:
# 分支规范
## 架构
本项目从一个共享代码库为多个客户端提供服务。
### 什么是核心(不要因客户端而修改)
[目录列表及其用途]
### 什么是客户端(每个部署不同)
[客户端目录结构及解释]
### 如何添加新客户端
1. 复制 `clients/_template/` 到 `clients/new-client/`
2. 编辑 `config.ts` 以包含客户端覆盖配置
3. 将种子数据添加到 `content/`
4. 创建编号为 0100+ 的迁移
5. 使用 `CLIENT=new-client wrangler deploy` 进行部署
### 分支测试
在修改任何文件之前:这是核心还是客户端?
- 核心 → 在 `src/` 中更改,所有客户端受益
- 客户端 → 在 `clients/name/` 中更改,不影响其他客户端
- 无法判断 → 边界需要先修复
### 迁移编号规则
| 范围 | 所有者 |
|-------|-------|
| 0001-0099 | 核心平台 |
| 0100-0199 | 客户端领域模式 |
| 0200+ | 客户端自定义功能 |
### 配置合并模式
客户端配置是浅层合并覆盖默认配置:
[展示项目中实际的合并代码]
审计之后,生成强制执行边界的具体步骤:
对于每个客户端代码位于 src/ 中的违规情况:
# 如果客户端目录不存在则创建
mkdir -p clients/acme/custom/routes
# 移动文件
git mv src/routes/acme-custom.ts clients/acme/custom/routes/
# 更新核心中的导入以使用动态发现
对于核心中的每个 if (client === ...):
// 之前(在 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 }
}
如果项目替换配置而不是合并:
// 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 },
};
}
如果客户端需要自定义路由但目前修改了核心:
// src/server/index.ts — 自动发现客户端路由
const clientRoutes = await import(`../../clients/${clientName}/custom/routes`)
.catch(() => null);
if (clientRoutes?.default) {
app.route('/custom', clientRoutes.default);
}
将脚本写入 .jez/scripts/fork-refactor.sh,该脚本:
| 客户端数量 | 该做什么 |
|---|---|
| 1 | 不要重构。只需记录边界(FORK.md),以便你知道它在哪里。 |
| 2 | 运行审计。修复高严重性违规。开始配置合并模式。 |
| 3+ | 完全重构模式。边界必须清晰 — 你现在已经知道什么是可变的了。 |
规范中的第 5 条规则:在达到第 3 个客户端之前不要抽象。只有 1 个客户端时你是在猜测。有 2 个时你是在模式匹配。有 3 个以上时你才知道什么是真正可变的。
if (client) 更好每周安装次数
61
仓库
GitHub 星标数
650
首次出现
6 天前
安全审计
安装于
opencode60
kimi-cli59
gemini-cli59
amp59
cline59
github-copilot59
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.
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.
if (client === 'acme') checks creeping into shared code| 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
Determine if this is a multi-client project and what pattern it uses:
| Signal | Pattern |
|---|---|
clients/ or tenants/ directory | Explicit multi-client |
| Multiple config files with client names | Config-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 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.
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
Scan for these specific anti-patterns:
# Search for hardcoded client identifiers in shared code
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"
# 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.
Check if client configs replace entire files or merge over defaults:
// 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.
Check if client-specific code lives outside the client directory:
# Files with client names in their path but inside src/
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.
Check if core has mechanisms for client customisation without modification:
| Extension point | How to check | What it enables |
|---|---|---|
| Config merge | Does config/ have a merge function? | Client overrides without replacing |
| Dynamic imports | Does core look for clients/{name}/custom/? | 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.
# List all migration files with their numbers
ls migrations/ | sort | head -20
# Check if client migrations are in the reserved ranges
# Core: 0001-0099, Client domain: 0100-0199, Client custom: 0200+
Severity : Low until it causes a conflict, then Critical.
// 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.
Write to .jez/artifacts/fork-discipline-audit.md:
# Fork Discipline Audit: [Project Name]
**Date**: YYYY-MM-DD
**Pattern**: [explicit multi-client / config-driven / monorepo / single-heading-multi]
**Clients**: [list of client deployments]
## Boundary Map
### Core (shared)
| Path | Purpose | Clean? |
|------|---------|--------|
| src/server/ | API routes | Yes / No — [issue] |
### Client (per-deployment)
| Client | Config | Content | Schema | Custom |
|--------|--------|---------|--------|--------|
| 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/ |
## 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]
## Top 3 Recommendations
1. [Highest impact fix]
2. [Second priority]
3. [Third priority]
Generate a FORK.md for the project root that documents the boundary:
# 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`
### The 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
### Migration Numbering
| Range | Owner |
|-------|-------|
| 0001-0099 | Core platform |
| 0100-0199 | Client domain schema |
| 0200+ | Client custom features |
### Config Merge Pattern
Client configs are shallow-merged over defaults:
[Show the actual merge code from the project]
After an audit, generate the concrete steps to enforce the boundary:
For each violation where client code lives in src/:
# Create client directory if it doesn't exist
mkdir -p clients/acme/custom/routes
# Move the file
git mv src/routes/acme-custom.ts clients/acme/custom/routes/
# Update imports in core to use dynamic discovery
For each if (client === ...) in core:
// 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 the project replaces configs instead of merging:
// 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 },
};
}
If clients need custom routes but currently modify core:
// 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);
}
Write a script to .jez/scripts/fork-refactor.sh that:
| 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.
if (client) even with one clientWeekly Installs
61
Repository
GitHub Stars
650
First Seen
6 days ago
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode60
kimi-cli59
gemini-cli59
amp59
cline59
github-copilot59
代码安全审查清单:最佳实践与漏洞防范指南(含密钥管理、SQL注入防护)
1,700 周安装
临床试验语义搜索工具 - 使用Valyu API智能搜索ClinicalTrials.gov数据库
68 周安装
Helm 调试与故障排除指南:解决部署失败、模板错误和配置问题
68 周安装
SWOT PESTLE分析工具 - 战略环境分析框架,含波特五力模型,助力竞争定位与战略规划
68 周安装
Next.js专家技能:App Router、服务器组件、性能优化与SEO最佳实践指南
69 周安装
Wiki 架构师:AI 代码库文档生成工具,自动创建结构化维基和入门指南
69 周安装
YAML配置验证器 - DevOps自动化工具,支持CI/CD与容器化最佳实践
71 周安装