Skip to content

One-door Plane onboarding: logic in one repo, data in every repo

Adityo Guni Waluyo

I almost copied a skill folder into every repository. The healthier shape turned out to be one onboarding door and tiny consumer footprints.

TL;DR

Instead of copying the skill to every repo, it stays centralized and consumers keep only light config and templates. This avoids maintenance debt since fixes happen once, while staying cheap because Claude Code only loads skill details when used. An offline helper handles scaffolding safely and idempotently, while online work runs through Plane's MCP tools.

My hand was already hovering over the keyboard, ready to type cp -r and duplicate the plane-init skill folder into a second repository. I stopped just in time.

My first instinct went like this: portability means every repo carries a full copy of the skill. Self-contained, no cross-repo pulling. It sounded reasonable right up until I imagined six months ahead. One bug fix in the skill, five repos to remember and update by hand. Copy-paste everywhere is not portability; it is maintenance debt in a nice wrapper.

So the final design flipped around: one-door onboarding. The plane-init skill lives in exactly one repo and is the only onboarding entry point. Consumer repos carry three light things: the .pm/plane.json config, markdown templates, and the project's own rules. No forks, no scattered SKILL.md copies.

Logic at the center, data at the edge

The decision has a small, honest reason: how Claude Code loads skills. A skill's body loads only when it is used [3], so piling long reference material into one central skill is cheap. Repos that rarely talk to Plane pay no penalty at all.

Everything that needs the network, like project resolution, module and label lookup, and work-item creation, belongs to the protocol in SKILL.md and runs through MCP calls. Plane's MCP surface offers 28 tools with 183 actions [2]. Meanwhile the init.py helper is deliberately offline, standard library only. It has no idea how to talk to a server, and that is the point: the part you can test without a network stays fully separate from the part that needs credentials.

I have firmed up my position on this for any skill that touches many repos: offline helper first, online protocol second, never the reverse. The online part is hard to test and the offline part does not need a server.

The selfcheck tests the output, not the template

One thing almost slipped while committing the templates: the selfcheck validates the generated scaffolding in a target root, not the template files themselves. Testing templates alone is false comfort. A good template does not guarantee a correct assembled result.

Inside the helper, a few details are easy to dismiss as trivia. Path safety via resolved-path containment, so relative paths from the config cannot escape the repo root. v2/v3 config validation. Config writes are atomic: write to a tmp file first, then os.replace. The scaffold never overwrites an existing file, and a re-run just reports state. Idempotent, so re-running is both cheap and safe.

Two favorite details of mine: the marker plane-doc-sync:v2 path=<rel> is a single line of plain text, and the hash sha256(normalize_body(text)) is computed with exactly the same rule in sync.py. The validation rule is even restated over there on purpose instead of being imported. Skills cannot import across skill directories, so disciplined duplication is more honest than a fragile import.

The concrete footprint in a consumer repo is tiny now: a .pm/ folder with the config, scaffolded templates, and a few rule lines in that project's AGENTS.md. No skill folder, no double-versioned scripts. The skill can evolve weekly while consumer repos live for years; two different life rhythms are safest when they are not merged into one.

An identity that does not wobble

On the Plane side, work-item descriptions carry a description_stripped field: plain text generated by the server, the stable identity plane [1]. The consumer-side marker and hash plug straight into that contract. Identity is stored as visible text, not HTML comments that a server-side sanitizer can sweep away. My probe run burned me on exactly that last time, and this whole onboarding design is built so it never happens again.

If a third skill ever needs to touch many repos, I will not start from "how do I distribute it" again. I will start from which logic deserves to live in one place and which data belongs in every repo. The rest follows.

Sources

Related articles