Commit Graph
6 Commits
Author SHA1 Message Date
bastien 7c05f75eab feat(21st): replace the magic MCP with the @21st-dev CLI + skill pack
Upstream supersedes `@21st-dev/magic` with `@21st-dev/cli` (bin `21st`):
same endpoint, `21st login` in place of an API key, no MCP process loaded
into every session.

- install-plugins.sh Step 8.7: `npm i -g @21st-dev/cli` (pinned in
  plugins.lock.json), staged `21st skills install`, TTY-only login offer,
  pack disabled by default. update-all.sh 7.4 refreshes both.
- The documented `21st install-skill` cannot be used: the installer refuses
  to follow a symlink on the target path and `~/.claude/skills` is one. The
  install runs under a throwaway HOME and the result moves into
  skills-external/21st-* (gitignored), symlinked on demand.
- toggle-external.sh manages `21st` as a pack (names globbed from
  skills-external/21st-*, parked under plain names). `magic` is gone.
- The 5 design skills join design/web/web-full/full and MANAGED_EXTERNALS;
  21st-registry and 21st-design-sync stay parked. MANAGED_MCPS is now empty
  and profile.sh's dead magic branches are removed.
- Design gate: GATE-BLOCK gains `21st` (required-manual, magic's old slot)
  and `21st-ui-build`; PATH repair extended to the npm global bin.
- settings.json: the 4 mcp__magic__* ask entries go; the outward-facing
  21st verbs land in autoMode.soft_deny, the tier that holds under auto
  mode (LRN-153).
- Docs: README, CLAUDE.global.md, design-gate.md, profile SKILL.md,
  .env.example, .gitleaks.toml, link.sh. BDR-093, LRN-158.

Tests: profile-set-managed 17/17, make test green except 2 pre-existing
gitflow FAILs (gitleaks binary absent on this host), shellcheck clean.
2026-09-22 02:53:31 +00:00
Bastien Chanot 5511c51a8e job4: seam design-tool-gate.sh
Env-var-only seams (§3.2), zero logic change (diff is only the seam
lines): PROFILE_SH gains DESIGN_GATE_PROFILE_SH override; CLAUDE_BIN
replaces the 5 bare `claude` invocation sites (ensure_claude_on_path's
reachability check + the plugin/mcp branches of tool_active — 2x
`command -v claude` + `claude plugin list` + `claude mcp list`). The
`echo "... claude mcp list  claude plugin list"` hint in the
READY-BUT-UNVERIFIED message stays literal (advisory text for a human,
not an invocation).

Unlocks (BACKLOG, not built here): a hermetic gate suite via
DESIGN_GATE_PROFILE_SH + CLAUDE_BIN injection, promoting J4-21.

Verified: bash -n clean, shellcheck clean, `design-tool-gate.sh design`
against the real repo unchanged ("READY"), full `make test` exit 0.
2026-07-06 21:46:19 +02:00
Bastien ChanotandClaude Opus 4.8 f96331844c fix(design-gate): resolve claude on a sanitized PATH (real cause of unknown)
The "unknown -> exit 11" path triggers when `command -v claude` fails. Root
cause is NOT the interactive alias (claude->dtach_claude) not surviving the
subshell — that's true but harmless: the real binary is on the inherited PATH,
so `command -v` finds it in a normal `bash script.sh` (proven: toggle-external
and the gate both resolve claude). The actual lever is PATH carrying the nvm
node bin. A skill/hook that shells the gate out with a sanitized PATH, or a node
upgrade moving the version-pinned nvm path, loses it.

ensure_claude_on_path(): if `command -v claude` already resolves, do nothing;
else probe known install dirs (~/.claude/local, ~/.local/bin, /usr/local/bin)
and the nvm glob, prepending the bin dir — which carries BOTH claude and its
node runtime (claude's shebang needs node, same dir). nvm keeps old versions
after an upgrade, so pick the newest that ships claude via sort -V, not the
first glob match. If nothing resolves, command -v still fails -> unknown ->
exit 11 (fail-visible net stays).

Verified: shellcheck clean; normal PATH -> READY exit 0 (function returns early,
no regression); PATH=/usr/bin:/bin (sanitized hook) -> now resolves claude via
the nvm glob and reports REAL magic state (READY exit 0), where before the fix
it was exit 11 unknown.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 12:12:49 +02:00
Bastien ChanotandClaude Opus 4.8 4d191354c6 fix(design-gate): fail-visible on unverified tools (no silent READY)
When `claude` is unreachable, the plugin/MCP checks (magic, ui-ux-pro-max —
the most important design tools) return `unknown`. The old verdict folded that
into a silent `READY` exit 0 — a fail-OPEN: the gate granted "proceed" exactly
when it had verified nothing. Contradicts the fail-closed-on-uncertainty rule.

Now fail-VISIBLE (not strict fail-closed — claude can be merely slow, false
blocks would get the gate ignored): a third outcome READY BUT UNVERIFIED with a
distinct exit 11. It proceeds, but says so loudly and names the unchecked tools,
telling the user to confirm with `claude mcp list` / `claude plugin list`. The
distinct non-zero code also stops a naive `if gate; then proceed` shell caller
from silently passing. Also covers the non-reproducible "transient claude absent
after apply" flake seen in live testing.

design-gate.md §DECISION updated: exit-code line + a real branch-3 state for 11.

Verified: shellcheck clean; reachable+active -> READY exit 0; claude off PATH ->
READY BUT UNVERIFIED exit 11 naming magic+ui-ux-pro-max; magic off -> INCOMPLETE
exit 10 (trip branch intact after the restructure).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 11:57:48 +02:00
Bastien ChanotandClaude Opus 4.8 131d0bcb5d feat(secrets): .env source-of-truth in ~/.claude + repo symlink
Move the real secret out of the git tree: the key lives in ~/.claude/.env
(outside the repo), and link.sh symlinks repo/.env -> ~/.claude/.env so
`source "$REPO/.env"` resolves transparently. The secret never enters git —
not as content (it's a link) and not by accident (gitignored).

link.sh: add link_env() — verify ~/.claude/.env exists + has MAGIC_API_KEY
(warn, never create/copy the secret), then create repo/.env -> ~/.claude/.env.
Defensive + idempotent: links only when repo/.env is absent or already the
right symlink; a residual REAL repo/.env is left untouched with a migrate hint
(never clobbered, so the secret can't be destroyed).

.gitignore: harden .env -> .env + .env.* + !.env.example (covers .env.local,
.env.bak, .env.save; keeps the template tracked).

Messages point at ~/.claude/.env (the canonical edit location) instead of the
ambiguous $REPO/.env: design-tool-gate.sh gate output, design-gate.md
(branch 3 + IMPORTANT), toggle-external.sh, install-plugins.sh.

Verified: shellcheck clean (link.sh, toggle-external.sh, design-tool-gate.sh);
link.sh created the symlink (1 change, idempotent re-run); repo/.env absent
from git status; magic-off path still exits 10 with the ~/.claude/.env hint.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 11:44:47 +02:00
Bastien ChanotandClaude Opus 4.8 3eefb8ad7c feat(design-gate): profile-based toolchain gate + design-tool-gate.sh
design-tool-gate.sh: deterministic design-toolchain state check. Reads the
design-core tools from design.profile's `# GATE-BLOCK:` allowlist + their
types via `profile.sh show design --plain` (claude-free parse contract),
checks each on its own channel (skill symlink / claude plugin list / claude
mcp list / command -v). Never reads disabledMcpServers. Exit 0 ready · 10
incomplete · 2 error.

Remedy is always a profile (/profile design), never an atomic tool toggle —
the profile system stays the single source of truth for activation. magic is
required-but-manual: it TRIPS the gate (not advisory) and the output names
the MAGIC_API_KEY step. Non-design tools bundled in the profile (browse,
plan-*, design-shotgun, graphify) are excluded from the trip via GATE-BLOCK,
so the gate fires only on real design tools.

design-gate.md: §DECISION rewritten profile-based (tier → run script → branch
on 3 groups), replacing the old atomic "ask user to activate ui-ux-pro-max".
§DETECTION unchanged. design.profile: add the `# GATE-BLOCK:` allowlist
(8 design-core tools); it is a comment, so read_profile/--plain are unaffected.

Verified: shellcheck clean; magic-off (real design profile) → exit 10 +
API-key line; all active → exit 0; browse-off (non-GATE-BLOCK) → exit 0,
no trip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 11:21:48 +02:00