Merge Rules
Merges .claude/rules/ from multiple projects into a unified portable rule set (.md + .examples.md). Promotes .local.md patterns that appear across a threshold of projects by converting them to Principles format. Merges .examples.md files alongside rule files.
Usage
/merge-rules # Merge using config file /merge-rules --config <path> # Merge using specified config file /merge-rules --dry-run # Show what would be merged without writing
Configuration
Config file search order:
- •
--config <path>argument - •
.claude/merge-rules.local.md(project-level) - •
~/.claude/merge-rules.local.md(user-level)
File format: YAML frontmatter only (no markdown body), same convention as extract-rules.local.md.
--- # Source projects (each must have extract-rules output) projects: - ~/projects/frontend-app - ~/projects/backend-api - ~/projects/shared-lib # Output directory (default: .claude/rules/) output_dir: .claude/rules/ # Rules directory within each project (default: .claude/rules/) # Corresponds to extract-rules' output_dir setting rules_dir: .claude/rules/ # Threshold for promoting .local.md patterns (default: 0.5 = majority) # Examples: 3 projects → 2/3 needed, 4 projects → 3/4, 5 projects → 3/5 promote_threshold: 0.5 # Report language (default: ja) language: ja ---
Processing Flow
Step 1: Load Configuration
- •Search for config file (see search order above)
- •If not found: Error "No config file found. Create
.claude/merge-rules.local.mdor specify with--config."
- •If not found: Error "No config file found. Create
- •Parse YAML frontmatter, apply defaults for omitted fields
- •
languageresolution order: Skill config → Claude Code settings (~/.claude/settings.json→languagefield) → defaultja
- •
- •Validate:
- •
projectsmust have at least 2 entries - •Each project path must exist and contain
rules_dir - •Error with clear message if validation fails
- •
Step 2: Collect Rule Files
For each project:
- •Find all
.md,.local.md, and.examples.mdfiles under{path}/{rules_dir}/(recursive) - •Categorize:
- •
languages/*.md→ portable principles (always merge). If the file also contains## Project-specific patterns(hybrid format fromsplit_output: false), treat patterns as promotion candidates (same as.local.md) - •
frameworks/*.md→ same as above - •
integrations/*.md→ same as above - •
languages/*.local.md→ promotion candidate - •
frameworks/*.local.md→ promotion candidate - •
integrations/*.local.md→ promotion candidate - •
languages/*.examples.md→ example file (merge with rules) - •
frameworks/*.examples.md→ example file (merge with rules) - •
integrations/*.examples.md→ example file (merge with rules) - •
project.md→ skip (inherently project-specific) - •
project.examples.md→ skip (inherently project-specific)
- •
- •Parse each file: extract YAML frontmatter (
paths:) and body sections (## Principles,## Project-specific patterns,## Principles Examples,## Project-specific Examples)
Step 3: Normalize Similar File Names
Before merging, group files that refer to the same concept but have different names. This applies to .md, .local.md, and .examples.md files — a .md and its corresponding .local.md and .examples.md share the same normalization (e.g., rails-controller.md, rails-controller.local.md, and rails-controller.examples.md are normalized together with their rails-controllers.* variants).
- •Detect similar file names within the same directory (e.g.,
rails-controller.mdvsrails-controllers.md,rails-model.mdvsrails-models.md)- •Singular/plural variants (e.g.,
controller/controllers) - •Minor naming differences for the same concept (use AI judgment based on file content and
paths:frontmatter overlap)
- •Singular/plural variants (e.g.,
- •For each group of similar files, select a canonical name:
- •Prefer the name used by the majority of projects
- •If tied, prefer the name matching extract-rules' layered framework convention (e.g.,
<framework>-<layer>)
- •Treat grouped files as the same file for subsequent merge steps (Step 4 and Step 5)
- •Report normalized groups in the summary (e.g., "
rails-controller.md+rails-controllers.md→rails-controllers.md")
Step 4: Merge Portable Rules (.md)
Design note: Once a pattern is promoted to a Principle (via Step 5), it becomes a permanent org-level rule. Subsequent merge-rules runs will preserve it through Step 4's principle deduplication, regardless of whether the original .local.md pattern still meets the promotion threshold. To demote or remove a promoted Principle, manually edit the org rules output.
For each unique (normalized) file name across projects (e.g., languages/typescript.md, integrations/rails-inertia.md):
- •Collect all versions from projects that have this file (including normalized variants)
- •Merge
## Principlessections:- •Deduplicate by principle name (text before parenthetical hints)
- •Union hints from all projects for the same principle
- •If same principle name but clearly different meaning → keep both, flag in report (see Conflict Handling)
- •Preserve unique principles from any project
- •Merge
paths:frontmatter: union of all path patterns, deduplicate - •If file exists in only 1 project, include as-is
Step 5: Promote .local.md Patterns to Principles
For each normalized category (e.g., languages/typescript, frameworks/rails-controllers, integrations/rails-inertia):
- •Collect
## Project-specific patternsfrom all projects — from.local.mdfiles and from hybrid.mdfiles that contain this section (see Step 2) - •Deduplicate against existing Principles: Exclude patterns whose description (text after
-) semantically matches an existing principle name in the corresponding.mdoutput (from Step 4). Use AI judgment for semantic equivalence (case-insensitive, synonyms). This prevents self-amplification when.local.mdcontains patterns previously promoted by older versions - •Match remaining patterns by inline code signature (backtick portion before
-)- •Use AI judgment to determine semantic equivalence (e.g.,
useAuth()anduseAuth() → { user, login, logout }refer to the same pattern)
- •Use AI judgment to determine semantic equivalence (e.g.,
- •Count occurrences per pattern across projects
- •Calculate threshold: pattern must appear in more than
len(projects) * promote_thresholdprojects (i.e., strict majority when threshold = 0.5) - •Convert to Principles format and append to
## Principlesin the corresponding normalized.mdoutput:- •Signature format:
`signature` - description→ Principles format:Description (simplified signature) - •The description becomes the principle name, the function/type name from the signature becomes the hint
- •Examples:
- •
`useAuth() → { user, login, logout }` - auth hook interface→Auth hook interface (useAuth) - •
`clean_bracket_params(:keyword)` - WAF付加のブラケット除去→WAF付加のブラケット除去 (clean_bracket_params) - •
`RefOrNull<T extends { id: string }> = T | { id: null }` - nullable refs→Nullable refs (RefOrNull<T>)
- •
- •Apply Step 4's principle deduplication to the converted principles (skip if same principle name already exists)
- •Signature format:
- •Patterns below threshold → discard (listed in report for reference)
Step 5.5: Merge Examples (.examples.md)
For each normalized .examples.md file group:
- •Collect all versions from projects that have this file (including normalized variants)
- •Principles Examples: Merge by section heading (e.g.,
### FP only)- •Same principle heading across projects → adopt the most detailed example, or merge Good/Bad from different projects
- •If Good/Bad contrast exists in one project but not another → adopt from the project that has it
- •Deduplicate identical examples
- •Promoted pattern examples: For patterns promoted in Step 5, include their examples under
## Principles Examples- •Use the same semantic equivalence judgment as Step 5 (matching by inline code signature with AI judgment) to link
###example headings to promoted patterns — do not rely solely on exact heading match - •
###title uses the converted Principle name (from Step 5), not the original signature - •Include the full original signature as a Good example showing usage
- •Discard examples for patterns below threshold (same as the pattern itself)
- •Use the same semantic equivalence judgment as Step 5 (matching by inline code signature with AI judgment) to link
- •Output
.examples.mdfile structure:
# <Category> Rules - Examples ## Principles Examples ### <Principle name> **Good:** ```<lang> <example>
Bad:
<example>
- `###` titles must match the corresponding rule name in the merged output `.md` file. Do not rephrase - No `paths:` frontmatter (prevents auto-loading) - If no examples exist for any merged rule, skip generating the `.examples.md` file ### Step 6: Write Output 1. Check output directory: - If `--dry-run`: skip writing, show planned file list with contents summary, then go to Step 7 - If exists and has files: warn and ask for confirmation before overwriting - If not exists: create with `mkdir -p` 2. Write merged files preserving directory structure: - `languages/<lang>.md` - `languages/<lang>.examples.md` (if examples exist) - `frameworks/<framework>.md` - `frameworks/<framework>.examples.md` (if examples exist) - `integrations/<framework>-<integration>.md` - `integrations/<framework>-<integration>.examples.md` (if examples exist) - Only `.md` and `.examples.md` files (no `.local.md` in output) 3. Output file format: ```markdown --- paths: - "**/*.ts" - "**/*.tsx" --- # TypeScript Rules ## Principles - Immutability (spread, map/filter/reduce, const) - Type safety (strict mode, explicit annotations, no any) - Auth hook interface (useAuth)
- •Output
.mdcontains only## Principles(promoted patterns are converted and included here) - •Omit
## Principlessection if no principles exist for this category - •If a corresponding
.examples.mdwas generated, append a reference section at the end:markdown## Examples When in doubt: ./<name>.examples.md
Step 7: Report Summary
Display report using the project's directory name (last path component) as label. Report headers are always in English.
# Merge Rules Report ## Sources - frontend-app (3 files) - backend-api (2 files) - shared-lib (4 files) ## File Name Normalization - `rails-controller.md` + `rails-controllers.md` → `rails-controllers.md` - `rails-model.md` + `rails-models.md` → `rails-models.md` ## Merge Results | File | Sources | Principles | Promoted to Principles | Examples | |------|---------|------------|------------------------|----------| | languages/typescript.md | 3/3 | 5 | 2 | 7 | | frameworks/react.md | 2/3 | 3 | 1 | 4 | | integrations/rails-inertia.md | 2/3 | 2 | 0 | 2 | **Principles** = total including promoted. **Examples** = total `###` entries in the output `.examples.md`. ## Promoted to Principles - `useAuth()` → Auth hook interface (useAuth) - 3/3 projects - `pathFor() + url()` → Path helpers (pathFor, url) - 2/3 projects ## Below Threshold (reference) - `useCustomHook()` (typescript) - 1/3 (frontend-app only) - `ApiClient.create()` (typescript) - 1/3 (backend-api only) ## Skipped - project.md x3 (project-specific, skipped)
Conflict Handling
- •Same principle, different hints: Union all hints, deduplicate
- •Same principle name, different meaning: Keep both, flag in report for human review
- •Same category, different paths: Union all path patterns
- •Contradicting principles: Keep both, report as conflict for human review