8000 design-os/CLAUDE.md at main · jangtrinh/design-os · GitHub
[go: up one dir, main page]

Skip to content

Latest commit

 

History

History
168 lines (145 loc) · 12.6 KB

CLAUDE.md

File metadata and controls

168 lines (145 loc) · 12.6 KB

CLAUDE.md — ease-design

Guidance for Claude Code (and any agent CLI) working in this repository to develop ease-design itself.

This is a runtime-neutral config stub. The real per-runtime adapters that teach a host CLI how to use ease-design as a design tool are generated by ui init (Phase 6) — they are not this file.

What this project is

ease-design is a multi-runtime design-cli. See README.md for the concept and the implementation plan (kept in the internal design-engine repo) for the 8-phase plan.

Two sources of truth

  • knowledge/ — plain-Markdown knowledge core. The host model reads these files directly; never duplicate their content into code.
  • ui binary (src/cli.ts → dist/cli.js) — deterministic, no LLM calls. All non-LLM work (autofix, layout validation, token compilation, color math, registry, export) is a ui <subcommand>, callable via Bash and identical on every runtime.

Constitution

The canonical governing principles live in .specify/memory/constitution.md (spec-kit). Specs for new work live in specs/NNN-slug/ (committed — any machine resumes); cross-machine task state lives in GitHub issues (stage:* labels). The rules below summarize repo practice; the constitution wins on conflict.

Working rules

  • Build / check before committing: npm run typecheck, npm run lint, npm run build, npm test — all must pass (CI runs the same).
  • Keep the binary deterministic: pure transforms, no network, no model calls.
  • Read selectively — open the knowledge/ file relevant to the task, not all of them.
  • Locked decisions live in .specify/memory/constitution.md (ratified from the original brainstorm); do not relitigate them. The pre-spec-kit brainstorm archive is single-machine under plans/ and may not exist on every clone.

Post-merge documentation protocol (README + changelog)

A merge to main is not done until the README and changelog reflect it. Run this as the final step of any task that merges — ideally BEFORE the merge commit so it ships in the same push (if already pushed, backfill in a follow-up commit). README carries two jobs on different clocks: a slow, taste-sensitive marketing body and a fast, mechanical changelog — never mix them.

  1. Classify the merge (significance gate — most merges stop cheaply here):
    • INTERNAL — refactor/fix with no user- or agent-visible change, no story change → update nothing.
    • USER-VISIBLE — observable behavior, flags, or output changed → changelog only.
    • STORY-CHANGING — a new capability, a proof-level/claim change, a new demo, or a brand change → changelog and README marketing body.
  2. Act by class:
    • USER-VISIBLE / STORY-CHANGING → append a dated entry to CHANGELOG.md (newest first, Keep-a-Changelog verbs) and add a top row to the README ## Changelog recent-wave table (Date | Change | Commit). Keep that table to the recent wave; retire old rows to CHANGELOG.md. This path is mechanical (Sonnet-tier).
    • STORY-CHANGING only → also edit the README marketing body (hero, proof narrative, demos). This surface is taste-sensitive: Opus edits, Fable audits against brand/ + knowledge/taste-rubric.md. Never let changelog noise leak into the marketing body.
  3. Marketing prose is expensive; the gate exists so only story-changing merges pay for it. When in doubt between USER-VISIBLE and STORY-CHANGING, do the changelog line and ask before touching the hero.

