Skip to content

/cairn-adopt-components

Backfill the Cairn component store into a project that was adopted before the component store shipped.

shell
$ npx -y skills add isaacriehm/cairn --skill cairn-adopt-components --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.
  • You can call itInvoke it directly when you want it.
  • Slash command/cairn-adopt-components
How auto-invocation works

Context preview

The summary Claude sees to decide when to auto-load this skill.

Backfill the Cairn component store into a project that was adopted before the component store shipped.

SKILL.md

cairn-adopt-components.SKILL.md
name: cairn-adopt-components
description: Backfill the Cairn component store into a project that was adopted before the component store shipped.
when_to_use: |
  Use when the operator wants the component store on a repo that already
  has `.cairn/` but no `components:` config (adopted before v0.18.0), or
  asks to "adopt components", "backfill the component registry", or "add
  `@cairn` headers". Drives detect → annotate → emit inline. Skip when
  the repo isn't adopted at all (send to cairn-adopt first) or already
  carries a built component store.
allowed-tools: Skill(cairn:cairn-attention), Task(component-annotator), Task(component-registrar), AskUserQuestion

Skill: cairn-adopt-components

Host portability

This skill is shared by Claude Code, Cursor, and Codex. Use the host's structured question UI when available; otherwise ask the same concise A/B/C question in chat and pause. Named subagent briefs live under `../../agents/`: dispatch by name where supported, or read the brief and pass it to the host's native subagent tool. Execute inline only when no subagent tool exists.

You are backfilling Cairn's **component store** into an already-adopted project — the one-time work the adoption pipeline does for fresh repos, applied to a repo that predates the store. The goal: every component file carries a `@cairn` registry header, the derived index is built, and `@singleton` headers become §INVs. Spec: `docs/PLUGIN_ARCHITECTURE.md` §6 (the component trio 9d→9e→9f) and `docs/COMPONENT_STORE_PLAN.md`.

This skill drives the bundled `dist/cli.mjs` internally using the plugin-root variable supplied by the active host (`PLUGIN_ROOT`, `CURSOR_PLUGIN_ROOT`, or `CLAUDE_PLUGIN_ROOT`). **Never surface a CLI subcommand to the operator** (Plugin spec §11) — the chat shows progress + consent gates, not commands.

Step 0 — classify the repo

Run this single probe to decide whether backfill applies. It is **ghost-aware**: a ghost-adopted repo has no in-repo `.cairn/` — its state lives out-of-repo at `~/.cairn/state/<root-commit>/`. The probe resolves the effective state home (in-repo when present, else the out-of-repo ghost dir keyed on the repo's root-commit) and prints `<mode> <verdict> <home>`:

node -e '
  const fs=require("node:fs");
  const os=require("node:os");
  const path=require("node:path");
  const cp=require("node:child_process");
  const root=process.cwd();
  let mode="committed";
  let home=path.join(root,".cairn");
  if(!fs.existsSync(home)){
    // No in-repo .cairn — may be ghost-adopted. Ghost state lives at
    // ~/.cairn/state/<repo-id>; repo-id is the move-stable root-commit SHA
    // (matches registerGhostRepo). Resolve it and probe there instead.
    let rc="";
    try{rc=cp.execFileSync("git",["-C",root,"rev-list","--max-parents=0","HEAD"],{encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim().split(/\s+/)[0]||"";}catch{}
    if(rc){const g=path.join(os.homedir(),".cairn","state",rc);if(fs.existsSync(g)){home=g;mode="ghost";}}
  }
  const cfg=path.join(home,"config.yaml");
  const idx=path.join(home,"ground","components");
  if(!fs.existsSync(home)||!fs.existsSync(cfg)){console.log(mode+" not-adopted "+home);process.exit(0);}
  let hasBlock=false;
  try{hasBlock=/^components:/m.test(fs.readFileSync(cfg,"utf8"));}catch{}
  const hasIndex=fs.existsSync(idx)&&fs.readdirSync(idx).length>0;
  console.log(mode+" "+(hasBlock&&hasIndex?"has-store":"backfill")+" "+home);'

The first token is the **mode** (`committed` | `ghost`); the third is the resolved **state home**. In **ghost mode**, every `.cairn/…` path in the steps below resolves under that home (the out-of-repo dir), NOT the repo root — the `node … cli.mjs components …` commands already resolve it automatically, so only the raw `cat` / in-place-edit snippets need `$CAIRN_HOME` substituted for `.cairn`. Export it: `CAIRN_HOME="<home>"`.

Branch on the verdict (second token):

  • **`not-adopted`** → the repo has no Cairn state. Surface one line:

"This project isn't adopted yet — run `/cairn:cairn-adopt` first; it builds the component store as part of adoption." End the turn.

  • **`has-store`** → a `components:` block and a built index already

exist. This is a **refresh**, not a first backfill — skip Step 1's detect (the config is already there), run **Step 2.5 (config-gap check)** first, then go to Step 3 (walk for newly-added un-headered files). Surface: "Component store already present — re-checking for un-headered components."

  • **`backfill`** → the normal path. Continue to Step 1.

Step 1 — detect + write the `components:` config

Run detection (LLM-driven + convention-agnostic — the same one adoption uses; it reasons over the repo's structure rather than probing a fixed list of conventional dir names, so any layout / monorepo tooling works):

node "${CLAUDE_PLUGIN_ROOT}/dist/cli.mjs" components detect

Read the stdout and branch:

  • **"No recognizable component layout found"** → the model found no

reusable UI components (e.g. a backend-only repo). Surface one line and end — there is nothing to backfill.

  • **"already carries a components: block"** → fall through to Step 2.
  • **"Wrote a components: block"** → the config now has a `components:`

block. If the output also says **"Monorepo detected"**, run Step 1.5. Otherwise continue to Step 2.

Step 1.5 — monorepo sharing (only when monorepo detected)

Every workspace is **isolated by default** — components in one workspace are OFF-LIMITS to the others. A shared UI library workspace (e.g. a `packages/ui` design system) should usually be `shared: true` so the whole repo may use it. Detection never guesses this (isolation invariant 3).

Read the workspace names from `.cairn/config.yaml` (`components.workspaces`). Render an `AskUserQuestion` (multi-select) listing the workspaces:

> Which workspaces expose their components repo-wide (a shared UI/design > library)? Leave all unchecked to keep every workspace iso

Read more
Read it on GitHub ↗

Showing the first part of this file.

Ships withcairn

Persistent ground truth for AI coding agents. First-class support for Claude Code, Cursor, and Codex. Stop agents from drifting.

Get the whole plugin, auto-invoked
Stats
6
Stars
0
Views
1
Forks
Active
Maintenance
TypeScript
Language
MIT
License
4d ago
Last commit
3mo ago
Created

Repo: isaacriehm/cairn