Skip to content

shared-asset-convention

Shared Assets Live Under .claude/, Never at the Kit Root

Section titled “Shared Assets Live Under .claude/, Never at the Kit Root”

A shared asset is a file two or more modules depend on and neither owns — a template, a fixture, a shared reference table. It lives at .claude/modules/<name>/_shared/, published by the module that maintains it. A kit-wide asset no single module maintains lives at the kit-level fallback .claude/_shared/. A bare kit-root _shared/ is a bug — file /t1k:issue against the owning kit rather than relocating it in a consumer.

Resolution order on a path collision is the existing 4-layer registry priority stack — module overlay p91+ → engine kit p90 → designer p50 → core p10 — identical to how t1k-routing-*, t1k-activation-* and t1k-config-* fragments already merge. Highest priority wins; a per-module _shared/ shadows the kit-level fallback at the same layer. Nothing may resolve a _shared/ path by scan order or extraction order, which is what makes the loser depend on which ZIP landed second.

Every _shared/ path is claimed by exactly one module (module.json). An unclaimed _shared/ file does not ship, exactly like an unclaimed script.

Every other kit-shipped asset — rules/, skills/, agents/, hooks/, scripts/ — already lives under .claude/. A bare kit-root _shared/ would be the only kit-shipped content outside that tree, so it complicates install-time flattening (rule-module-ownership.md § “flattening model”) and every doctor check that walks .claude/ for kit-owned content. Per-module-with-a- fallback is also the reversible choice: it can be narrowed or widened later without moving a consumer’s install path.

  1. Adding a shared asset — put it under the maintaining module’s _shared/, claim it in that module.json, and regenerate .claude/t1k-modules.json in the SAME commit (module-registry-sync.md).
  2. No module maintains it — that is usually a missing module, not a licence to go kit-root. Use .claude/_shared/ only for a genuinely kit-wide asset with no owner.
  3. Two modules want the same path — do not ship two copies. One module owns it; the other depends on that module.
  4. Referencing one — address it by its _shared/ path, never by a sibling module’s directory.

Collection into the release ZIP, install-time flattening, and the consumer-side resolution gate live in theonekit-release-action, which owns the registry merge — the gate half landed at release-action#154 and the rest is still to come. Until it does, this rule is the decision and the placement is advisory: core’s own tree is checked by hooks/__tests__/shared-asset-convention.test.cjs, which is not a consumer-side guarantee.

Explicitly not in scope: deletions[] is retired in favour of canonical-files.json and gate #20 (validate-deletions-removed.cjs) forbids re-adding it.

Rejected alternatives, the collision worked example, and the release-action wiring still owed: docs/shared-asset-convention.md.

rule-module-ownership.md (the same module-owns-it model for rules) · module-registry-sync.md (regenerating the rollup) · wired-not-just-present.md (assert the path the runtime loads, not the repo copy) · docs/shared-asset-convention.md.