SKILL.md
Coding Standards
Every line you write, edit, or review must comply with these rules. They come in two sets, and you apply common + exactly one framework on every task:
- Universal rules (
references/common/) — clean-code principles that govern the inside of every
function, class, and module, regardless of language.
- Per-framework rules (
references/<framework>/) — folder layout and framework patterns that govern
the outside: where files live and what folders mean.
All paths in this document are relative to this SKILL.md file, so they resolve wherever the skill is installed.
The flow: bootstrap (once) → skip exclusions → pick a mode → detect framework → resolve structure → pick execution shape → open a task list → run the branch. Steps 0–6 are that sequence; Step 7 is the branch (7a pipeline, 7b inline).
Step 0 — Bootstrap the enforcement hooks (once per session, only if needed)
Run the fast read-only check first (single absolute-path command, no cd/&& — it matches the pre-approved permission rule, so it won't prompt):
python3 <skill-dir>/bootstrap.py --verify
Exit 0 → hooks are wired; go to Step 1. Non-zero → run the full install and act on its output:
python3 <skill-dir>/bootstrap.py --auto-install
Wired/Updated/Install OK→ tell the user to restart the session so hooks activate.
**Until they do, the PreToolUse hooks are NOT active in this session** (Claude Code reads settings.json at session start), so a Write here won't be blocked. This is the unenforced-first-session gap. Close it: for any code you write or review in this session, run the linter yourself over the touched files as a compensating check before reporting done — python3 <skill-dir>/hooks/review-files.py <file> … works immediately, no restart needed — and fix what it finds. Tell the user write-time blocking starts next session; this session is covered by the manual linter pass.
Blocking issues:→ surface it verbatim and stop until the user resolves it.cannot determine install scope→ the skill is outside a.claude/skills/tree; point atREADME.md
and continue without write-time blocking — the rules still apply, and review-files.py still runs as a manual linter over what you touch.
The install self-detects project vs global scope and auto-installs the skill's required packages. Flags, the venv/PEP-668 fallback, and the readiness breakdown live in references/bootstrap.md — read it only if bootstrap misbehaves.
Step 1 — Skip excluded files (always)
Some files are owned by third-party tooling, not the user — editing them breaks the upgrade path or churns generated code. A file is excluded if any of these hold:
- Its path matches a built-in default (installed deps, generated code, build output, lock files, ORM
migrations, shadcn components/ui/, the skill's own .coding-standards/ reports). Full list: hooks/exclusions.py → DEFAULTEXCLUSIONS.
- Its first 10 lines carry a generation marker (
@generated,DO NOT EDIT,Code generated by, …). - It matches a pattern in the project's
.coding-standards-ignore(gitignore-style, at the repo root,
seeded by bootstrap).
Check by inspection: the path against the default list, the marker in the file you're reading anyway, the ignore file if one exists. Review and fix scope you don't filter by hand — hooks/review-files.py skips excluded files itself, and the write-time hooks exit silently on them.
Refuse to modify an excluded file. If the user explicitly asks to edit one, warn that the file is tool-owned and changes will be lost on regeneration, then proceed only after consent.
Step 2 — Pick a mode (only on contextless activation)
Skip this if the user's message already names a task — "write X", "refactor Y", "review this PR", "is this clean?", "what does FN-005 mean?". Those phrases are the mode; go to Step 3.
Trigger it only when the skill activated with no clear task: bare /coding-standards, "what does this skill do?", or a message too generic to infer mode. Ask with AskUserQuestion, using the exact mode-picker payload in references/activation.md — the labels are what the routing below matches on.
Route the answer:
- Write code that follows these rules → Write mode, full flow.
- Check existing code against these rules → ask what to check (file, folder, diff, PR). Once the user
answers, the scope is known — so build the whole-workflow task list now, before any reviewing starts (Step 6), with resolve + record structure as item 1, and fold any extra targets the user named in as more items.
- Show me the rules → resolve framework + structure silently, load references, present a one-screen
rule index. No structure question, no run-mode question — nothing is written.
There is no picker entry for Fix — it's triggered by phrasing ("fix the findings" / "apply the review", or "continue the fix" / "resume the fix" to pick up a non-done milestone plan), which routes straight to Fix mode.
Ask at most once per session; once mode is set, it stays set. Never ask when the user already named a task.
Step 3 — Detect the framework
Look at the file you're acting on and match the signals below. Stop at the first match whose "owns file types" includes the extension of the file you're editing. A row whose file types don't include the current file is skipped even if its repo-level signal is present — so editing a .php file in a Laravel + Inertia/Vue repo resolves to laravel, not vue-nuxt, and a Component.vue in the same repo resolves to vue-nuxt. This is the rule; the "more specific" note below only breaks ties within one language.
| Framework key | Owns file types | Detection signals (any of these) |
|---|---|---|
nextjs |
.ts .tsx .js .jsx |
next.config.{js,ts,mjs} at repo root or next in package.json deps or the file lives under app/ or pages/ next to that config |
react-native |
.ts .tsx .js .jsx |
expo, react-native, or @expo/* in package.json or app.json with "expo" key or metro.config.js |
nativescript |
.ts .js .xml |
nativescript.config.{js,ts} or nativescript in package.json or a .xml file paired with a .ts page |
cocos-creator |
.ts .js |
assets/ + settings/ + (library/ or temp/ in .gitignore) at repo root or cc / cocos-creator import or .scene / .prefab files |
vue-nuxt |
.vue .ts .js |
vue or nuxt in package.json or nuxt.config.{ts,js} or .vue files |
nestjs |
.ts |
@nestjs/ in package.json or .module.ts / .controller.ts / .service.ts with NestJS decorator imports |
node-express |
.ts .js |
express or fastify in package.json and no NestJS |
laravel |
.php |
composer.json with laravel/framework or an artisan file at root or .php under app/ |
csharp |
.cs |
.csproj, .sln, *.cs files |
spring-boot |
.java .kt |
pom.xml with spring-boot-starter-* or Gradle with the Spring Boot plugin or @SpringBootApplication |
django |
.py |
manage.py + a settings module or django in pyproject.toml / requirements.txt |
fastapi |
.py |
fastapi in deps or from fastapi import FastAPI and not Django |
flask |
.py |
flask in deps or from flask import Flask and not Django/FastAPI |
go-http |
.go |
go.mod + a router (gin, echo, fiber, chi, mux) or net/http handler routing |
unsupported |
(any) | A framework/ecosystem the skill recognizes but has no structure reference for. Web: Angular (@angular/core / angular.json), Svelte / SvelteKit (.svelte, svelte in package.json, svelte.config.), Astro (astro dep / astro.config. / .astro), Remix (@remix-run/*), Ember, SolidStart, Qwik. Game engines: Unity (Assets/ + ProjectSettings/), Godot (project.godot). Other ecosystems not yet covered in v5: Ruby/Rails, Rust, Swift/iOS, Flutter/Dart, Android native. … |
vanilla-js |
.ts .js |
Plain .ts / .js that fits none of the above and is not one of the recognized-unsupported frameworks (libraries, CLIs, scripts, browserless projects) |
- If two rows of the SAME language could apply (e.g. a
.tsthat's both Next.js and vue-nuxt-eligible),
pick the more specific one (a .tsx in a Next.js repo is nextjs, not react-native). The file-type gate above already separates different languages — this only breaks within-language ties.
- Monorepos pick the framework per file, not per repo — walk up from the file until a signal
matches whose file types include the file. apps/web/... → nextjs; apps/api/... → nestjs.
- Engine/ecosystem override (checked FIRST): if the project shows a game-engine marker — Unity
(Assets/ + ProjectSettings/) or Godot (project.godot) — route to unsupported, even though a .cs would otherwise match csharp. The csharp row's vertical-slice web layout is wrong advice for a Unity project, so the engine signal wins. common/ line rules still apply; structure review is declined.
- Plain libraries with no framework signal default to
vanilla-js(JS/TS) orcommon/only (Python). - Recognized but unsupported (the
unsupportedrow): say so and do NOT fall back tovanilla-js—
imposing vanilla-js's business-folder + barrel layout on an Angular or SvelteKit app actively fights the framework's own conventions, which is worse than declining. Instead: apply all of common/ (it's universal — the line-level rules and the write-time hooks still enforce on the language), keep the project's existing layout for placement, and tell the user the skill has no structure reference for this framework yet so structural/placement review is limited to common/structure.md (ST-*), not a framework-specific shape. Skip the Step 4 structure question (there's no catalog to offer).
- If you can't tell, ask once — don't guess across frameworks.
Step 4 — Resolve the project structure (the outer shell)
This resolves the outer folder layout — where the top folders sit and what they're called. It decides placement only; the inside of every folder still follows common/structure.md (business → feature → sub-feature → unit), whichever shell is chosen. Follow what the project already uses (the framework's scaffold, or the layout that's there); the choice is remembered in a .coding-standards-structure file so the user is asked at most once.
Comprehend the structure once, then trust the record. When .coding-standards-structure does not exist, comprehend the structure (build and confirm the map, references/structure-map.md) and record the resolved layout in it. When it does exist, follow it and do not re-derive or propose restructuring — ordinary write/review runs check code against the recorded structure, they never re-open it. Re-run the full structure comprehension (then update the record) only when the user explicitly asks to restructure or review the structure — "restructure this", "review the structure", "show me the structure tree". A normal task ("add X", "review this PR", "is this clean?") is not that request.
The decision in brief:
- File exists at the framework project root → read it and follow it (a
follows: <standard>target,
or a described custom layout). No question. If the file is non-canonical — carries comments, a hooks: block, or any rule toggle — normalise it in place (keep only the follows: line / layout: body), write it back, and report it.
- No file, folders match a standard → use that standard's reference, and record it — write
follows: <standard> plus the full layout: tree, so later runs follow it instead of re-comprehending. No question.
- No file, custom layout → ask the user once with
AskUserQuestion(recommended structure first,
"keep current" last), then write the file recording their choice.
The file records placement only — a follows: line and/or a full layout: tree (the actual solved structure; a project may carry both). It never carries rule toggles: every rule is always enforced, deep-import is derived from whether a barrel exists, and the ST-008 size/folder checks run at fixed thresholds. block-structure-file-violations.py enforces this.
Read references/structure-resolution.md before acting on case 3, a monorepo, or a non-canonical file. It has the full mechanics: monorepo file placement (the sub-project root, not the repo root), the question shapes, what "keep current" does and doesn't exempt, the self-heal normalisation, and the messy-project fallback.
The resolved structure replaces references/<framework>/structure.md in the Step 7b load list, and the pipeline passes it to Worker 1 as STRUCTURE. For "Show me the rules" / pure Q&A, resolve silently — don't ask or write a file.
Step 5 — Pick the execution shape (orchestrator pipeline vs inline)
Two shapes for Write and Review. Resolve Step 4 first — structure is always the first question, run-mode the second.
| Trigger | Shape |
|---|---|
| Single-file edit (≤30 lines), single-function refactor, or rule Q&A | Inline — you do it yourself. Steps 6 → 7b. |
2+ files, a new feature, a diff/PR review, --thorough, or the /coding-standards command |
Orchestrator pipeline — you dispatch workers. Steps 6 → 7a. |
Agent tool unavailable in this host (Cursor, Codex, OpenCode) |
Inline regardless of scope — flag it in the routing line. |
| Apply review findings ("fix the findings", "continue the fix" / "resume the fix") | Orchestrator pipeline, MODE: fix, always. No run-mode question. |
The choice is made one of two ways:
A) Invoked via /coding-standards or the Step 2 picker, and Agent is available → ask the run-mode question with the exact payload in references/activation.md. "Multiple agents" → pipeline (7a); "Single agent" → inline (7b). Ask at most once per session; reuse the answer afterward.
B) Plain message (no command) → decide from the table.
Announce the routing — one line, before the substantive work
Once mode, framework, structure, and shape are resolved, emit a single line so the user sees the route:
coding-standards: framework=nextjs · structure=route-colocated (matched) · shape=pipeline (2+ files)
Name the shape's trigger (the table row, or "user choice"), and mark how structure resolved — (matched), (file), or (asked). Pure rule Q&A skips the line; there's nothing to route.
Step 6 — Track the run with a task list
On any real work — writing, editing, refactoring, or reviewing — open the task list up front, the moment the scope is known: for the Step 2 picker path, right after the "what to review / write" answer; for a plain message ("review this PR"), as soon as you've identified the files. Open it before Steps 3–4, so framework detection and resolve + record structure land on the list as the first tracked items, not pre-work that runs before any list exists. The list is what makes the standards visible — the user sees each stage applied instead of trusting it happened. Use the host's task-list tool (TodoWrite, TaskCreate/TaskUpdate, …); skip the list only if the host has none, and skip it for pure rule Q&A — there's no multi-step work to track.
Build the whole workflow at once, then extend it with any extra targets the user named. Track the work the user cares about (fold in real file/feature names), keep exactly one item in_progress, and complete it before starting the next. A typical review list: resolve + record structure → detect framework → load rules → review → run hooks → write report. A typical write list: resolve + record structure → detect framework → read existing code → load rules → draft → rewrite the draft → sweep the rules → write the files. The first item, resolve + record structure, may be marked completed only when .coding-standards-structure exists at the resolved project root — resolved one of three ways: matched a standard, asked-and-recorded, or a skip-reason logged (recognized- unsupported framework, or below the scope threshold). Having run a structure-comprehension agent is not completion; the file on disk is. The pipeline and Fix shapes add their worker/ledger stages — see orchestrator-pipeline.md. A milestone-driven fix adds one item per milestone at plan approval — a display mirror only; the plan file on disk stays the source of truth.
Step 7a — Pipeline branch: orchestrate the workers
You (the main agent) become the orchestrator: you coordinate three sequential workers and do the final Write yourself, so the hooks fire exactly once on the complete code. Read references/orchestrator-pipeline.md and follow it — that's the full protocol (worker roster, Write/Review/Fix pipeline shapes, dispatch loop, validation, retry/fallback, summary).
Invariants, true even before you open the reference:
- Comprehend before you check. Above the scope threshold and when the structure isn't already
recorded in .coding-standards-structure (or the user explicitly asked to restructure / review the structure — see Step 4), build and confirm the structure map (references/structure-map.md) before dispatching Worker 1; pass it as STRUCTURE_MAP. When a record already exists and no such request was made, skip the map and pass the recorded structure as STRUCTURE — don't re-open the layout. Cross-feature structure (duplication, mislabeled nesting, split features, themed-cluster promotion) is found by diffing the real tree against the map — a per-file worker cannot see it.
- Workers never call
Write/Edit. They emit code/findings as JSON; only you write to disk. - Sequential, not parallel. Worker 1 (Structure) → Worker 2 (Quality) → Worker 3 (Failure); each one's
output is the next one's input.
- No retries past 2. Twice-failed validation or unparseable JSON → fall back to inline.
Step 7b — Inline branch: load the references, then apply
Before writing or reviewing any code, read these fully — once per session, not once per task:
- All eight
common/files (always):functions.md,naming.md,objects-and-data.md,
formatting.md, comments.md, error-handling.md, code-principles.md, structure.md.
- The resolved structure (from Step 4) — a
structures/<name>.mdvariant or
references/<framework>/structure.md.
Read them fully because the judgement lives in the worked examples, not the rule names. Recalling "keep functions small" misapplies FN-001; the examples show where the line actually sits.
Then apply each rule at its scope: common governs the inside of code; the resolved structure governs the outside (file placement, folder names, public APIs). When the two seem to conflict, that scope split is the answer — a framework entry file is both thin (the framework rule) and the function it exports is short (the common rule).
Modes: Write, Review, Fix
Write
Writing gets the same rule-by-rule walk as reviewing. Most common/ rules have no write-time hook, so a rule you don't name is a rule you don't apply. Seed these steps into the Step 6 task list. Step 1 runs once per area, not once per file.
- Read the existing code. Open the 2-3 files closest to where the new code lands: those in the
destination folder, else the nearest comparable feature in the same language. Note the project's verb for each recurring operation, what an entry file exports, how a feature divides into units, which domain error types exist, how imports are ordered. A rule beats the project's convention; the project's convention beats the default you would otherwise reach for. An existing file that breaks a rule stays as it is; a write task does not reorganize what is already there. In a genuinely new area you set the vocabulary, so keep it consistent across every file you write.
- Load the references (Step 7b): all eight
common/files plus the resolved structure. - Draft it. Get the behavior right first. A messy first draft is permitted (FN-012).
- Rewrite the draft. Reread each file as its first reader: split the function you excused at 25 lines,
replace the name you settled for, delete the parameter nobody needs.
- Sweep the rules one at a time. Walk
common/against what you wrote and file each rule under
applied, already met, or not applicable with a reason. Re-read the reference for any rule you had not considered before this step. Report the buckets in your reply, not in the file.
- Fix what you find now. A violation ships fixed or does not ship. Never pair one with a
TODO.
Write no comments by default (comments.md). Say it in the names and the shape of the code. A sentence earns a place in the file only when it carries what the code cannot — a constraint, a rationale, an external reference, a sharp edge. Never narrate the code, never narrate your edit, and never leave the conversation behind in the file: no as requested, no I think, no we could also, no untracked TODO, no emoji, no Note: preamble. If a thought belongs in the reply to the user, put it there and leave the file clean.
A separate model judges the comments a turn wrote before that turn can end (hooks/judge-comments.py), so a comment that doesn't earn its place comes back as a fix to apply, not a note to ignore. Write it right the first time and the judge stays silent — it costs one short model call per turn that touched code, and none at all on a turn whose files gained no comments.
Always author and modify code through the Write / Edit / MultiEdit tools — never by shell redirection (> file, >>, tee, sed -i, cat <<EOF > file). The enforcement hooks fire only on Write/Edit/MultiEdit; a file written through the shell silently bypasses every one of them. This is a real hole in the enforcement perimeter, and the only thing closing it for shell writes is this instruction — so honor it. If some tool genuinely must generate a source file outside the Write path, run python3 <skill-dir>/hooks/review-files.py <file> over the result and fix what it reports.
Review
Walk the rules systematically — don't freelance. Seed the Step 6 list first, the moment you know what's in scope and before you start reviewing; the numbered steps map onto it, and step 0 is the list's first item.
- **Resolve + record the structure — the first task item, done only when
.coding-standards-structure
exists. Above the scope threshold, and only when it isn't already recorded: if .coding-standards-structure already records the structure and the user didn't ask to restructure / review the structure, skip this** — follow the recorded structure and review code against it; don't re-open the layout. Otherwise build the structure map (references/structure-map.md) over the tree, confirm it with the user once, persist it to .coding-standards/structure-map.md, and record the resolved layout in .coding-standards-structure. Hand the map to the structure worker as STRUCTURE_MAP: cross-feature findings (duplication, misnested peers, split features, themed-cluster promotion) are diffs against it, which a per-file pass can't see. Skipped below the threshold — say that cross-feature structural checks were not run.
- Scope + resolve structure. List each file in the diff; detect its framework (Step 3) and resolve
structure (Step 4).
- Load references. Every
common/file, plus the resolved structure for each framework in the diff. - Judgement pass — the rules regex/AST can't catch (FN-001 length nuance, FN-009 CQS, OD-003
Demeter, EH-002 boundaries, the CM-* comment-prose rules, the structure.md rules). Per rule, report PASS, a finding as file.tsx:42 — <rule> — <what's wrong>, or SKIPPED — <why it doesn't apply>.
- Run the hooks as a linter (deterministic pass, LAST — don't skip). The
block-*.pyhooks only fire
on Write/Edit, so a review must invoke them explicitly: ``bash python3 <skill-dir>/hooks/review-files.py <file> [<file> ...] # diff: git diff --name-only | python3 <skill-dir>/hooks/review-files.py --stdin # pipeline: add --json and parse per file ` It applies the same write-time contract (any, Hungarian, 4+/5+ args, swallowed errors, junk-drawer paths, deep imports, the TS/Python AST checks) and skips excluded files. review-files.py self-selects an interpreter that can load the TS/JS grammars (its bundled venv if the launching python3 lacks them) and flags the gap loudly if none can — it never silently reports a TS file clean. Every finding it returns is a violation to fix: the existence of the finding is deterministic and never re-litigated (an any is an any). For the ST-008 decl-count block, the remedy is the reviewer's judgement — a cohesive split, OR a recorded exemption (.coding-standards-ignore + reason, logged accepted`) when the file is one cohesive job the proxy miscounts. A split that creates scatter or copies a sibling's machinery is itself an ST-008 + DP-007 violation, not a fix.
- Merge, write the report, summarize. Combine judgement + linter findings into one list, ordered by
file then rule code. There are no severity tiers — every finding is a violation to fix. A finding's only non-fix exit is at Fix time: accepted (the reviewer judged it is not a violation here — reason required) or deferred (a real breach left open). At review time the call is binary: a rule breaks (file it) or it doesn't (it's a pass, not a soft "consider"). Persist the merged result to a report file per references/review-report.md (.coding-standards/reviews/<timestamp>.md, gitignored) and tell the user the path — every review writes one, inline included. The report's Structure baseline: field is mandatory and names the recorded .coding-standards-structure (or declares NOT RECORDED with a reason) — see references/review-report.md. Above the scope threshold (defined once in orchestrator-pipeline.md → Fix mode), trim the chat output to the shape in review-report.md; the report file always holds everything.
- Verify the structure baseline, then never silently skip a rule. After writing the report, run
python3 <skill-dir>/hooks/check-review-report.py <report.md> (pass --root <sub-project> in a monorepo). Exit 2 means the report asserts a structure with no .coding-standards-structure behind it — the structure step (Step 4 / step 0) was skipped: resolve + record it and rewrite the report before reporting done. Exit 1 is a declared skip — surface the reason. This is the back-stop for the task-list item: the report can't read as complete while claiming a baseline that was never written. And if you didn't check a rule, say so — a review with hidden gaps is worse than one that admits its scope.
Fix
Triggered by "fix the findings" / "apply the review" / fixes requested right after a Review — or by "continue the fix" / "resume the fix" to pick up a non-done milestone plan. Fix mode always runs as the orchestrator pipeline (MODE: fix) — it's inherently multi-file: it fans out one fix-agent per file, tracked by a completeness ledger so nothing is silently half-fixed. Don't offer a "single agent" option; if Agent is unavailable, run the documented sequential-batch fallback and say so.
Every finding ends fixed (violation removed), accepted (judged not a violation — reason required), or deferred (real breach, not fixed — an open breach). A run with open breaches reports done-with-open-breaches, never done, until the user resolves them — see references/fix-plan.md.
The input is the most recent .coding-standards/reviews/<ts>.md; if none exists, run Review first. Everything else — the ledger, the per-file fan-out, the scope threshold, and the milestone-driven plan file for big fixes (references/fix-plan.md) — lives in references/orchestrator-pipeline.md under "Fix mode".
Finding a rule on demand
Rules are organized by the kind of question you're asking (functions, naming, objects/data, formatting, comments, error handling, principles, structure), not by language. The full question→file index is in references/rule-index.md — use it for "what does FN-005 mean?" lookups or when unsure which reference owns a concern. During real work Step 7 already loads everything, so the index is just for targeted lookups.
On conflict — the simpler correct design wins
When two designs are both correct and pull apart on other principles, DP-006 (KISS) breaks the tie: the simpler one wins, even if it mildly bends DRY or another rule. KISS is a tiebreaker, not a trump card — it never overrides correctness, a real requirement, or SOLID. You cannot invoke it to skip a class per variant you were handed (OD-002), swallow an error, or ship a silent fallback. Adding a pattern (Strategy, Visitor, a fifth layer of indirection) just to satisfy a rule? Stop — the simple version is usually right. Defending a god-file or a duplicated switch as "simple" to avoid the structure? Also stop — that's not simpler, just smaller.
What this skill does NOT cover
- Performance tuning — use a profiler; clean code is fast enough by default.
- Security review — use a dedicated security skill (e.g.
security-review). Clean-code review catches
some bug classes but is not a security audit.
- Test design — tests follow the same clean-code rules, but TDD/BDD/mutation strategy is out of scope.
- UI/UX visual review — a separate domain (
web-design-guidelines,design-taste-frontend). These
frameworks cover code organization, not visual design.
If the user asks for any of these, say so and point them at the right tool.