Merge Rules
SkillAI & modelsMerge extract-rules output from multiple projects into a unified portable rule set. Promotes .local.md patterns shared across projects to Principles format.
Available today. Use it from your connected AI after setup.
No other account needed.
Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
Then ask your AI: use the Merge Rules skill
What this skill tells your AI
The instructions your AI receives, as published by hiroro-work/claude-plugins in skills/merge-rules/SKILL.md and read by ahel’s review.
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).
---
# Source projects (each must have extract-rules output)
projects:
- ~/projects/frontend-app
- ~/projects/backend-api
- ~/projects/shared-lib
# Output directory (default: .claude/rules)
# Examples go to the sibling <output_dir>-extras (.claude/rules-extras)
output_dir: .claude/rules
# Rules directory within each project (default: .claude/rules)
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
---
Examples directories (no configuration key). Examples live in a sibling of the rules directory: that path with any trailing / removed and -extras appended — {path}/{rules_dir}-extras for each source project, <output_dir>-extras for the output. Strip the trailing / before appending, or a configured .claude/rules/ yields .claude/rules/-extras. Collection takes each rule file's examples from the derived directory first and from the rule directory itself only when that misses, so a pre-split layout — examples still beside the rule files — is still collected; writing always targets the derived directory. merge-rules does not read extract-rules' examples_output_dir. Source of truth for the derived name is extract-rules' examples_output_dir default; keep in sync when that default changes.
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:
- Recursively list
{path}/{rules_dir}/and{path}/{rules_dir}-extras(derived per § Configuration's "Examples directories (no configuration key)" paragraph) — two listings per project, never a third. A missing-extrasdirectory is the normal pre-split case: treat it as an empty listing rather than an error. Rule files are the.mdand.local.mdentries of the first listing; examples are the union of the*.examples.mdentries of both, with-extraswinning when the same relative sub-path appears in each. A project part-way through migration therefore contributes both its moved and its still-co-located examples - 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 aboveintegrations/*.md→ same as abovelanguages/*.local.md→ promotion candidateframeworks/*.local.md→ promotion candidateintegrations/*.local.md→ promotion candidatelanguages/*.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 at the same relative sub-path (e.g.,
rails-controller.mdvsrails-controllers.md,rails-model.mdvsrails-models.md). The rule tree and the examples tree mirror each other, soframeworks/in either tree is the same position- 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)
Once a pattern is promoted to a Principle (via Step 5), subsequent merge-rules runs 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
- 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). - 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
- If no examples exist for any merged rule, skip generating the `.examples.md` file
### Step 6: Write Output
1. Check the output directories `output_dir` and `<output_dir>-extras` (derived per § Configuration's "Examples directories (no configuration key)" paragraph):
- If `--dry-run`: skip writing, show planned file list with contents summary, then go to Step 7
- If either exists and has files: warn and ask for confirmation before overwriting
- For either that does not exist: create with `mkdir -p`
2. Write merged files preserving directory structure:
- `<output_dir>/languages/<lang>.md`
- `<output_dir>-extras/languages/<lang>.examples.md` (if examples exist)
- `<output_dir>/frameworks/<framework>.md`
- `<output_dir>-extras/frameworks/<framework>.examples.md` (if examples exist)
- `<output_dir>/integrations/<framework>-<integration>.md`
- `<output_dir>-extras/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:
The path is that examples file —## Examples When in doubt: <relative-path-to-examples-file><output_dir>-extras/<same relative sub-path>/<name>.examples.md— expressed relative to the rule file's own directory. With the defaultoutput_dir,languages/typescript.mdgets../../rules-extras/languages/typescript.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
- Contradicting principles: Keep both, report as conflict for human review
Signals
- GitHub stars
- 47
- Forks
- 3
- Last commit
- Sep 2026
Advanced
- Item type
- skill
- Key
merge-rules- Source
- github.com/hiroro-work/claude-plugins