Hard-won rules (from the dogfood loop — each cost a real bug)

  • A standard needs an emitter AND a linter. Prose-only standards drift — our own compiler violated the paired convention the knowledge already taught (L7). When you write a standard, ship the code that emits it and the check that fails without it, same commit if possible.

  • Every phase budgets one run on real data before it's "done". All eight dogfood findings (L1–L8) came from real projects/files, zero from fixtures. A green suite on a fresh fixture validates the mechanism, not the contract.

  • A missing-rule bug is fixed at the shared layer. Ask "which other consumer has this blind spot" first — the redirect-stub rule patched only into a11y-lint resurfaced in validate-layout (L1→L4). Extract the helper; don't patch where it surfaced.

  • Generated artifacts run the FULL linter set in their own tests. The generated specimen page shipped an unguarded animation because its gate ran 3 of 4 linters (missed taste-lint).

  • A field report is a symptom, not a diagnosis. Triage at the source before you spec. Of the dana dogfood's 11 findings, two were misdiagnosed — F5 said --tokens was discarded (it persists as tokensUsed; the probe read the wrong key), F10 said the scan "can't see a UI below root" (it can; it spends its 4000-entry budget depth-first and alphabetically, then gives up silently). Worse: we re-ran F5's own probe script and reported "confirmed". A reporter without the source guesses causes; that is not a flaw in the report, it is what a report is.

  • If it looks like a hole, first assume it is a decision — then go find the sentence that made it. Nine times in one session a deliberate design read as an accident: the alphabetical walk (project-scan.ts:6 — it is Art I determinism), the mode compiler "dropping" $extensions (figma-ds-tokens.ts:14 — "preserved and documented"), the registry's two doors (figma-ds-registry.ts:13 — the strict name pattern "exists for code-authored components, not scanned Figma inventory"), slice(0,5) (declared at :24), npm-link (README:132). Every one had a sentence next to the code saying why. Grep-sized reads manufacture false findings.

  • Count before you target. A headline number is a hypothesis. In one session: a 20× coverage gap measured 3.5×; "68 missing components" measured 25; ten plausible SKIP_DIRS additions measured 0/9 while the one that mattered (.venv) was 8,187 files — 54% of a real repo's tree. Counting the breakpoint scale did not just confirm Tailwind's values, it proved the corpus uses rem — the accessible choice a guess would have missed. The cheapest rule in the repo; it paid three times before a line of code was written.

  • A report is not evidence. Re-run the gate yourself, where it actually runs. An executor reported "4 pre-existing failures"; main was clean — it had committed before stashing, so the stash was a no-op. And a git checkout refused by a worktree silently gates the wrong tree: our own "GATE PASS" was theatre until it ran inside the worktree (the tell was two branches reporting the identical test count).

  • A test can enshrine the bug, and a test can assert its own filesystem path. ds-round-trip.test.ts was named "init → context → registry add → DS_TAMPERED" and asserted the tamper as correct — every green CI run certified that registering a component must break the design system. Separately, an unanchored /F\d+/i over a wrapper that embeds an absolute path passed for months and failed only from a worktree whose hash held f826. When a gate result surprises you, suspect the assertion before the code.

  • A mock must reproduce the STRICT API contract — what throws, what's required — not the happy path. Two spec-005 bugs survived a green suite because the test mock was more lenient than Figma: the sync .mainComponent getter throws under documentAccess: "dynamic-page" (mock returned a value), and set_name rejects an empty/undefined name (mock accepted it). Both only surfaced on the first live round-trip. When you mock an external API, encode its refusals; a permissive mock is a green light that means nothing.

  • A feature branch is a time machine. Anchor every measurement on main, and fetch first. A branch 59 commits behind reports a world that stopped existing weeks ago. In one session it produced four wrong facts: the package version (0.1.0; main was at 0.2.0), a dead figma-agent reference in setup.sh (main had cleaned it up), a "complete precedent" that turned out to live in zero commits, and a spec number (022) already taken on main by a different feature. Read with git ls-tree main <path> / git show main:<path> / git grep <pat> main -- <path>, never the working tree — and remember local main itself goes stale: git fetch before you branch, or you build on a tip that moved yesterday.

  • A zero is a claim. Prove it with a probe that CAN go red. git grep -E "\bvsf\b" returned nothing and "Ring 0 is clean" went into a design doc; POSIX ERE has no \b, so the pattern matched an empty string. git grep -P found 21 hits. Same session, same class, second form: zsh does not word-split an unquoted $FILES, so a rewrite loop silently received one giant argument. Before trusting an empty result, feed the probe an input you know should match. A search that has never returned anything has not been shown to work.

  • Baseline before you blame your own change. A full suite came back with 5 red files after a port. Four of them were red on clean origin/main before the change, and the fifth passed 92/92 in isolation — it only timed out under full-suite parallel load. Cost of finding out: one throwaway worktree at origin/main and one isolated re-run. main is not assumed green; measure it, then diff against the measurement, and say which failures you inherited.

  • A repo-wide rewrite needs an explicit file list, not a pattern. A perl -pi renumbering spec 022 → spec 028 for one feature also edited tests/spec-022-lifecycle.ts, which belongs to an unrelated spec that legitimately owns the number. Enumerate the files first, eyeball the list, then rewrite — and re-audit every touched file afterwards, because the damage looks exactly like the intended change.

  • "Not merged" is not a finding — squash-merge makes every merged branch look unmerged. A squash lands new content under a NEW commit, so the branch's tip was never an ancestor of main and git merge-base --is-ancestor reports it unmerged forever. Sweeping 15 abandoned worktrees, that heuristic flagged 12 of 12 merged branches as unmerged; grepping commit subjects was no better, because a merge reworded them. Two probes settled it: gh pr list --head <branch> --state all for the PR's real state, and git grep for a distinctive symbol the branch introduced — is the CONTENT on main, whatever commit carries it. That pair found the one branch in fifteen that genuinely still held work (a universal AGENTS.md runtime adapter, four files absent from main) and one uncommitted guard worth recovering. Before deleting a branch, prove its content landed; never infer it from ancestry.

  • A GREEN assertion is a claim too. Feed it an input that MUST turn it red. The mirror of "a zero is a claim", and it cost a false green in the same repo: a test asserting the emitted build script contains -an stayed green after -an was deleted from the ffmpeg call, because it matched the comment explaining -an two lines above. The probe caught the command's documentation, not the command. So when the file under assertion holds both instructions and prose — a script with # comments, HTML with <!-- -->, config with doc-strings — strip the commentary before matching and anchor to the line shape (/^\s+-an /m), never toContain over the whole file. And break every knob, not a sample: three of the four breaks in that session went correctly red, so stopping at two would have certified a guard that does not guard.

[IMPORTANT] ES Workflow (mandatory)

  • Every coding task: apply the /es-lazy ladder — reuse codebase → stdlib → native platform → installed dep → one line → only then minimum code. Deliberate corner-cuts carry an es-debt: <ceiling>, <upgrade trigger> comment.
  • Every feature request: route through /es-feature-dev (classify → brainstorm → plan → dev → review). Never skip straight to code on non-trivial work.
  • Use CONTEXT.md canonical terms in all naming (code, plans, commits); never use terms listed under _Avoid_. New terms crystallise via /es-brainstorm grilling.
  • An es-* skill misled or lacks a rule? Do NOT patch the skill mid-task — record a gap in the evolution ledger (/es-librarian branch record); /es-librarian run graduates it with a judge + human merge.
  • End of ANY substantial task (pipeline or not): Reflect — distil AT MOST ONE durable lesson. Product lesson → .brv/CONTEXT.md; kit lesson → gap; nothing durable → record NOTHING.
0