Designing Init Rules Skill: Layered Pack and Idempotency
A 199-line spec for the init-rules skill: layered universal + per-stack packs, CLAUDE.md as an index, and idempotent re-runs the Go generated-code and Copier way.
TL;DR
Starting from a bloated root CLAUDE.md, the author split rules into on-demand, path-specific files instead of one massive doc. The fix became a layered, harness-first skill pack with universal, stack-specific, and scaffold layers plus assert-based self-checks. To make reruns safe, the generator owns all artifacts like Go's generated-code marker, enabling idempotent smart merges that preserve local changes.
I was writing a spec in specs/2026-09-27-init-rules-design.md, 199 lines of it, for a repo onboarding feature using AI rules. This skill would accompany plane init, delivering rule layers to a new repo in one go. Initially, I had a wrong assumption. I thought one massive CLAUDE.md file in the root was enough to hold all instructions, and rerunning the generator on an existing repo was perfectly safe.
That assumption turned out to be a massive headache.
The Trap of One Massive File
When a session starts, the root CLAUDE.md is indeed loaded as persistent project instructions [1]. But if its contents become a messy mix of static facts and long, multi-step procedures, the file gets heavy and hard to manage. Procedures that are repetitive and already too complex for CLAUDE.md should be separated into skills or rules tied to specific paths [2].
This is where I realized the single-file approach just isn't scalable. I needed a way for the repo to pull only the rules it actually needs, rather than swallowing all documentation at once. Generator output that piles every rule into one place becomes a file that is hard to maintain and prone to conflicts.
Separating Facts from Procedures
The solution was a strict separation. I changed the function of the root CLAUDE.md to act more like an index or a signpost. Specific rules stay in their respective paths. This makes sense because CLAUDE.md files in subdirectories are only loaded on-demand when the AI reads a file in that location [1].
From here, the concept of the init rules skill layered pack was born. I divided the structure into three layers. First, universal process rules covering vertical slices, testing, and debugging. Second, per-stack packages for Go, Next.js, or MySQL. Third, empty scaffolds for deployment needs or project constraints. All of this is built harness-first: SKILL.md plus scripts/selfcheck.sh are written first, then the pack contents. The selfcheck is assert-based, verifying rule file frontmatter, placeholder closure, pack manifest, and seed data leaks.
The skill itself is only loaded when used [2]. This follows the open Agent Skills standard, where only six frontmatter fields are portable outside a specific ecosystem [4]. So, the target repo isn't burdened by instructions irrelevant to its stack. I intentionally limited the rule content to English only, ensuring consistency and easy processing by any AI model.
Go-Style Idempotency
The second problem I encountered was the safety of rerunning the generator. I ran into issues when trying to run the generator on a repo that already had new features. Without a clear mechanism, the generator would overwrite local changes not tracked in the template.
I eventually adopted a strict artifact ownership pattern. The generator repo, my-plan, owns all rule artifacts. Meanwhile, the consumer repo acts only as a read-only seed for the generated parts. This is very similar to generated code conventions in Go, where the line ^// Code generated .* DO NOT EDIT.$ is an unbreakable law [6].
With this pattern, rerunning the initialization process becomes idempotent. Template changes are handled with a smart-merge that respects project evolution, much like how Copier preserves user answers via a .copier-answers.yml file [7]. If there is a conflict during an update, it signals a manual change that needs review, not something to be blindly overwritten.
This design isn't about creating the most comprehensive rules, but the most manageable ones. Letting the consumer repo stay clean and only pull what is necessary turns out to be far more sustainable than forcing a single master file trying to accommodate every possibility.
Sources: