Global: `make link` generates githooks/ from lib/gitflow.sh and sets git's
global core.hooksPath to ~/.claude/githooks, so every repo on the machine
runs the pre-commit protection and the post-commit / post-merge push, even
one that never ran gitflow init. A repo's own local core.hooksPath still
wins, so hooks/session-start.sh calls `gitflow reconcile-hooks` once per
session and rewrites a .githooks/ that lags the lib (LRN-114 automated);
the pre-commit exemption now covers .githooks/** next to .claude/**.
Per-repo opt-outs for a foreign clone: `git config gitflow.protect false`
(branch model) and `git config gitflow.autopush false` (push). Both, and
the GIT_CONFIG_GLOBAL= / GIT_CONFIG= env bypass, are static deny rules.
`make test` and the two suites that commit on main export
GIT_CONFIG_GLOBAL=/dev/null so the machine's global hooks never fire in
throwaway repos. doctor gains "Git hooks" (global setting, githooks/ equal
to the emitters) and "Scratchpad" (warn when TMPDIR sits on a tmpfs with
usrquota: systemd caps each user at 80% of it, which killed two shells
today, BLK-021). Tests: T18h, T19d, T20 (reconcile), T21 (whitelist and
protect opt-out); this repo's own stale .githooks/ refreshed.
make plugin never installed impeccable. The 3.2.0 pin had rotted upstream
(the CLI fetches its skill dist at install time; that release's zip is
gone), the --scope=project staging moved the skill dir alone and dropped
the 4 impeccable-* subagents, and /impeccable init was never announced.
Step 8d now installs at --scope=global straight through the
~/.claude/{skills,agents} symlinks into the repo (both paths gitignored),
guards on those symlinks existing, keeps a profile-parked copy parked,
falls back to @latest on a pin failure with a bump-the-lock warning, and
prints the per-project init hint. update-all.sh mirrors the shape.
Found while probing: with a copy already installed a rotted pin exits 0
("Could not check for skill updates ... left unchanged"), byte-identical
on disk to an up-to-date rerun, so imp_install reads the installer output
instead of trusting the exit code. Harness 4/4 in a sandbox HOME with the
real installer.
plugins.lock.json: impeccable 3.2.0 -> 4.1.0 (CLI only). link.sh drops
impeccable from EXTERNAL_SKILLS. lib/design-gate.md section 5: suggest-only
/impeccable init check when a frontend project has no PRODUCT.md.
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.
RC fix (soak day 1): `make update` step 7.5 failed on a 300s clone timeout
of alchaincyf/find-skills. The skill (search the skills.sh registry from
Claude) was never used; the discovery case stays reachable manually via
`npx -y skills find <query>`. Removed from install-plugins.sh (install list
+ summary + comment), update-all.sh (refresh list), link.sh
(NPX_EXTERNAL_SKILLS), lib/toggle-external.sh (MANAGED_TOOLS + case arms),
plugin-advisor.md, .gitignore; local skills/find-skills symlink deleted.
Memory-registry and test-fixture mentions kept — append-only history.
toggle-external `list` verified post-removal; suites 8/8.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpphkdTosUzokBDNG7PToS
Complementary to frontend-design (kept: build-time aesthetic direction).
impeccable adds what the chain lacked: 45 deterministic anti-slop rules
(npx impeccable detect, exit 0/2, --json) — the design counterpart of the
semgrep gate — plus 23 design verbs under one /impeccable skill and
persistent per-project design context.
- plugins.lock.json: CLI pinned 3.2.0 (rules update = audit output change
on unchanged code, LRN-077 class); skill dist = its own release track
- install-plugins.sh Step 8d: staged npx install (tmpdir) -> moved to
skills-external/impeccable (machine-owned, gitignored, ctx7 pattern);
never writes through the ~/.claude/skills symlink into the tracked tree
- update-all.sh: pin-honored refresh, Node<24 or failure -> dist kept
- Node >= 24 required (host at 22): steps skip gracefully, activation
deferred to a deliberate Node bump
- link.sh EXTERNAL_SKILLS, profiles (design/web/web-full/full),
plugin-advisor, CLAUDE.md design routing, design-gate, README, CHANGELOG
- NOT in design GATE-BLOCK yet: promotion after first dogfood
Claude Code loads modular rule files from ~/.claude/rules/ (user scope,
recursive, markdown, optional paths: frontmatter for lazy path-scoped
loading — stable, symlink-supported). The repo had no rules/ at all, so
the ctx7 setup had created ~/.claude/rules as a REAL directory outside
version control — invisible to the repo, unreproducible on a new machine.
- rules/README.md — doctrine: one rule per file; paths:-scoped extraction
is the token win, always-on doctrine stays in CLAUDE.md
- link.sh — rules added to the symlinked-dirs loop
- .gitignore — rules/context7.md ignored (machine-owned: `ctx7 setup`
(re)writes it, same treatment as skills/find-docs/)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpphkdTosUzokBDNG7PToS
The magic check + link_env grep'd `^MAGIC_API_KEY=` on $REPO/.env, but on a
fresh machine ~/.claude/.env is often created AFTER link.sh runs, so the
repo/.env symlink (which toggle-external.sh sources) is never made — the key
looks absent though it's set, and the warning misleadingly points at
~/.claude/.env.
- install-plugins.sh: self-heal — if ~/.claude/.env exists but repo/.env is
missing, create the symlink before checking. Accurate message.
- Both: tolerate optional `export ` + leading whitespace and require a
non-empty value (regex sanity-tested), so common .env formats match.
Immediate fix for an affected machine: `make link`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UyNYwD4UccVw9ZCFZyJX55
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>
Per-skill SKILL.md symlinks don't expose gstack's shared infrastructure.
Multiple skills hardcode ~/.claude/skills/gstack/bin/ (gstack-config,
gstack-update-check, gstack-paths) and gstack/browse/dist/ (browse binary).
Create targeted symlinks in link.sh:
- ~/.claude/skills/gstack/bin/ → skills-external/gstack/bin/
- ~/.claude/skills/gstack/browse/dist/ → skills-external/gstack/browse/dist/
Fixes: browse binary not found, gstack-config failures, freeze gstack-paths
resolution. Safe with profile toggles (profiles move per-skill dirs, not
the gstack/ infra dir). Safe with stale link cleanup (only removes -L
symlinks, not real dirs created by mkdir -p).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Source: anthropic-agent-skills plugin cache → skills-external/frontend-design/
Auto-install via install-plugins.sh step 8b, auto-update via update-all.sh
step 7.1, symlinked by link.sh (refactored to loop over EXTERNAL_SKILLS).
Added to profiles: design, web, web-full, full.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
`claude plugin enable|disable` only toggles marketplace plugins.
Tools living as symlinks (gstack per-skill entries, emil-design-eng,
darwin-skill, find-skills) had no lever — users had to edit symlinks
by hand. The new script moves symlinks in/out of skills-disabled/
so Claude Code stops or starts scanning them.
Also removes the legacy global `skills/gstack` symlink that shadowed
per-skill entries with a duplicate top-level "gstack" skill (same
description as "browse"). gstack detection in detect-plugins.sh now
probes an individual skill instead.
plugin-advisor reads the new script's `list` command when gathering
state and emits its `enable|disable` commands in recommendations.
Co-Authored-By: Claude <noreply@anthropic.com>
`claude plugin update` rejects a bare plugin name when multiple
marketplaces are registered — the CLI demands `name@marketplace`.
update-all.sh stripped the suffix via `${_p%%@*}`, causing every
marketplace plugin update to fail with "Plugin not found". Fixed
by passing the unmodified spec from `claude plugin list`.
Also adds install + update paths for external skills distributed
through the `npx skills` CLI (vercel-labs/skills):
- alchaincyf/darwin-skill
- alchaincyf/find-skills
These land in ~/.agents/skills/ and are now symlinked into
$REPO/skills/ via link.sh using absolute paths — the previous
relative `../../.agents/...` targets resolved incorrectly when
the repo is cloned below $HOME (as ~/Documents/claude/), leaving
dangling symlinks.
Co-Authored-By: Claude <noreply@anthropic.com>