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.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "fork-discipline" with this command: npx skills add jezweb/claude-skills/jezweb-claude-skills-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.

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.

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

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

Audit Mode

Step 1: Detect Project Type

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.

Step 2: Map the Boundary

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

Step 3: Find Violations

Scan for these specific anti-patterns:

Client Names in Core Code

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.

Config Replacement Instead of Merge

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.

Scattered Client Code

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.

Missing Extension Points

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.

Migration Number Conflicts

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.

Feature Flags vs Client Checks

// 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.

Step 4: Produce the Report

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)

PathPurposeClean?
src/server/API routesYes / No — [issue]

Client (per-deployment)

ClientConfigContentSchemaCustom
acmeconfig.tskb/0100-0120custom/routes/

Blurred (needs attention)

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

PointPresent?Notes
Config mergeYes/No
Dynamic importsYes/No
Feature flagsYes/No

Health Score

[1-10] — [explanation]

Top 3 Recommendations

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

Document Mode

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

RangeOwner
0001-0099Core platform
0100-0199Client domain schema
0200+Client custom features

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

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

  1. Replace Client Checks with Feature Flags

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 } }

  1. Implement Config Merge

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 }, }; }

  1. Add Extension Point for Custom Routes

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); }

  1. Generate the Refactoring Script

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

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.

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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Security

ux-audit

No summary provided by upstream source.

Repository SourceNeeds Review
775-jezweb
Security

dependency-audit

No summary provided by upstream source.

Repository SourceNeeds Review
161-jezweb
General

tailwind-v4-shadcn

No summary provided by upstream source.

Repository SourceNeeds Review
2.7K-jezweb
General

tanstack-query

No summary provided by upstream source.

Repository SourceNeeds Review
2.5K-jezweb