# Inherit Legacy Style

> Legacy-project style inheritance skill. Use when the user types /inherit-legacy-style, or when onboarding an AI coding agent onto a hand-written legacy project and you need to prevent "style drift" (the model imposing its pretrained mainstream idioms onto the project). Language- and framework-agnostic — it aligns only the meta-architecture, not surface syntax. Once run, it becomes a behavioral constraint on all subsequent coding tasks. Do NOT use for pure research or one-off questions unrelated to code-style alignment.

- Skill: `linsywu/inherit-legacy-style` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add linsywu/inherit-legacy-style`
- Raw SKILL.md: https://api.skillmd.com/api/skills/linsywu/inherit-legacy-style/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: linsywu (https://skillmd.com/u/linsywu)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/linsywu/inherit-legacy-style

---


# Inherit Legacy Style

## Overview

When an AI coding agent is dropped onto a hand-written legacy project, the model's pretraining bias makes it impose its own "mainstream" idioms, causing **Style Drift**: two competing list-refresh patterns in one codebase, inconsistent naming, duplicated utility functions.

This skill fixes that. It is **fully language- and framework-agnostic** — it does not judge syntax sugar; it extracts and aligns the project's **meta-architecture** along four dimensions, then crystallizes the consensus into a root-level `.ai-style-rules.md` that acts as a binding constraint on subsequent coding.

**Core principle: reuse the legacy exemplar over free improvisation. When a legacy pattern can be reused pixel-for-pixel, never inject the model's own preferred style.**

## Output Language Policy (applies to every produced artifact)

The **skill's own instructions are in English**, but everything this skill *produces for the user* must **mirror the language the user is communicating in**:

- `.ai-style-rules.md` content, the `CLAUDE.md` hook line, grilling questions, the strength-of-enforcement prompt, status reports, and the evolution log — **all written in the user's language** (e.g. Chinese if the user writes in Chinese, English if in English).
- Detect the user's language from their messages in the conversation. When ambiguous, default to English and proceed.
- Code-level tokens (identifiers, file paths, JSON keys, the exemplar names) stay verbatim regardless of language.

---

## Step 0 — Adaptive Mode Detection (zero-argument trigger)

On `/inherit-legacy-style`, the **first action must be a silent check** for `.ai-style-rules.md` at the project root:

```
if .ai-style-rules.md absent  →  Branch A: [First-time · Full-Scan mode]
if .ai-style-rules.md present →  Branch B: [Evolution · Incremental-Sniff mode]
```

Use `Glob` or `ls`. Announce the chosen mode in one line, then execute that branch directly — **do not ask the user to pick a mode**.

---

## Branch A — First-time · Full-Scan mode

Use `Glob` to locate sources, `Grep` to search patterns, `Read` to study exemplars, and extract the **four-dimension implicit rules**.

### Measure scale first, then pick a scan tier (Scale-Adaptive)

Scanning is **not one-size-fits-all sampling** — sampling *starves* small projects (too few samples, distorted frequencies); full traversal *blows up* large repos (token/time overflow). First measure scale:

```bash
git ls-files 2>/dev/null | grep -cE '\.(js|ts|jsx|tsx|vue|py|go|rs|java|kt|rb|php|cs|swift|c|cpp|h)$'
# Non-Git fallback: find . -type f \( -name '*.js' -o -name '*.vue' ... \) -not -path '*/node_modules/*' | wc -l
```

Pick a tier by source-file count:

| Tier | Source files | Strategy |
|---|---|---|
| **Small** | ≲ 50 | **Full close-read**. `Read` every core source; no sampling. When frequency base is too low (e.g. "3 vs 2") to auto-decide, **escalate to grilling** — never guess the majority. |
| **Medium** | 50 – 500 | **Layered**. Infra layer (apis/utils/store/config) read **fully**; business layer (views/pages/handlers) sample 2-3 exemplars per dimension by **frequency + recency (Git)**. |
| **Large** | ≳ 500 | **Strict sampling + budget cap**. Read only files touched in the last N Git changes + the top 2-3 highest-frequency exemplars per dimension; cap file/read count, locate via `--stat` summary before `Read`. |

In every tier, evidence priority is fixed: **frequency > recency (Git) > human-written (avoid generated code / vendor)**. Tell the user the chosen tier in one line (e.g. "833 source files → Large project, strict sampling").

### The Four Scan Dimensions

> Examples span frontend / backend / CLI to show "cross-stack, meta-architecture only" — map to the project's actual stack; do not force irrelevant examples.

1. **File Anatomy** — the in-file declaration order. Frontend: `import → types/consts → main → helpers → export`; Backend: `package/imports → consts → struct/types → constructors → methods → private helpers`; CLI: `flag defs → config load → main flow → subcommands`. Record the actual template.

2. **State & Control Flow** — control-variable naming and combinations in common high-frequency logic. Frontend: async tri-state `loading/error/data`, pagination `pageIndex/pageSize/total`; Backend: pagination `offset/limit` vs `page/size`, transaction boundaries, context propagation; CLI: exit-code conventions, `--verbose/--dry-run` flag naming. Record naming + combination.

3. **Infrastructure** — where high-frequency cross-cutting operations are reused, and how named. Frontend: where interceptors / date-money formatters live; Backend: where DB conns/middleware/loggers are initialized, where error-code constants are defined; CLI: location of config parsing / output formatting utilities.

4. **Error Handling** — exception preference (try/catch vs global interceptor vs `Result`/`error` return vs panic/recover) and null-check habits (optional chaining, early return, default fallback, `if err != nil`).

### Conflict handling

If the scan finds the project **self-contradicting** (two list-refresh patterns, two naming styles), **do not self-arbitrate** — go to [Grilling & Alignment] below, one at a time.

### Output (first-time creation of `.ai-style-rules.md`)

Once there are no conflicts (or all are resolved), **create from scratch** the root `.ai-style-rules.md` with three mandatory sections (write content in the **user's language** per the Output Language Policy):

- **[Golden Files]** — real exemplar file paths per dimension, each annotated with what it demonstrates.
- **[Naming & State-Control Rules]** — concrete, checkable rules distilled from the four dimensions.
- **[DONTs]** — bad smells that exist but **must not propagate** (deprecated idioms, the arbitrated-away alternative).

Template: [`reference/rules-template.md`](reference/rules-template.md).

### Critical: write the persistent hook (else the rules expire next turn)

`.ai-style-rules.md` is **not** auto-loaded by future sessions. **Right after creating it, install the hook** so it becomes a per-turn constraint (see Step 5). Do this before reporting.

---

## Branch B — Evolution · Incremental-Sniff mode

**Refuse to re-scan in full.** Only sniff whether the human's recent changes introduced a new style or new utility that **differs from the recorded rules**.

### Sniff steps

1. `Read` the existing `.ai-style-rules.md` into context. If its header records the last-scan commit fingerprint, run `git diff <last_hash> HEAD --stat` to pinpoint the delta precisely.
2. Read the human's recent changes (try in order of availability):
   ```bash
   git status
   git log -3 --stat          # last 3 commits (read --stat summary first to locate touched files)
   git diff HEAD~3 HEAD        # only inspect suspect files on demand; do not dump the full diff
   ```
   **Oversized-diff fallback** (e.g. just merged hundreds of files): read only the `--stat` summary + sample the few largest source changes; do not load the whole diff. Non-Git repo or command failure → fall back to most-recently-modified sources (`ls -lt`).
3. Compare: does the new code show patterns / utilities / idioms that are **unrecorded or in conflict** with the existing rules?

### Conflict handling

Conflict found → go to [Grilling & Alignment], one at a time. No conflict → tell the user "no style drift detected, rules unchanged" and stop.

### Output (append, never overwrite)

**Never overwrite or erase old rules.** Append an evolution log at the **end** of `.ai-style-rules.md` (in the user's language):

```markdown
### [YYYY-MM-DD] Style Evolution Log
- New consensus: … (source: path, user chose option N)
- New exemplar / utility: …
```

Use the real current date (`date +%F`).

---

## Grilling & Alignment — shared by both branches

### Pass the signal threshold before interrupting the user (noise reduction)

Not every difference deserves a question. On finding two styles, assess **signal strength** first to avoid spamming the user:

- **Weak signal → no grilling, auto-suppress**: when one side is overwhelmingly minor (minority < 5% **and** absolute count < 10), take the majority as the rule, record the minority in DONTs, and just mention it in one line in the final report.
  - *Example*: `pageIndex`(843) vs `pageNum`(8) → auto-suppress; library choice `moment`(1) vs `dayjs`(2) → base too small, non-core, skip entirely.
- **Strong signal → must grill**: the two styles are comparable (e.g. 6:4), **or** despite skew it is a semantic fork on a core dimension (two incompatible list-refresh mechanisms, two error-handling paradigms).
- **Small-project exception**: when sources ≲ 50, the frequency base is inherently low — "3 vs 2" is not a majority signal, so **downgrade to grilling**; do not auto-arbitrate.

The threshold is heuristic; when unsure, **lean toward asking** rather than guessing.

### When grilling, enforce strict one-at-a-time interaction

Once confirmed a strong signal, **enforce the one-soldier grilling protocol**:

1. **One soldier**: raise exactly **one** conflict at a time. Never stack multiple questions.
2. **Fixed question format** (in the user's language):

   > 📄 **Evidence in code**: at `pathA` style `X` is used, while at `pathB (or in existing rules)` style `Y` is used.
   > ⚠️ **Risk**: mixing both in new features will fracture the project's style.
   > ❓ **Choose**: which should subsequent development follow? Reply with a number —
   > `1` follow X　`2` follow Y　`3` this is the latest evolution, update the rules　`4` I have a new rule: [input]

   Prefer presenting the four options via the `AskUserQuestion` tool.
3. **Suspend and wait**: only after an explicit answer is received **and recorded** may you proceed to the next conflict. Multiple conflicts → serial, one by one.

---

## Step 5 — Runtime Enforcement Hook

> ⚠️ **Core insight**: the instructions loaded this turn **evaporate** when the conversation ends or context is compacted. Relying on "I'll remember to load the rules" is unreliable — the constraint dies in a new session. **A real constraint must be written where Claude reads it automatically every turn.**

### 5.1 Let the user choose enforcement strength, then hook accordingly (mandatory after first-time creation)

Enforcement strength is **the user's call** — do not default it for them. After creating the rules, first ask via `AskUserQuestion` (one of three, phrased in the user's language):

> ❓ `.ai-style-rules.md` is created. How strongly should subsequent coding obey it?
> - `1` **Soft hook (recommended · sufficient)**: write into the project `CLAUDE.md`, auto-loaded every session. Easy, but the model can theoretically bypass it.
> - `2` **Hard hook (mechanical enforcement)**: soft hook + a `PreToolUse[Write|Edit]` hook that injects a rule reminder before every file write, unbypassable. Modifies `settings.json`.
> - `3` **No hook for now**: just keep the rules file; I'll `@`-reference it manually.

**Options 1 and 2 both do the soft hook first** — attach the rules to the project root `CLAUDE.md` (auto-injected every session):
1. Check for a root `CLAUDE.md` (`Glob`/`ls`).
2. **Exists**: `Edit` to **append** one reference line in a prominent place, never overwriting:
   ```markdown
   > 📐 Read @.ai-style-rules.md before coding — obey its naming/state/infra rules, avoid the DONTs.
   ```
   (Write this line in the user's language.)
3. **Absent**: `Write` a minimal `CLAUDE.md` containing only that line.

**Only option 2 adds the hard hook** — configure a `PreToolUse` hook. **Key: do NOT `cat` the full file** (~1000 extra tokens per write); the content is already loaded once per session via the CLAUDE.md `@` reference, so the hook only injects a ~20-token reminder. Prefer invoking the `update-config` skill to write into `settings.json`:
```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit|MultiEdit",
        "hooks": [
          { "type": "command",
            "command": "echo 'Reminder: obey .ai-style-rules.md before this write (naming/state/infra rules + DONTs).'" }
        ]
      }
    ]
  }
}
```
> Use `PreToolUse[Write|Edit|MultiEdit]` not `UserPromptSubmit`: the former fires only when actually writing a file (zero cost on reads/chat); the latter burns tokens every turn. After writing, tell the user it's set and a new session / reload fully activates it.

**Option 3**: skip hooks; just tell the user the rules live in `.ai-style-rules.md` and to `@`-reference them when needed.

> Without the 5.1 hook (option 1/2), this skill is a one-shot scanner, betraying its "workflow / binding constraint" identity.

### 5.2 Per-turn compliance declaration (runtime behavior)

Whenever `.ai-style-rules.md` (loaded via 5.1's CLAUDE.md reference) is in context, any write/edit/refactor task **must explicitly load it as the first thought** and open the reasoning with a **compliance declaration**, e.g. (in the user's language):

> "Loaded `.ai-style-rules.md`. This round I will reuse the state-control pattern from exemplar `X` pixel-for-pixel, keep existing naming, and avoid the DONTs."

Two constraints:
- **Reuse structure and naming, but do not copy bugs**: if the exemplar has obvious dead code / defects (duplicate `return`, empty catch), **do not replicate them** — flag to the user instead.
- If the new task itself introduces a requirement conflicting with the rules, go back to [Grilling & Alignment], then continue.

---

## Checklist (hard rules)

- [ ] Step 0 silently detects file presence, auto-branches, never asks the user to pick a mode.
- [ ] Branch A measures scale via `git ls-files | wc -l` first, picks Small/Medium/Large tier (Small=full read, Large=sampling+budget cap), announces the tier.
- [ ] Scan aligns meta-architecture only, never judges syntax; examples map to the project's actual stack.
- [ ] Conflicts pass the signal threshold first: weak → auto-suppress into DONTs, strong → grill; grill one at a time, fixed four-option format, suspend and wait.
- [ ] First-time = create three sections from scratch; incremental = append evolution log at end, never overwrite.
- [ ] After creating rules, run 5.1: first `AskUserQuestion` for enforcement strength (soft/hard/none); soft → write CLAUDE.md; hard → also call `update-config` for a `PreToolUse` hook (inject a one-line reminder, never cat the full file).
- [ ] Every later coding turn opens with a compliance declaration; reuse structure but never copy the exemplar's bugs / dead code.
- [ ] All user-facing artifacts (rules file, hook line, questions, reports, log) are written in the **user's language**; only the skill's own instructions are English.

