CLAUDE.md · git:20260901.f0ccc20 · 2026-09-01 · sha256 ce17a239247c0d4d

CLAUDE.md git:20260901.f0ccc20A

Immutable. This exact content is served forever at /api/v1/blob/ce17a239247c0d4d.

# Working in this repository

A skill and the documents describing it drift apart silently: the skill runs either way, and the
document only fails when somebody trusts it. So changing a skill is not finished when the skill
works — it is finished when the sentences that describe it are still true.

## After changing anything under `plugins/`

1. **Changelog and version together.** A line in `plugins/<p>/CHANGELOG.md` saying what changed and
   why, and the version in `plugins/<p>/.claude-plugin/plugin.json` raised. `verify.py` fails if a
   version has no entry — an installed version nobody can read the reason for is the worst of the two.
2. **Open the documents that carry a claim about what you changed.** Which ones, by what you touched:

   | Changed | Open |
   |---|---|
   | What a skill does | its row in `plugins/<p>/README.md`, and in both root READMEs — the skills table **and** the paragraph that describes it |
   | What a skill needs — a tool, a token, a connector | root READMEs: *Prerequisites* and *What each skill needs* |
   | A check `/fig:lint` runs | root READMEs: *What `/fig:lint` looks at* |
   | Anything crossing `fig` ↔ `pm` | root READMEs: *Where the two meet*, and `.github/two-plugins.html` if the picture names it |
   | A default that ships on or off | wherever a README states that default — a promise turned off in code stays a promise in prose |
   | A config key a document names | `_common/conventions.example.yaml`, whose comments are the schema's own guide |

3. **Both root READMEs, every time.** `README.md` is the original and `README.ko.md` is the same
   document, not a summary of it. Write the English first, then carry the change over.
4. **Generalize it before it ships.** Whatever prompted the change is one team's instance of it,
   not its definition. A rule goes in as the general shape — a canonical page and an archive of it,
   a tracker that may be any tool, a state list each team writes for itself — and the value that
   made you think of it goes in `conventions.example.yaml` as an example, or nowhere. Where a
   convention cannot be assumed at all, the key ships `null` and the check is skipped, rather than
   firing on every file that does it differently. This is the step with no checker behind it: the
   string check below sees names, never framing.
5. `bash tools/verify-all.sh` — every check CI runs.

## What the checker catches, and what it cannot

`tools/verify.py` catches the mechanical half: a skill missing from a README's skills table, a
command in one language's README and not the other, a section count that has drifted apart, a config
key or a skill reference that does not exist, a version with no changelog entry, a team's own value
left in a shipped file.

It cannot read a sentence and tell whether it is still true. Every drift found by hand on
2026-09-01 was of that kind — a README still promising a Figma write that had turned out to be
impossible, a token described as one skill's when a second had started needing it, a lint table
naming one component check when there were three. Nor can it see framing: a rule written around the
way one team happens to work passes every string check there is. A green `verify-all` means nothing
was left undeclared — steps 2 and 4 are what make it true, and true for somebody else's team.

## Conventions

- Commit messages are Korean, in the form `type(scope): what changed, in a sentence`; the body says
  why, and what was tried. Documents and code comments are English.
- Prose never carries a skill count — it goes stale the day a skill is added, and `verify.py` fails
  on one. List the skills, or say "the skills".
- The example config is a schema **and** a guide: a new key arrives with the comment explaining what
  it governs and what happens when it is `null`.
- `_common/scripts/lib/resolve-config.py` and `preflight.py` are copies in each plugin, byte for
  byte. Change one, copy it across — `verify.py` compares them.
- Anything belonging to one team — a brand colour, a product name, a document id — never ships.
  `tools/team-strings.local.txt` is what the check reads; it is gitignored, so a fresh clone skips
  that check and says so. It covers the files inside a plugin, and the ones outside it that are just
  as public: this file, `tools/`, and the diagram sources under `.github/`.