--- name: md-writing description: Use when creating, overhauling, or cleaning up CLAUDE.md / AGENTS.md files for a codebase or module — including tidying /init output or auto-generated drafts that describe the code instead of constraining behavior, when a rules file made a weak model refactor/edit things it should not have, or right after code changes that invalidate statements in an existing CLAUDE.md set (moved paths, new/changed extension points, retired rules) — run its update mode to re-sync. --- # Writing AGENTS.md / CLAUDE.md ## Overview An AGENTS.md/CLAUDE.md is a **law, not documentation**. Every line the model can already read from the code is noise that dilutes signal — and with weaker models (minimax-m3, small local models) a described fact reads as an invitation to "fix" it. Keep ONLY what the model cannot infer: intent, invariants, gotchas, prohibitions — PLUS the one class of facts models never infer: the **reuse map / feature inventory** (what already exists, where every piece lives, what to extend — see step 3). Inventory lines are routing, not noise; do not cut them under this rule. **Core principle: describe → constrain.** Facts get deleted; constraints stay. ## You are NOT reviewing the code A rules file records the **owner's decisions**, not your opinion of the code. NEVER turn "this should be 307 not 301", "externalize this to config", or "extract to a trait" into a MUST/NEVER — that is a code-review finding, not a recorded intent, and a weak model will execute it. Only record intent that (a) the user confirmed in the interview, or (b) a code comment / obvious construct proves is deliberate. If you believe something *should* change but no one confirmed it, leave it out (raise it separately as a review note, never as a rule). ## Two modes — pick FIRST, before any step - **Create mode** (no `.md` set exists, or user asks for a from-scratch overhaul): full workflow below, steps 0–6. - **Update mode** (a `.md` set already exists): do NOT rewrite from scratch — the existing files hold owner-confirmed intent you must not lose. Instead: 1. Find the sync baseline: the `Last synced: ` marker in root (fall back to the `.md` files' own last-modified commits). 2. `git diff ..HEAD` (or review the just-made changes) → list every `.md` statement the diff invalidates: moved paths, renamed symbols, new/changed extension points, retired or new rules, new modules with no `.md` yet. 2b. **Verify against the ACTUAL current state before asking anything — investigate, don't guess.** The diff tells you what changed; it does NOT tell you what is now true. Before forming any proposed update, open the current code and confirm each candidate change against reality: does the symbol/path still exist at the new location (grep it), is the new extension point really the canonical one (not a temporary/duplicate), did the logic actually change behavior or just move, does a `.md` statement you think is now wrong actually contradict the code as it stands now? EVERY question you ask the user must be one you could NOT answer yourself from the code — never ask to confirm something a 30-second grep settles. A question that reveals you didn't check is worse than no question. This is step-1's "investigate first" applied to update mode. 3. **Confirm BEFORE writing (always ask, never silent-write).** After a task changes code, do not touch the `.md` files until the user confirms. Present the proposed sync as a **plan-mode question in tiếng Việt**: list each `.md` file that would change and the one-line diff for each (what statement becomes what), then ask to proceed — with proceeding as the recommended answer, e.g. "Task vừa xong đổi `X`. Đề xuất cập nhật 2 file .md: `root` (đổi path A→B), `module/Y` (thêm extension point Z). Cập nhật luôn nhé? [Recommend: có]". Options at least: cập nhật tất cả / chọn file / bỏ qua. Do NOT write any `.md` before this confirmation. 4. On confirm, update ONLY the affected (and approved) files; new intent questions (if any) still go through the step-2 interview style — one at a time, recommended answer first, tiếng Việt. 5. Update the `Last synced:` marker, then run a light step-6 pass (one test request is enough). ## The workflow — do in order, do NOT jump straight to writing ### 0. Draft the raw facts — don't blank-page - **Root draft:** run `/init` (or open the existing file) for a raw scan of the project. - **Module drafts:** `/init` does not cover modules — dispatch **Explore subagents in parallel, one per module, in a single message**, each reporting that module's raw facts (structure, patterns, extension-point candidates, commands). On a large repo this beats sequential reading by a wide margin. Treat all drafts as **facts to be transformed**, never as finished files. The whole job below is turning drafts into constraints. ### 1. Analyze the source FIRST — investigate before asking Read the module before asking anything. Map the intent candidates yourself: what looks like a bug but might be deliberate, what looks duplicated/hardcoded on purpose, what build/test commands the files imply. Hunt SPECIFICALLY for reusable extension points — type-discriminated tables, enums, generic repos/services that new features should extend instead of duplicating; a missed one is the single most expensive omission (see "Reuse map" in step 3). Form your own best guess for each. NEVER ask a question you could have answered by reading code — investigate first, ask only to confirm what code can't settle. This step feeds the recommended answers in step 2. ### 2. Interview — one question at a time, each with a recommended answer Ask in **Vietnamese** (tiếng Việt), in **plan mode**, sequentially: ONE question, wait for the answer, then the next. NEVER dump a batch of questions at once. Each question MUST carry your recommended answer first (drawn from step 1's investigation) so the user can confirm with one tap instead of composing prose — e.g. "I read `LotteryService` as deterministic on purpose (not a bug). Correct? [Recommended: yes, intentional]". Aim ONLY for what code can't tell you: - What looks like a bug but is intentional? (deterministic where you'd expect random; duplicated on purpose; magic sentinel values) - What breaks silently if changed? (cross-repo contracts, fixed byte layouts, param whitelists) - What must NEVER be done here? (don't merge/refactor X; don't cancel a shipped order) - Which decisions are load-bearing vs incidental? - Build/test/deploy commands not obvious from the files. Verify each answer against real code + line refs; discard anything the model reads trivially. Stop asking when answers stop yielding non-inferable facts. ### 3. Plan the file list — get approval BEFORE writing Default is **multi-file, full coverage**: ALWAYS one `.md` at the project root, PLUS a `.md` in EVERY module/folder — not just the "important" ones. A single root file, or root + a couple of favorites, is the wrong default. The set doubles as a **structure reference**: someone (or a model) reading only the `.md` files can see what exists where and what to extend. **Reading protocol — lazy-load, never read-all.** The set is designed to be read on demand, not front-to-back: 1. Task arrives → read **root CLAUDE.md first**, before touching any source. 2. Root's routing table names the module(s) the task touches → read ONLY those modules' `.md` files — before reading that module's source, and never the `.md` of unrelated modules. 3. Only then open source files, and only the ones the relevant `.md` points at. Write the files so this works (root routes precisely; each module file is self-sufficient for its module — no "see also" chains forcing extra reads), and **state this protocol explicitly at the top of the root file** so any consuming model follows it instead of bulk-reading everything. **Check-before-create rule — every root file MUST contain it.** Alongside the reuse map, every root `.md` you write must carry this standing rule for future work: "Before creating ANYTHING new — table, function, class, variable, config key, file, endpoint — FIRST check whether something with the same purpose or reusable already exists: search the reuse map in the relevant `.md`, then grep the code. If it exists, extend/reuse it; do NOT create a parallel one. Only create new after confirming nothing covers it." The reuse map makes the check cheap; this rule makes it mandatory — the map alone did not stop agents from building parallel structures. **Anti-rot sync rule — every root file MUST contain it.** The set goes stale the moment code changes: paths move, extension points appear, rules stop being true — and a stale routing table is worse than no file (the model trusts it). So every root `.md` you write must carry this standing rule for future work: "Any change that makes a statement in any `.md` file wrong (moved path, new/changed extension point, retired rule) MUST update that `.md` in the same change." When YOU edit code in a project that has this file set, obey it too. Root also carries a **`Last synced: ` marker** — set it when writing, bump it on every sync; it is update mode's diff baseline. **Layering — root = routing map, module = deep:** - **Root** — answers "what is this project, how do the pieces connect, and which file do I read next?". Contains: project identity (domain + stack), an **architecture table where each row points to that module's `.md`** (columns: part | role | "details at → path/CLAUDE.md"), global cross-cutting rules only, and build/test commands. The routing table is the token-saver: the model reads root, jumps straight to the right module file, never scans the tree. - **Module files** — go deeper and more specific for that part: its function, requirements, business logic, technology, mandatory patterns ("new X MUST extend Y"), and its own gotchas/prohibitions. This is where detail lives; the module still obeys "constrain not describe" (every line non-inferable intent/gotcha/prohibition), just at finer grain than root. - **Reuse map / feature inventory — the highest-value content.** For each existing feature/capability, spell out in detail WHAT already exists and WHERE every piece lives, across all layers, so nothing gets rebuilt. Per feature: - **backend** — service/repo/controller paths (e.g. `app/Repositories/Blacklist/BlacklistRepository.php`) - **frontend/UI** — screens/components that expose it - **database** — tables, enums, migrations (e.g. `blacklist` table, `ListType` enum) - **how to verify it works** — endpoint, port, command, or admin screen to check it live - **extension point** — the imperative: "new list/blacklist types extend THIS table + THIS enum — do NOT create a parallel table" Field evidence: without this inventory, 3 of 4 test agents (including strong models WITH the .md) invented a parallel table; base-class rules alone did not prevent it. Strong models infer framework patterns on their own — what NO model infers is which existing domain structure already covers a feature and must be extended. Hunt for these in step 1 (enums, type-discriminated tables, generic repos/services, existing UI) and confirm each in the step-2 interview. - **Length follows content, not a quota.** Be detailed, complete, and accurate — include EVERY confirmed intent, invariant, gotcha, and prohibition, however many lines that takes. The only thing to cut is what the model can already read from code; never cut a real constraint to hit a size target. - **Handover test** for the whole set: a new engineer (or model) reading ONLY the `.md` files must be able to take a one-line feature request and land on the correct module, reuse the existing pattern, and produce a minimal diff — without tree-scanning or inventing parallel structures (e.g. a new table when an existing one covers it via a `type` column). Produce the full list: every dir/module and the constraints each `.md` will hold. No prose yet. Present it, get the user's OK, then write. ### 4. Rewrite the draft as constraints, not facts - **Imperative prohibitions** (KHÔNG / CHỈ / PHẢI / NEVER / ONLY), not descriptions. Weak models obey commands, not implied intent. - Every gotcha names its **consequence** ("…else the request is rejected by the Gateway before the action runs"). - "Looks like a bug but is intentional" → say so + why, or the model will "fix" it. - Anchor to **stable symbols** (class/method/table/enum names, e.g. `BlacklistRepository::updateListTypeFile`), not line numbers — lines drift after a few commits; use `file:line` only when no named symbol exists. On any duplication/smell you must mention, add **"describe current state — do NOT change."** - Module rules → that module's file (scope). Don't dump into root. **Calibrate to the weakest model, without handcuffing the strongest.** Write so that the WEAKEST model that will ever read this (a Haiku-class model, a small local model) can complete a hard task in this module without struggling: full context — every location, contract, invariant, verify command, extension point it needs — spelled out, nothing left to "it will figure it out". But supply that as **context + constraints + pointers, never as recipes**: state WHAT must hold, WHERE things live, and what is FORBIDDEN — do not dictate step-by-step procedures or paste ready-made code snippets to copy. A strong model must stay free to choose its own implementation; a weak model must never be missing a fact it needed. If a line prescribes HOW to write the code rather than what must be true of it, cut the how, keep the what. ### 5. Note on subagents Subagents do NOT auto-load CLAUDE.md files. If the workflow dispatches subagents (explore, implement, review), their prompts must explicitly say "read /CLAUDE.md first". When writing a root file, consider stating this so future orchestration doesn't run blind. ### 6. Verify with a weak model — the set is not done until it passes Writing the files is not the finish line; field evidence shows a set that LOOKS complete can still fail (a strong model with the .md still built a parallel table). After writing: 1. Pick 1–2 realistic one-line feature requests for this codebase (e.g. "Thêm chức năng blacklist cho địa chỉ IP") — ideally one the user suggests. 2. Dispatch a **weak-model subagent** (Haiku-class) with ONLY that request plus "read the CLAUDE.md files starting from root, follow their reading protocol" — no other hints. Plan-only is enough: ask it to output its implementation plan, not apply edits. 3. Grade against the handover test: correct module? reused the named extension point (no parallel structure)? followed the mandatory patterns? minimal scope? 4. Any failure = a missing or unclear line in the `.md` set, never the subagent's fault. Fix the file, re-run until it passes. Report the verification result to the user as part of delivery. ## Fact vs constraint — the test for every line | ❌ Fact (model reads from code → delete) | ✅ Constraint + intent (keep) | |---|---| | "Registered in `Application.php` in order 1,2,3" | (delete — model reads it) | | "`isHttps()` duplicated in two middlewares — extract to a trait" | "`isHttps()` duplicated ON PURPOSE. Do NOT extract — no shared base wanted." | | "CSP is permissive with `unsafe-inline`" | "CSP `unsafe-inline` is REQUIRED (jQuery/GA). Don't tighten without testing inline scripts." | | "`LotteryService` uses `getUserIndex() % ratioCycle`" | "Lottery is deterministic ON PURPOSE — NOT random. Don't 'fix' to `rand()`." | **Inventory paths are NOT noise.** "Delete what the model reads from code" applies to structure trivia (registration order, config keys). It does NOT apply to the reuse map: a path is only readable from code if the model already knows to look there — the inventory's job is exactly that routing ("blacklist already exists: backend at a, frontend at b, DB at c, verify via port 123"). Keep those. ## Checklist before writing any line 1. Can the model read this from code? → **delete** (noise) — EXCEPT reuse-map/feature-inventory lines (see above). 2. Is it invariant / intent / gotcha / prohibition? → if not, **delete**. 3. Right scope (module rule lives in that module's file)? ## Red flags — STOP, you're producing noise - Describing registration order, config keys, or file structure the model can list itself. - "Extract to…", "consider refactoring…", "should be tightened…", "use 307 instead", "switch to atomic increment" → **refactor bait / code review**; weak models execute it. A rules file is not a code review. - Turning your own opinion that code is wrong into a MUST/NEVER. If no one confirmed the intent, it's not a rule. - Speculative advice (Redis, production hardening) not tied to a real decision. - Bolting on a test framework where none exists. - Wrote files before the user approved the plan. - Asking questions before reading the code (should investigate first, ask to confirm). - Dumping many questions in one message (must be sequential, one at a time). - A question with no recommended answer (every question carries your best guess first). - Only a root `.md`, or `.md` for only a few "important" modules (default is full coverage: root + every module/folder). - A module file with no reuse-map line — no named extension point means the next feature builds a parallel structure. - Step-by-step recipes or paste-ready code snippets in a rules file — that's dictating HOW; state what must hold and where things live, let the model implement. - A weak model would still need to guess a location/contract/command after reading the file — context incomplete, add the missing fact. - Root file lacks the read-root-first / read-only-relevant-modules protocol, or module files cross-reference each other so a task forces reading unrelated `.md`s. - Root file lacks the check-before-create rule (search reuse map + grep before creating any new table/function/class/file; reuse if found). - Root file lacks the anti-rot sync rule ("code change that invalidates a `.md` line updates that `.md` in the same change") or the `Last synced:` marker. - Declared the set done without the step-6 weak-model verification pass. - Rewriting an existing `.md` set from scratch when update mode applies — owner-confirmed intent gets lost; diff and patch instead. - Writing `.md` changes after a task without first asking the user to confirm the proposed diff (update mode step 3 — always confirm, never silent-write). - Asking a confirm question in update mode without first verifying against current code (step 2b) — a question answerable by grep proves you skipped the check. - Root file holds module-level detail, or lacks the routing table pointing to module files — root is a map, not a dump. - Fails the handover test: a one-line feature request read against only the `.md` set wouldn't route to the right module + existing pattern. - File is long while the code is small → padding. ## Rationalization table | Excuse | Reality | |---|---| | "More context helps the model" | Facts dilute the constraints. Weak models drown. Cut. | | "/init already wrote it, just tidy" | /init output is descriptive facts — the exact failure mode. Rewrite as constraints. | | "Documenting the duplication is helpful" | Naming a smell without "do NOT change" reads as "please refactor". | | "Interviewing wastes time" | The non-inferable facts are the ONLY high-value content. Skip it → a file of noise. | | "I'll write all files then plan" | Plan first — writing before approval produces scope you throw away. | | "I'll ask first, then read the code" | Backwards. Read first, form a guess, ask only to confirm. Questions answerable from code are noise. | | "Batching questions is faster" | One at a time. A wall of questions gets skimmed; sequential plan-mode gets real answers. | | "The user knows the answer, no need to recommend" | Always recommend. A guess to confirm is one tap; an open question is homework. | | "The files look complete, verification is overkill" | Field evidence: a complete-LOOKING set still let a strong model build a parallel table. Run the step-6 weak-model pass. | | "The code is clearly wrong, I'll make a rule to fix it" | That's a code-review opinion, not a recorded decision. Leave it out or raise separately. Rules record confirmed intent only. |