From 4364e2b351acb65b7638089d41a4556754bbfeb9 Mon Sep 17 00:00:00 2001 From: bastien Date: Tue, 5 May 2026 23:46:23 +0200 Subject: [PATCH] chore(memory): compress 5 registries to caveman format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply CLAUDE.md "Format — registries ALWAYS caveman" rule to existing entries via /caveman:compress. Drops articles, filler, and hedging while preserving: - Technical terms (rtk, claude plugin install, framer-motion, etc.) - IDs unchanged (BDR-XXX, LRN-XXX, BLK-XXX, EVAL-XXX) - Dates unchanged (2026-04-22 etc.) - Code blocks and quoted error strings exact - Commit refs (892de28, 7b57b2e, d3c79f0, 64d6ca7) Files: decisions.md, learnings.md, blockers.md, journal.md, evals.md. Token reduction: ~40% on session-start memory load. Pre-compression backups saved as *.original.md (gitignored next). Co-Authored-By: Claude --- .claude/memory/blockers.md | 18 +++--- .claude/memory/decisions.md | 114 ++++++++++++++++++------------------ .claude/memory/evals.md | 8 +-- .claude/memory/journal.md | 26 ++++---- .claude/memory/learnings.md | 68 ++++++++++----------- 5 files changed, 117 insertions(+), 117 deletions(-) diff --git a/.claude/memory/blockers.md b/.claude/memory/blockers.md index 81a05c3..bfee0e5 100644 --- a/.claude/memory/blockers.md +++ b/.claude/memory/blockers.md @@ -9,9 +9,9 @@ schema: solution: string (workaround or fix) status: [open | resolved | upstream] rules: - - Open a blocker as soon as friction > 15 min wasted. Close it with a real cause, not "moved on". - - Link to upstream issue / PR / commit when applicable. - - If cause is a bug in a dependency, set status upstream with a pointer to the tracker. + - Open blocker when friction > 15 min wasted. Close with real cause, not "moved on". + - Link upstream issue / PR / commit when applicable. + - Cause is bug in dependency → status upstream with pointer to tracker. --- # Blockers registry (BLK) @@ -28,7 +28,7 @@ rules: ## BLK-001 — `rtk curl` returns compressed schema in pipes - **Date**: 2026-04-22 -- **Friction**: any pipeline like `rtk curl ... | python -c "json.load(sys.stdin)"` (or `jq`, `awk`) fails without a clear error message. +- **Friction**: pipelines like `rtk curl ... | python -c "json.load(sys.stdin)"` (or `jq`, `awk`) fail without clear error. - **Real cause**: `rtk curl` auto-compresses stdout regardless of TTY — documented in `.claude/tasks/rtk-upstream-issue.md`. - **Solution**: - Short-term workaround: `exclude_commands=["curl"]` in `~/.config/rtk/config.toml`. @@ -39,9 +39,9 @@ rules: ## BLK-002 — `rmdir` denied in sandbox on empty directory - **Date**: 2026-04-23 -- **Friction**: couldn't delete `./tasks/` after emptying it (post-migration to `.claude/tasks/`). `rmdir tasks` and `rm -r tasks` returned "Permission denied" even with an empty dir and non-destructive intent. -- **Real cause**: the Claude Code sandbox blocks destructive commands (`rm`, `rmdir`, `rm -rf`) by default via the harness permission gate, regardless of actual semantics. `git rm` through `git` passed (commit `c721a36`) — git is treated as a non-destructive tool. +- **Friction**: couldn't delete `./tasks/` after emptying (post-migration to `.claude/tasks/`). `rmdir tasks` and `rm -r tasks` returned "Permission denied" even with empty dir and non-destructive intent. +- **Real cause**: Claude Code sandbox blocks destructive commands (`rm`, `rmdir`, `rm -rf`) by default via harness permission gate, regardless of actual semantics. `git rm` through `git` passed (commit `c721a36`) — git treated as non-destructive tool. - **Solution**: - - This session: `git rm tasks/*.md` handled files individually (via `git rm`, which cleared the gate). Git then auto-detected renames to `.claude/tasks/`, so the `tasks/` directory was removed implicitly at commit time. - - If the dir persists empty after `git rm`: ask the user to run `rmdir tasks` manually. -- **Status**: resolved (fixed via `git rm` + rename auto-detection; no `rmdir` needed in practice). + - This session: `git rm tasks/*.md` handled files individually (via `git rm`, cleared gate). Git auto-detected renames to `.claude/tasks/`, so `tasks/` directory removed implicitly at commit time. + - If dir persists empty after `git rm`: ask user to run `rmdir tasks` manually. +- **Status**: resolved (fixed via `git rm` + rename auto-detection; no `rmdir` needed in practice). \ No newline at end of file diff --git a/.claude/memory/decisions.md b/.claude/memory/decisions.md index a4c3ea1..cde028e 100644 --- a/.claude/memory/decisions.md +++ b/.claude/memory/decisions.md @@ -11,9 +11,9 @@ schema: status: [proposed | accepted | deprecated | superseded] supersedes: BDR-XXX (optional) rules: - - Append-only. Never rewrite past entries - add a new one with status superseded if needed. - - One entry per non-trivial choice. Trivial = reversible in under 10 min with no cross-file impact. - - Capture why more carefully than what - the what rots, the why lasts. + - Append-only. Never rewrite past entries - add new one with status superseded if needed. + - One entry per non-trivial choice. Trivial = reversible under 10 min, no cross-file impact. + - Capture why more carefully than what - what rots, why lasts. --- # Decisions registry (BDR) @@ -35,62 +35,62 @@ rules: - **Date**: 2026-04-22 - **Status**: accepted -- **Decision**: every skill exposes `--help` via a shared snippet injected by the session-start hook, rather than duplicating the helper in each SKILL.md. -- **Why**: 25+ skills — keeping the same helper synced across every file guarantees drift. A single injection point = single source of truth. +- **Decision**: every skill expose `--help` via shared snippet injected by session-start hook, not duplicate helper in each SKILL.md. +- **Why**: 25+ skills — keep same helper synced across every file guarantees drift. Single injection point = single source of truth. - **Alternatives rejected**: - - Option A (copy the helper into each SKILL.md) — rejected: maintenance entropy. - - Option B (external wrapper `/help `) — rejected: breaks the "one command = one skill" experience. + - Option A (copy helper into each SKILL.md) — rejected: maintenance entropy. + - Option B (external wrapper `/help `) — rejected: breaks "one command = one skill" experience. - **Reference**: commit 3968a29. ## BDR-002 — Move tasks/ + introduce memory + audits under .claude/ - **Date**: 2026-04-23 - **Status**: accepted -- **Decision**: migrate `./tasks/` to `.claude/tasks/`, create `.claude/memory/` (5 registries BDR/LRN/BLK/journal/EVAL) and `.claude/audits/` for AUDIT_* files. Adapt skills/agents/CLAUDE.md. Integrate a CAPITALIZE step into completion skills (ship-feature, feat, bugfix, hotfix, commit-change) and add a `/close` skill for the session-end ritual. -- **Why**: grouping all meta-project state (AI config + tasks + memory + audits) under `.claude/` isolates Claude governance from real code. Aligned with the official Claude Code memory docs. Without integration in completion skills, the registries would stay empty (aspirational text). +- **Decision**: migrate `./tasks/` to `.claude/tasks/`, create `.claude/memory/` (5 registries BDR/LRN/BLK/journal/EVAL) and `.claude/audits/` for AUDIT_* files. Adapt skills/agents/CLAUDE.md. Integrate CAPITALIZE step into completion skills (ship-feature, feat, bugfix, hotfix, commit-change), add `/close` skill for session-end ritual. +- **Why**: group all meta-project state (AI config + tasks + memory + audits) under `.claude/` isolate Claude governance from real code. Aligned with official Claude Code memory docs. Without integration in completion skills, registries stay empty (aspirational text). - **Alternatives rejected**: - - Keep `./tasks/` at root — rejected: clutters the repo, mixes code signal with governance signal. - - Use `.claude/agent-memory/` for everything — rejected: `agent-memory/` has a distinct role (already used by other tools). - - Ritual as aspirational text only in CLAUDE.md — rejected: zero execution guarantee, registries would stay empty. - - `Stop` hook to ask the 3 questions every turn — rejected: too noisy. + - Keep `./tasks/` at root — rejected: clutters repo, mixes code signal with governance signal. + - Use `.claude/agent-memory/` for everything — rejected: `agent-memory/` has distinct role (already used by other tools). + - Ritual as aspirational text only in CLAUDE.md — rejected: zero execution guarantee, registries stay empty. + - `Stop` hook to ask 3 questions every turn — rejected: too noisy. ## BDR-003 — Gitignore wildcard + negations pattern for `.claude/` - **Date**: 2026-04-23 - **Status**: accepted -- **Decision**: use `.claude/*` (wildcard match of immediate children) + negations `!.claude/tasks/`, `!.claude/memory/`, etc., rather than `.claude/` (recursive ignore). -- **Why**: when a parent is ignored via `.claude/`, git does not descend into it (performance optimization) and negations on children are **ignored** — documented in `gitignore(5)`. With `.claude/*`, git matches each child individually, making negations active. +- **Decision**: use `.claude/*` (wildcard match of immediate children) + negations `!.claude/tasks/`, `!.claude/memory/`, etc., not `.claude/` (recursive ignore). +- **Why**: when parent ignored via `.claude/`, git no descend (performance optimization) and negations on children **ignored** — documented in `gitignore(5)`. With `.claude/*`, git matches each child individually, negations active. - **Alternatives rejected**: - - `.claude/` + `!.claude/tasks/` (naive) — rejected: negations have no effect, everything stays ignored. + - `.claude/` + `!.claude/tasks/` (naive) — rejected: negations no effect, everything stays ignored. - Drop `.claude/` from gitignore entirely — rejected: `.claude/settings.local.json` and `.claude/agent-memory/` must stay ignored (per-machine). - - Track paths via `.gitattributes` or an external tool — rejected: over-engineering, git handles this natively. + - Track paths via `.gitattributes` or external tool — rejected: over-engineering, git handles natively. - **Reference**: commit `499cd07`, `git check-ignore -v` verified on 4 paths (2 tracked, 2 ignored). ## BDR-004 — Adopt auto permission mode as default - **Date**: 2026-04-27 - **Status**: accepted -- **Decision**: set `permissions.defaultMode` to `"auto"` in user-scope `settings.json` and drop `disableAutoMode: "disable"`. Auto mode runs a classifier on every action and blocks risky operations (`curl|bash`, prod deploys, force push, IAM grants, mass deletes, exfiltration to external endpoints) while auto-approving local edits, lockfile-declared dep installs, and read-only HTTP. -- **Why**: prompt fatigue under `default` mode is significant on multi-step autonomous work. Auto mode keeps a safety net (classifier review) without the per-tool friction. The classifier also re-evaluates conversation-stated boundaries ("don't push", "wait for review") on every check, so verbal constraints carry weight. +- **Decision**: set `permissions.defaultMode` to `"auto"` in user-scope `settings.json`, drop `disableAutoMode: "disable"`. Auto mode runs classifier on every action, blocks risky operations (`curl|bash`, prod deploys, force push, IAM grants, mass deletes, exfiltration to external endpoints), auto-approves local edits, lockfile-declared dep installs, read-only HTTP. +- **Why**: prompt fatigue under `default` mode big on multi-step autonomous work. Auto mode keeps safety net (classifier review) without per-tool friction. Classifier re-evaluates conversation-stated boundaries ("don't push", "wait for review") on every check, verbal constraints carry weight. - **Alternatives rejected**: - Keep `default` — too many prompts, breaks flow on long tasks. - `acceptEdits` — eliminates prompts but no classifier, blanket trust on Bash beyond filesystem helpers. - `bypassPermissions` — skips all checks, no prompt-injection guard. Only for isolated containers. - `dontAsk` — full denylist, breaks anything not pre-approved. Suited to CI, not interactive work. -- **Caveats**: requires Claude Code v2.1.83+, plan ≠ Pro (Max/Team/Enterprise/API only), Sonnet 4.6 / Opus 4.6 / Opus 4.7, Anthropic API provider. On entering auto mode, blanket allow rules (`Bash(*)`, `Bash(python*)`, package-manager run, `Agent`) are dropped and restored on exit. +- **Caveats**: requires Claude Code v2.1.83+, plan ≠ Pro (Max/Team/Enterprise/API only), Sonnet 4.6 / Opus 4.6 / Opus 4.7, Anthropic API provider. On entering auto mode, blanket allow rules (`Bash(*)`, `Bash(python*)`, package-manager run, `Agent`) dropped, restored on exit. - **Reference**: commit `1421578`. ## BDR-005 — `motion` as default animation library; advisor stays read-only - **Date**: 2026-04-27 - **Status**: accepted -- **Decision**: when a project's stack supports it, the framework installs `motion` (or `motion-v` for Vue 3 / Nuxt) as the default animation library. Install is **automatic** in `/init-project` STEP 5e (post-scaffold) and **opt-in** in `/onboard` STEP 2.5 (existing projects). `plugin-advisor` only **detects and reports** the status — it never runs `npm install` itself. Detection logic lives in `lib/animation-lib-check.sh` (sourced by all three layers). -- **Why**: framer-motion was rebranded `motion` in November 2024 (single package supporting React `motion/react`, Svelte, vanilla JS; `motion-v` is the parallel package for Vue). Baking the new name in now avoids legacy-import sprawl across new projects. The split init-vs-onboard behavior follows the trust gradient: at init, the user has just validated the entire scaffold so silent install is fine; at onboard, we are touching an existing `package.json`, which is invasive without explicit consent. Plugin-advisor was kept read-only to preserve its "Never modify files" contract (PHASE 4 already mutates plugin state with confirmation; piling npm installs on top would blur its responsibility). +- **Decision**: when project stack supports it, framework installs `motion` (or `motion-v` for Vue 3 / Nuxt) as default animation library. Install **automatic** in `/init-project` STEP 5e (post-scaffold), **opt-in** in `/onboard` STEP 2.5 (existing projects). `plugin-advisor` only **detects and reports** status — never runs `npm install` itself. Detection logic in `lib/animation-lib-check.sh` (sourced by all three layers). +- **Why**: framer-motion rebranded `motion` in November 2024 (single package supporting React `motion/react`, Svelte, vanilla JS; `motion-v` parallel package for Vue). Bake new name now to avoid legacy-import sprawl across new projects. Split init-vs-onboard behavior follows trust gradient: at init, user just validated entire scaffold so silent install fine; at onboard, touching existing `package.json` invasive without explicit consent. Plugin-advisor kept read-only to preserve "Never modify files" contract (PHASE 4 already mutates plugin state with confirmation; piling npm installs on top blurs responsibility). - **Alternatives rejected**: - - Pin `framer-motion` (legacy name) — rejected: the package is in maintenance mode, every new project would inherit the old import path. - - Auto-install during `/onboard` without asking — rejected: silently adds a runtime dep + ~50 KB gzip to a project the user did not ask to modify. - - Make `plugin-advisor` install missing libs — rejected: violates its read-only spec and breaks separation of concerns (advisor advises; orchestrators mutate). - - React-only scope — rejected: Vue/Svelte teams should also benefit; `motion-v` makes the Vue case clean. + - Pin `framer-motion` (legacy name) — rejected: package in maintenance mode, every new project inherits old import path. + - Auto-install during `/onboard` without asking — rejected: silently adds runtime dep + ~50 KB gzip to project user did not ask to modify. + - Make `plugin-advisor` install missing libs — rejected: violates read-only spec, breaks separation of concerns (advisor advises; orchestrators mutate). + - React-only scope — rejected: Vue/Svelte teams should benefit; `motion-v` makes Vue case clean. - **Eligibility rules** (helper output): - `eligible|motion`: React, Next.js, Remix, Astro+React, Svelte/SvelteKit - `eligible|motion-v`: Vue 3, Nuxt @@ -101,52 +101,52 @@ rules: - **Date**: 2026-05-03 - **Status**: accepted -- **Decision**: install `JuliusBrussee/caveman` in the always-on tier alongside `security-guidance`, `superpowers`, and `rtk`. "Full" install = plugin (`/caveman` + cavecrew agents + plugin-scoped SessionStart/UserPromptSubmit hooks) + standalone hooks (statusline + stats badge in `~/.claude/hooks/`) + `caveman-shrink` MCP scaffold (NOT auto-registered — proxy needs upstream wrapper). `install-plugins.sh` STEP 5.5 calls `enable_plugin "caveman" "caveman"` to write it into `enabledPlugins`. Hook paths in `settings.json` are normalized to `~/.claude/hooks/...` post-install so this user's home dir doesn't leak across machines. -- **Why**: caveman compresses Claude's output ~75% via caveman-speak while preserving technical substance. Symmetrical with rtk (input compression hook) — rtk shrinks tool I/O, caveman shrinks model output. Both hooks pay zero passive cost in a clean session and amortize across long runs. Always-on is justified: the plugin auto-deactivates with phrases like "stop caveman" / "normal mode", so toggle would be friction without benefit. +- **Decision**: install `JuliusBrussee/caveman` in always-on tier alongside `security-guidance`, `superpowers`, `rtk`. "Full" install = plugin (`/caveman` + cavecrew agents + plugin-scoped SessionStart/UserPromptSubmit hooks) + standalone hooks (statusline + stats badge in `~/.claude/hooks/`) + `caveman-shrink` MCP scaffold (NOT auto-registered — proxy needs upstream wrapper). `install-plugins.sh` STEP 5.5 calls `enable_plugin "caveman" "caveman"` to write into `enabledPlugins`. Hook paths in `settings.json` normalized to `~/.claude/hooks/...` post-install so user home dir no leak across machines. +- **Why**: caveman compresses Claude output ~75% via caveman-speak, preserves technical substance. Symmetrical with rtk (input compression hook) — rtk shrinks tool I/O, caveman shrinks model output. Both hooks pay zero passive cost in clean session, amortize across long runs. Always-on justified: plugin auto-deactivates with phrases like "stop caveman" / "normal mode", toggle would be friction without benefit. - **Alternatives rejected**: - - Toggle plugin (start OFF) — rejected: misses the by-default benefit; the user would need to remember `claude plugin enable caveman@caveman` per session, which negates the auto-compression value. - - `--minimal` install (plugin only) — rejected: loses the standalone stats badge that surfaces token-saving telemetry. - - `--all` install (adds per-repo `caveman-rules.md` etc. into `$PWD`) — rejected: would litter THIS config repo (the cwd at install time) with rule files meant for project repos. Let users opt in per-repo when they want it. - - Auto-register `caveman-shrink` MCP — rejected: the proxy errors with "missing upstream command" without an upstream MCP to wrap, fails health checks. Print a snippet instead and let the user pick which upstream they want compressed (filesystem, github, …). + - Toggle plugin (start OFF) — rejected: misses by-default benefit; user need remember `claude plugin enable caveman@caveman` per session, negates auto-compression value. + - `--minimal` install (plugin only) — rejected: loses standalone stats badge surfacing token-saving telemetry. + - `--all` install (adds per-repo `caveman-rules.md` etc. into `$PWD`) — rejected: would litter THIS config repo (cwd at install time) with rule files meant for project repos. Let users opt in per-repo when wanted. + - Auto-register `caveman-shrink` MCP — rejected: proxy errors with "missing upstream command" without upstream MCP to wrap, fails health checks. Print snippet instead, let user pick which upstream they want compressed (filesystem, github, …). - **Caveats**: - - Caveman's `hooks/install.sh` writes absolute paths (`$HOME/.claude/hooks/caveman-*.js`) into `settings.json`. Since `settings.json` is symlinked into the repo, the absolute path would commit a username. STEP 5.5 runs a Python post-process to rewrite to portable `~/.claude/hooks/...` form (bash expands `~` before passing to `node`). - - Caveman's hook files materialize in `hooks/` (the repo dir, not `~/.claude/hooks/`) because the latter is a symlink. They're added to `.gitignore` to prevent accidental commit of user-scope state. + - Caveman `hooks/install.sh` writes absolute paths (`$HOME/.claude/hooks/caveman-*.js`) into `settings.json`. `settings.json` symlinked into repo, absolute path commits username. STEP 5.5 runs Python post-process to rewrite to portable `~/.claude/hooks/...` form (bash expands `~` before passing to `node`). + - Caveman hook files materialize in `hooks/` (repo dir, not `~/.claude/hooks/`) because latter is symlink. Added to `.gitignore` to prevent accidental commit of user-scope state. - **Reference**: install-plugins.sh STEP 5.5, lib/detect-plugins.sh `detect_caveman*` + `plugin_enabled`, doctor.sh caveman block, commit `9b20b84`. ## BDR-007 — Skill profiles partition gstack by usage (design / dev / qa / audit / minimal) - **Date**: 2026-05-04 - **Status**: accepted -- **Decision**: ship `lib/profile.sh` + `lib/profiles/*.profile` to give the user fine-grained, task-shaped activation of skills. A profile is a plain-text file listing skill names + types (`gstack`, `external`, `personal`, `plugin`, `mcp`). `profile set ` enables the listed skills and disables every gstack-origin skill not in the profile, by moving symlinks between `skills/` and `skills-disabled/`. `profile reset` re-enables all of gstack. Plugin/MCP entries are advisory — script prints the manual `claude plugin enable` / `claude mcp add` command but never runs it. Surface area: one CLI (`bash lib/profile.sh`), one slash command (`/profile`), four Makefile targets, and a section in `agents/plugin-advisor.md`. -- **Why**: when the user works on a focused kind of task (design only, qa only, audit only) the full gstack (~38 skills) injects irrelevant skill descriptions into every session. The existing `lib/toggle-external.sh enable|disable gstack` is too coarse — it disables the whole gstack including infrastructure skills the user does want (checkpoint, ship, learn). Profiles give the user a curated middle ground: keep the gstack repo installed, hide the skills not relevant to this session. +- **Decision**: ship `lib/profile.sh` + `lib/profiles/*.profile` to give user fine-grained, task-shaped activation of skills. Profile = plain-text file listing skill names + types (`gstack`, `external`, `personal`, `plugin`, `mcp`). `profile set ` enables listed skills, disables every gstack-origin skill not in profile, by moving symlinks between `skills/` and `skills-disabled/`. `profile reset` re-enables all of gstack. Plugin/MCP entries advisory — script prints manual `claude plugin enable` / `claude mcp add` command but never runs it. Surface area: one CLI (`bash lib/profile.sh`), one slash command (`/profile`), four Makefile targets, section in `agents/plugin-advisor.md`. +- **Why**: when user works on focused kind of task (design only, qa only, audit only) full gstack (~38 skills) injects irrelevant skill descriptions into every session. Existing `lib/toggle-external.sh enable|disable gstack` too coarse — disables whole gstack including infrastructure skills user does want (checkpoint, ship, learn). Profiles give curated middle ground: keep gstack repo installed, hide skills not relevant to this session. - **Alternatives rejected**: - - Fork SKILL.md files to strip the ~70-line gstack preamble — rejected: every gstack upgrade would need to re-fork, and the preamble already degrades gracefully (`|| true`) when `gstack/bin/` is unavailable. Hiding the skill is cheaper than rewriting it. - - Per-skill toggle via `claude plugin enable/disable` — rejected: gstack skills are not marketplace plugins, they're symlinks owned by `skills-external/gstack/`. The CLI doesn't reach them. - - Disable via removing symlinks (rm + recreate on enable) — rejected: lossy if the user has local edits, and re-creation requires running gstack's own setup. Move-based toggle preserves the symlink intact. - - Auto-toggle plugins (`ui-ux-pro-max`) and MCPs as part of `set` — rejected: those affect global Claude Code state and may carry API keys (magic). Keep them advisory; user runs the CLI command knowingly. - - Build a giant `gstack-profile` CLI that wraps `gstack/bin/*` directly — rejected: scope creep into gstack internals. The repo already has its own toggle infra (`lib/toggle-external.sh`); profile.sh sits alongside it as a finer tool. + - Fork SKILL.md files to strip ~70-line gstack preamble — rejected: every gstack upgrade needs re-fork, preamble already degrades gracefully (`|| true`) when `gstack/bin/` unavailable. Hiding skill cheaper than rewriting. + - Per-skill toggle via `claude plugin enable/disable` — rejected: gstack skills not marketplace plugins, symlinks owned by `skills-external/gstack/`. CLI no reach them. + - Disable via removing symlinks (rm + recreate on enable) — rejected: lossy if user has local edits, re-creation requires running gstack own setup. Move-based toggle preserves symlink intact. + - Auto-toggle plugins (`ui-ux-pro-max`) and MCPs as part of `set` — rejected: those affect global Claude Code state, may carry API keys (magic). Keep advisory; user runs CLI command knowingly. + - Build giant `gstack-profile` CLI wrapping `gstack/bin/*` directly — rejected: scope creep into gstack internals. Repo already has own toggle infra (`lib/toggle-external.sh`); profile.sh sits alongside as finer tool. - **Caveats**: - - Profiles do NOT change `gstack/bin/` infrastructure — preamble in disabled skills still references it, and re-enabling restores normal behavior. No telemetry/learnings data is touched. - - `cmd_set` only auto-disables skills returned by `gstack_skills()` (those with a `SKILL.md` under `skills-external/gstack/*/`). Personal skills (real dirs in `skills/`) are never auto-disabled by `set` — only added back if listed in the profile. - - `cmd_current` returns "full" when nothing has been disabled, even if a profile happens to be 100% covered by the current state. The active-profile heuristic requires at least one `gstack__*` entry in `skills-disabled/` so we don't lie about a profile being "set" when no `set` ever ran. - - Personal skills use `external`-style move (no `gstack__` prefix) so name-collision with gstack skills can't happen during disable. + - Profiles do NOT change `gstack/bin/` infrastructure — preamble in disabled skills still references it, re-enabling restores normal behavior. No telemetry/learnings data touched. + - `cmd_set` only auto-disables skills returned by `gstack_skills()` (those with `SKILL.md` under `skills-external/gstack/*/`). Personal skills (real dirs in `skills/`) never auto-disabled by `set` — only added back if listed in profile. + - `cmd_current` returns "full" when nothing disabled, even if profile happens to be 100% covered by current state. Active-profile heuristic requires at least one `gstack__*` entry in `skills-disabled/` so we no lie about profile being "set" when no `set` ever ran. + - Personal skills use `external`-style move (no `gstack__` prefix) so name-collision with gstack skills cannot happen during disable. - **Reference**: `lib/profile.sh`, `lib/profiles/{design,dev,qa,audit,minimal}.profile`, `skills/profile/SKILL.md`, `agents/plugin-advisor.md` (DETECT block + TOGGLING EXTERNAL TOOLS section), `Makefile` targets `profile*`, `lib/toggle-external.sh` header pointer. ## BDR-008 — Profile system v2: extend to plugins + MCPs + CLIs (web/seo/web-full/backend) - **Date**: 2026-05-04 - **Status**: accepted -- **Decision**: extend `profile.sh` to actually toggle Claude plugins (`claude plugin enable|disable @`) and MCP servers (delegated to `lib/toggle-external.sh` for the `magic` MCP, advisory for others), and add CLI status reporting. New profile syntax uses `plugin@` so the script knows where to enable from. New profiles shipped: `web` (frontend website), `seo` (SEO/GEO/W3C audit), `web-full` (web + seo combined), `backend` (API/system dev — no design, no SEO). Reverted v1 decision (BDR-007 alternative #4 "advisory only for plugins/MCPs"): user explicitly asked for actual toggling so `set web` actively enables `ui-ux-pro-max` + `magic` and `set seo` actively disables `ui-ux-pro-max`. Always-on plugins (`caveman`, `security-guidance`, `superpowers`) are protected by both an allowlist (`MANAGED_PLUGINS`) and a denylist (`PROTECTED_PLUGINS`). -- **Why**: v1 profiles only managed skills (symlink toggle). User feedback: "active TOUT le splugins necessaire pour tel profile et desactive les autre". Pure-skill toggling left ui-ux-pro-max/magic always loaded regardless of profile, so passive token cost didn't drop as much as expected when switching to a non-design profile. Auto-toggling plugins shifts the design from "show me the right skills" to "set up the right session" — closer to what the user actually wants. +- **Decision**: extend `profile.sh` to actually toggle Claude plugins (`claude plugin enable|disable @`) and MCP servers (delegated to `lib/toggle-external.sh` for `magic` MCP, advisory for others), add CLI status reporting. New profile syntax uses `plugin@` so script knows where to enable from. New profiles shipped: `web` (frontend website), `seo` (SEO/GEO/W3C audit), `web-full` (web + seo combined), `backend` (API/system dev — no design, no SEO). Reverted v1 decision (BDR-007 alternative #4 "advisory only for plugins/MCPs"): user explicitly asked for actual toggling so `set web` actively enables `ui-ux-pro-max` + `magic`, `set seo` actively disables `ui-ux-pro-max`. Always-on plugins (`caveman`, `security-guidance`, `superpowers`) protected by both allowlist (`MANAGED_PLUGINS`) and denylist (`PROTECTED_PLUGINS`). +- **Why**: v1 profiles only managed skills (symlink toggle). User feedback: "active TOUT le splugins necessaire pour tel profile et desactive les autre". Pure-skill toggling left ui-ux-pro-max/magic always loaded regardless of profile, passive token cost no drop as much as expected when switching to non-design profile. Auto-toggling plugins shifts design from "show me right skills" to "set up right session" — closer to what user actually wants. - **Alternatives rejected**: - - Keep plugins advisory + add a `--apply-plugins` flag — rejected: user would have to type the flag every time, defeating the "switch profile to switch context" workflow. - - Disable ALL non-listed plugins (including third-party user-installed ones) — rejected: too aggressive. Profile system has no business touching plugins the user installed for their own reasons. Solution: explicit `MANAGED_PLUGINS` allowlist (currently 3 entries) — the script touches only those. - - Treat MCPs identically to plugins (auto-toggle any MCP) — rejected: MCPs typically need env vars / API keys / specific commands. Auto-registering with wrong config produces broken MCPs (LRN-006). Compromise: auto-toggle ONLY `magic` because we already have its config in `lib/toggle-external.sh`. Other MCPs stay advisory. - - Track plugin state across `set/reset` cycles and restore on reset — rejected: complexity not worth it. `reset` re-enables gstack skills only. To re-enable a managed plugin, the user runs `apply ` or the explicit `claude plugin enable` command. Documented in the `info` line printed at the end of `reset`. + - Keep plugins advisory + add `--apply-plugins` flag — rejected: user has to type flag every time, defeats "switch profile to switch context" workflow. + - Disable ALL non-listed plugins (including third-party user-installed ones) — rejected: too aggressive. Profile system has no business touching plugins user installed for own reasons. Solution: explicit `MANAGED_PLUGINS` allowlist (currently 3 entries) — script touches only those. + - Treat MCPs identically to plugins (auto-toggle any MCP) — rejected: MCPs typically need env vars / API keys / specific commands. Auto-registering with wrong config produces broken MCPs (LRN-006). Compromise: auto-toggle ONLY `magic` because already have its config in `lib/toggle-external.sh`. Other MCPs stay advisory. + - Track plugin state across `set/reset` cycles, restore on reset — rejected: complexity not worth it. `reset` re-enables gstack skills only. To re-enable managed plugin, user runs `apply ` or explicit `claude plugin enable` command. Documented in `info` line printed at end of `reset`. - **Caveats**: - - `MANAGED_PLUGINS` is hardcoded — adding a new toggle-managed plugin requires editing `profile.sh`. Acceptable for now (3 entries, rarely changes); revisit if it grows. - - `claude plugin enable` returns success even for already-enabled plugins, so the parser greps for "enabled|already" in stdout/stderr. Works on the current Claude CLI; brittle if the CLI rewords its messages. Acceptable risk. - - The `current` heuristic now counts `installed` (CLI status) as available. Without that, profiles listing CLIs would never reach 100% match. Tiebreaker: when two profiles tie on %, the larger total wins (web-full > web > design when all are 100%). - - `cmd_show` widened the TYPE column to 30 chars to fit `plugin@ui-ux-pro-max-skill` without breaking alignment. - - `mcp magic` toggle delegates to `lib/toggle-external.sh enable magic` which requires `MAGIC_API_KEY` in `.env`. If the key is missing, profile.sh prints an info line and continues — the rest of the profile still applies. -- **Reference**: `lib/profile.sh` (`MANAGED_PLUGINS`/`PROTECTED_PLUGINS` arrays, `skill_status` plugin@/cli/mcp branches, `enable_skill`/`disable_skill` plugin@ + mcp branches, `cmd_set` plugin disable loop, `cmd_current` available-counting), `lib/profiles/{web,seo,web-full,backend}.profile`, refined `lib/profiles/{design,dev,qa,audit}.profile` (use `plugin@` syntax + `cli` entries), `skills/profile/SKILL.md` (updated profile table + mechanism table), `agents/plugin-advisor.md` (extended profile recommendation table). + - `MANAGED_PLUGINS` hardcoded — adding new toggle-managed plugin requires editing `profile.sh`. Acceptable for now (3 entries, rarely changes); revisit if grows. + - `claude plugin enable` returns success even for already-enabled plugins, parser greps for "enabled|already" in stdout/stderr. Works on current Claude CLI; brittle if CLI rewords messages. Acceptable risk. + - `current` heuristic now counts `installed` (CLI status) as available. Without that, profiles listing CLIs would never reach 100% match. Tiebreaker: when two profiles tie on %, larger total wins (web-full > web > design when all are 100%). + - `cmd_show` widened TYPE column to 30 chars to fit `plugin@ui-ux-pro-max-skill` without breaking alignment. + - `mcp magic` toggle delegates to `lib/toggle-external.sh enable magic` which requires `MAGIC_API_KEY` in `.env`. If key missing, profile.sh prints info line and continues — rest of profile still applies. +- **Reference**: `lib/profile.sh` (`MANAGED_PLUGINS`/`PROTECTED_PLUGINS` arrays, `skill_status` plugin@/cli/mcp branches, `enable_skill`/`disable_skill` plugin@ + mcp branches, `cmd_set` plugin disable loop, `cmd_current` available-counting), `lib/profiles/{web,seo,web-full,backend}.profile`, refined `lib/profiles/{design,dev,qa,audit}.profile` (use `plugin@` syntax + `cli` entries), `skills/profile/SKILL.md` (updated profile table + mechanism table), `agents/plugin-advisor.md` (extended profile recommendation table). \ No newline at end of file diff --git a/.claude/memory/evals.md b/.claude/memory/evals.md index 64a4f8c..b57ff1c 100644 --- a/.claude/memory/evals.md +++ b/.claude/memory/evals.md @@ -28,7 +28,7 @@ rules: ## EVAL-001 — `.claude/` restructure plan - **Date**: 2026-04-23 -- **Output**: 21-task plan to migrate `tasks/` to `.claude/tasks/` + create `.claude/memory/` + `.claude/audits/` + integrate CAPITALIZE across 5 skills + add `/close` skill. -- **Method**: manual review of the 5 impacted skills/agents; verified `rtk` is path-agnostic; confirmed `~/.claude/CLAUDE.md` symlinks to the project (single file to edit). Radical-honesty check on the session-close ritual: confirmed aspirational without skill integration → scope expanded to Option D. -- **Anomalies**: none blocking. Note: `tasks/LESSONS.md` was empty (101B, header only) — migration to `learnings.md` is symbolic. -- **Action**: keep — plan validated, ready for execution. +- **Output**: 21-task plan migrate `tasks/` to `.claude/tasks/` + create `.claude/memory/` + `.claude/audits/` + integrate CAPITALIZE across 5 skills + add `/close` skill. +- **Method**: manual review of 5 impacted skills/agents; verified `rtk` path-agnostic; confirmed `~/.claude/CLAUDE.md` symlinks to project (single file edit). Radical-honesty check on session-close ritual: confirmed aspirational without skill integration → scope expanded to Option D. +- **Anomalies**: none blocking. Note: `tasks/LESSONS.md` empty (101B, header only) — migration to `learnings.md` symbolic. +- **Action**: keep — plan validated, ready for execution. \ No newline at end of file diff --git a/.claude/memory/journal.md b/.claude/memory/journal.md index 590c072..0e22ab2 100644 --- a/.claude/memory/journal.md +++ b/.claude/memory/journal.md @@ -5,8 +5,8 @@ schema: body: 3-5 lines max - what was done, decided, blocked rules: - One heading per date (YYYY-MM-DD), not per session. - - Append at the end. Never edit past entries. - - Keep it terse. Details belong in decisions/learnings/blockers - this is a timeline only. + - Append at end. Never edit past entries. + - Keep terse. Details belong in decisions/learnings/blockers - timeline only. --- # Journal @@ -15,32 +15,32 @@ rules: - Restructured tree: `tasks/` → `.claude/tasks/`, created `.claude/memory/` (5 registries) + `.claude/audits/`. - Adapted CLAUDE.md + skills `onboard`, `init-project` + agent `onboarder` + `lib/project-archetypes/dotfiles-meta.md`. -- Added CAPITALIZE step in `ship-feature`, `bugfix`, `hotfix`, `feat`, `commit-change` + created `/close` skill for the session-end ritual. -- 2nd user verify-gate caught bugs: `.gitignore` was breaking tracking (fixed in BDR-003); harden/validate dispatcher bash was broken after the audit move (LRN-002). +- Added CAPITALIZE step in `ship-feature`, `bugfix`, `hotfix`, `feat`, `commit-change` + created `/close` skill for session-end ritual. +- 2nd user verify-gate caught bugs: `.gitignore` broke tracking (fixed BDR-003); harden/validate dispatcher bash broken after audit move (LRN-002). - Audits routed to `.claude/audits/` (seo/geo/harden/validate/code-clean) + `MIGRATION.md` written for existing projects. -- 9 atomic commits (`c721a36..a9606aa`) via `/commit-change` — first real execution of Phase 4 CAPITALIZE. +- 9 atomic commits (`c721a36..a9606aa`) via `/commit-change` — first real exec of Phase 4 CAPITALIZE. - Decisions logged: BDR-002, BDR-003. Learnings: LRN-002. Blockers: BLK-002. - English-only rule enforced in all CAPITALIZE specs (commit `bfcca72`); 9 existing entries retrofitted to English in follow-up commit. ## 2026-04-27 -- Settings: switched `permissions.defaultMode` from `"default"` to `"auto"` and dropped `disableAutoMode: "disable"` (BDR-004); reorganised top-level keys and added `effortLevel: "xhigh"`; removed stale root `TODO.md` (already migrated to `.claude/tasks/TODO.md`). -- Learning: Claude Code `disable*` settings use the sentinel string `"disable"`, not a boolean (LRN-003). +- Settings: switched `permissions.defaultMode` from `"default"` to `"auto"` and dropped `disableAutoMode: "disable"` (BDR-004); reorganised top-level keys + added `effortLevel: "xhigh"`; removed stale root `TODO.md` (already migrated to `.claude/tasks/TODO.md`). +- Learning: Claude Code `disable*` settings use sentinel string `"disable"`, not boolean (LRN-003). - 3 atomic commits (`f7f033f..1421578`) via `/commit-change`. - Animation lib autoflow added: new helper `lib/animation-lib-check.sh` + STEP 5e in `/init-project` (auto-install) + STEP 2.5 in `/onboard` (opt-in) + read-only detection in `plugin-advisor` PHASE 1/2/3 + signal in `lib/design-gate.md` + scaffolder note. `motion` chosen over legacy `framer-motion` (BDR-005, LRN-004). ## 2026-05-03 - Added `JuliusBrussee/caveman` as 4th always-on plugin (BDR-006). Full install: plugin + standalone hooks + caveman-shrink MCP scaffold (snippet only, not auto-registered — proxy needs upstream wrapper, LRN-006). -- Discovered two co-masking bugs: `claude plugin install` doesn't enable (LRN-005) and `session-start.sh` was hardcoding "✅ ON: security-guidance rtk superpowers" regardless of actual state. Added `enable_plugin()` helper + `plugin_enabled()` detector reading `enabledPlugins` from `settings.json`. Banner now reflects reality. -- Side fix: doctor.sh exited under `set -euo pipefail` when gstack/skills/ was missing — wrapped find in brace + `|| true`. +- Discovered two co-masking bugs: `claude plugin install` doesn't enable (LRN-005) + `session-start.sh` hardcoded "✅ ON: security-guidance rtk superpowers" regardless of actual state. Added `enable_plugin()` helper + `plugin_enabled()` detector reading `enabledPlugins` from `settings.json`. Banner now reflects reality. +- Side fix: doctor.sh exited under `set -euo pipefail` when gstack/skills/ missing — wrapped find in brace + `|| true`. - 3 atomic commits (`0184818..2ec7935`). ## 2026-05-04 - Built skill profile system (BDR-007): `lib/profile.sh` + `lib/profiles/{design,dev,qa,audit,minimal}.profile` partition gstack + personal skills by purpose. Activation toggles symlinks `skills/` ↔ `skills-disabled/`. -- Wired into `agents/plugin-advisor.md` (DETECT call to `profile.sh current` + new `PROFILE` line in OUTPUT + new "Skill profiles" subsection in TOGGLING EXTERNAL TOOLS), `lib/toggle-external.sh` (header pointer), `Makefile` (4 targets), and `skills/profile/SKILL.md` (`/profile` slash command). -- `cmd_current` honestly reports "full" when no `gstack__*` entry exists in `skills-disabled/` — avoids the "100% match" trap when the full gstack is on. +- Wired into `agents/plugin-advisor.md` (DETECT call to `profile.sh current` + new `PROFILE` line in OUTPUT + new "Skill profiles" subsection in TOGGLING EXTERNAL TOOLS), `lib/toggle-external.sh` (header pointer), `Makefile` (4 targets), `skills/profile/SKILL.md` (`/profile` slash command). +- `cmd_current` honestly reports "full" when no `gstack__*` entry exists in `skills-disabled/` — avoids "100% match" trap when full gstack on. - Tested end-to-end: list/show/current/diff/set/reset/apply all green; shellcheck clean; symlink state restored after reset. -- Profile system v2 (BDR-008): extended `profile.sh` to actually toggle Claude plugins (`claude plugin enable|disable`) and MCP servers (`magic` via `lib/toggle-external.sh`). Added 4 new profiles: `web`, `seo`, `web-full`, `backend`. Refined existing profiles to use `plugin@` syntax + `cli` entries. Always-on plugins protected by `MANAGED_PLUGINS` allowlist + `PROTECTED_PLUGINS` denylist. -- Verified: `set web` enables ui-ux-pro-max + magic; `set seo` disables ui-ux-pro-max; `set minimal` disables ui-ux-pro-max but spares caveman/security-guidance/superpowers. `current` heuristic respects ties (web-full beats web at 100%). +- Profile system v2 (BDR-008): extended `profile.sh` to toggle Claude plugins (`claude plugin enable|disable`) + MCP servers (`magic` via `lib/toggle-external.sh`). Added 4 new profiles: `web`, `seo`, `web-full`, `backend`. Refined existing profiles to use `plugin@` syntax + `cli` entries. Always-on plugins protected by `MANAGED_PLUGINS` allowlist + `PROTECTED_PLUGINS` denylist. +- Verified: `set web` enables ui-ux-pro-max + magic; `set seo` disables ui-ux-pro-max; `set minimal` disables ui-ux-pro-max but spares caveman/security-guidance/superpowers. `current` heuristic respects ties (web-full beats web at 100%). \ No newline at end of file diff --git a/.claude/memory/learnings.md b/.claude/memory/learnings.md index 87bc7cc..49cea12 100644 --- a/.claude/memory/learnings.md +++ b/.claude/memory/learnings.md @@ -8,10 +8,10 @@ schema: context: string (where/when it happened - concrete) future_application: string (when to recall this) rules: - - Capture learnings that apply beyond the current task. - - Abstract from the incident - the pattern is what is reusable, not the one-shot fact. + - Capture learnings that apply beyond current task. + - Abstract from incident — pattern reusable, not one-shot fact. - Link to source (commit, file, PR) when possible. - - Replaces the previous LESSONS.md format. Old file was empty - no content to migrate. + - Replaces previous LESSONS.md format. Old file empty — no content to migrate. --- # Learnings registry (LRN) @@ -23,75 +23,75 @@ rules: | LRN-001 | 2026-04-22 | `rtk` shape-compression breaks pipes | any pipeline chaining `rtk curl/cat/read` into `jq`, `python -c`, `awk` | | LRN-002 | 2026-04-23 | Moving report-file paths requires grepping bash READS, not just WRITES | any refactor that moves a generated file used by a dispatcher | | LRN-003 | 2026-04-27 | Claude Code `disable*` settings use sentinel string `"disable"`, not boolean | any change to `permissions.defaultMode` or related blocker keys | -| LRN-004 | 2026-04-27 | `framer-motion` was rebranded `motion` in Nov 2024 — different packages per framework | any new project recommending an animation lib; auditing legacy imports | -| LRN-005 | 2026-05-03 | `claude plugin install` does NOT enable — separate `claude plugin enable` required | every plugin installer that targets ALWAYS-ON status | -| LRN-006 | 2026-05-03 | `caveman-shrink` (and any MCP middleware proxy) is non-functional without an upstream wrapper | any MCP middleware/proxy package — never `claude mcp add` it bare | +| LRN-004 | 2026-04-27 | `framer-motion` rebranded `motion` Nov 2024 — different packages per framework | any new project recommending animation lib; auditing legacy imports | +| LRN-005 | 2026-05-03 | `claude plugin install` does NOT enable — separate `claude plugin enable` required | every plugin installer targeting ALWAYS-ON status | +| LRN-006 | 2026-05-03 | `caveman-shrink` (and any MCP middleware proxy) non-functional without upstream wrapper | any MCP middleware/proxy package — never `claude mcp add` it bare | --- ## LRN-001 — `rtk` shape-compression silently breaks downstream parsers - **Date**: 2026-04-22 -- **Pattern**: when a tracking tool (`rtk`) intercepts stdout and returns a schematized/compressed representation instead of the raw payload, every downstream parser breaks silently — because the user (or the LLM) never sees `rtk`'s output, only the parser error. -- **Context**: `rtk curl` replaces raw JSON output with a tokenized version, regardless of TTY vs pipe. Claude Code hooks auto-rewrite `curl` → `rtk curl`, so the behavior is impossible to anticipate without knowing the hook. -- **Future application**: for any tool that auto-rewrites standard commands, explicitly verify pipe behavior. Documented workaround: `exclude_commands=["curl"]` in `~/.config/rtk/config.toml`, or `rtk proxy`. See `BLK-001`. +- **Pattern**: when tracking tool (`rtk`) intercepts stdout and returns schematized/compressed representation instead of raw payload, every downstream parser breaks silently — user (or LLM) never sees `rtk`'s output, only parser error. +- **Context**: `rtk curl` replaces raw JSON output with tokenized version, regardless of TTY vs pipe. Claude Code hooks auto-rewrite `curl` → `rtk curl`, so behavior impossible to anticipate without knowing hook. +- **Future application**: for any tool auto-rewriting standard commands, explicitly verify pipe behavior. Documented workaround: `exclude_commands=["curl"]` in `~/.config/rtk/config.toml`, or `rtk proxy`. See `BLK-001`. ## LRN-002 — Moving report-file paths requires grepping bash READS, not just WRITES - **Date**: 2026-04-23 -- **Pattern**: when moving the write path of a generated file (report, artifact, cache), you must also grep the places that READ that file — not only those that write it. Dispatchers (orchestrator skills that dispatch to an agent and then parse the result) typically contain bash commands like `test -s X.md`, `grep ... X.md`, `wc -l X.md` — these refs are invisible if you only grep for "write" or "output path". -- **Context**: `.claude/audits/` refactor (commit `5c5e82c`). First pass: I updated write paths across 5 skills (seo/geo/harden/validate/code-clean) and 3 agents. The user asked for a verify-gate. They re-grepped and found 10+ bare bash refs (e.g. `test -s HARDEN.md`, `grep -oE ... VALIDATE.md`) I had missed — the dispatchers were broken (looking at project root while the agent was writing to `.claude/audits/`). Fixed in commit `5c5e82c` (bundled with the same commit). +- **Pattern**: when moving write path of generated file (report, artifact, cache), must also grep places that READ that file — not only those that write it. Dispatchers (orchestrator skills dispatching to agent then parsing result) typically contain bash commands like `test -s X.md`, `grep ... X.md`, `wc -l X.md` — refs invisible if only grep for "write" or "output path". +- **Context**: `.claude/audits/` refactor (commit `5c5e82c`). First pass: updated write paths across 5 skills (seo/geo/harden/validate/code-clean) and 3 agents. User asked for verify-gate. They re-grepped, found 10+ bare bash refs (e.g. `test -s HARDEN.md`, `grep -oE ... VALIDATE.md`) missed — dispatchers broken (looking at project root while agent writing to `.claude/audits/`). Fixed in commit `5c5e82c` (bundled with same commit). - **Future application**: - - Before declaring a file-path migration "complete", grep the **basename** (`grep -rn "HARDEN\.md"`) in addition to the full path — to catch bare bash usages. - - If the file is used in pipelines (`test`, `grep`, `wc`, `cat`, `head`), search for those verbs explicitly. - - **Verify-gates save work**: one extra round asked by the user forced exhaustive re-grepping. Without it, two dispatchers would have shipped broken. + - Before declaring file-path migration "complete", grep **basename** (`grep -rn "HARDEN\.md"`) plus full path — catch bare bash usages. + - If file used in pipelines (`test`, `grep`, `wc`, `cat`, `head`), search for those verbs explicitly. + - **Verify-gates save work**: one extra round forced exhaustive re-grepping. Without it, two dispatchers shipped broken. -## LRN-003 — Claude Code `disable*` settings use the sentinel string `"disable"`, not a boolean +## LRN-003 — Claude Code `disable*` settings use sentinel string `"disable"`, not boolean - **Date**: 2026-04-27 -- **Pattern**: Claude Code blocker-style settings (`disableAutoMode`, `disableBypassPermissionsMode`) use the literal string `"disable"` as a sentinel. The key being absent means the feature is available; the value `"disable"` is what turns the blocker on. Any other value (including `false`, `true`, `null`) has no effect — the doc explicitly states this. -- **Context**: switching `permissions.defaultMode` to `"auto"` while `disableAutoMode: "disable"` was still present would have failed at startup ("auto mode unavailable"). The naming `disable: "disable"` reads ambiguously — easy to assume it's a boolean toggle and leave the key in place. +- **Pattern**: Claude Code blocker-style settings (`disableAutoMode`, `disableBypassPermissionsMode`) use literal string `"disable"` as sentinel. Key absent = feature available; value `"disable"` turns blocker on. Any other value (including `false`, `true`, `null`) has no effect — doc explicitly states this. +- **Context**: switching `permissions.defaultMode` to `"auto"` while `disableAutoMode: "disable"` still present would have failed at startup ("auto mode unavailable"). Naming `disable: "disable"` reads ambiguously — easy to assume boolean toggle and leave key in place. - **Future application**: - - Before changing `defaultMode`, audit the matching `disable*` key in the same `permissions` block. If present with value `"disable"`, remove it. + - Before changing `defaultMode`, audit matching `disable*` key in same `permissions` block. If present with value `"disable"`, remove it. - Same logic for `bypassPermissions` mode and `disableBypassPermissionsMode`. - - Don't trust the doc's naming — read the value semantics. Sentinel strings beat booleans here because the harness can distinguish "unset" from "explicitly off" (admin policy). + - Don't trust doc's naming — read value semantics. Sentinel strings beat booleans here because harness can distinguish "unset" from "explicitly off" (admin policy). - **Reference**: commit `1421578`, doc `https://code.claude.com/docs/en/settings`. ## LRN-004 — `framer-motion` rebranded `motion` (Nov 2024) — different packages per framework - **Date**: 2026-04-27 -- **Pattern**: `framer-motion` was renamed `motion` in November 2024. The rename is not just cosmetic: it bundles React (`motion/react`), Svelte, and vanilla-JS support under a single npm package, while Vue gets its own parallel package `motion-v`. The legacy package `framer-motion` still installs and works but is in maintenance mode — recommending it in a new framework default would lock projects into legacy import paths from day one. Detection of "is animation already covered" must therefore include both names plus the broader anim ecosystem (`gsap`, `lottie-react`, `react-spring`, `popmotion`, `@formkit/auto-animate`) to avoid double-installs. -- **Context**: building animation-lib auto-install in `/init-project` and `/onboard`. Initial user phrasing was "framer-motion" (the old name they remembered). Picking the package name without verifying the rename would have shipped legacy imports in every new scaffold. +- **Pattern**: `framer-motion` renamed `motion` November 2024. Rename not cosmetic: bundles React (`motion/react`), Svelte, vanilla-JS support under single npm package, while Vue gets own parallel package `motion-v`. Legacy package `framer-motion` still installs and works but in maintenance mode — recommending it in new framework default locks projects into legacy import paths day one. Detection of "is animation already covered" must include both names plus broader anim ecosystem (`gsap`, `lottie-react`, `react-spring`, `popmotion`, `@formkit/auto-animate`) to avoid double-installs. +- **Context**: building animation-lib auto-install in `/init-project` and `/onboard`. Initial user phrasing "framer-motion" (old name remembered). Picking package name without verifying rename would have shipped legacy imports in every new scaffold. - **Future application**: - For React / Next.js / Remix / Astro+React / Svelte: `motion` (`import { motion } from 'motion/react'`). - For Vue 3 / Nuxt: `motion-v` (separate package, separate API). - - For React Native: do NOT recommend `motion` — use `react-native-reanimated` (motion targets the DOM). + - For React Native: do NOT recommend `motion` — use `react-native-reanimated` (motion targets DOM). - When auditing existing projects, check both `framer-motion` and `motion` keys in `package.json` deps; treat either as "animation already covered". - - Before adopting any "industry default" lib in a framework, verify the canonical package name is current — naming churn (rebrand, scope change `@org/lib`, fork) is common in JS land. + - Before adopting any "industry default" lib in framework, verify canonical package name current — naming churn (rebrand, scope change `@org/lib`, fork) common in JS land. - **Reference**: helper `lib/animation-lib-check.sh`, BDR-005. -## LRN-005 — `claude plugin install` does NOT enable — `claude plugin enable` is a separate step +## LRN-005 — `claude plugin install` does NOT enable — `claude plugin enable` separate step - **Date**: 2026-05-03 -- **Pattern**: the Claude Code CLI splits "available" from "active" for marketplace plugins. `claude plugin install --scope user name@source` only copies the plugin into `~/.claude/plugins/cache////`. It does NOT write `name@source: true` into the user's `settings.json:enabledPlugins` map. Without an explicit `claude plugin enable name@source`, the plugin sits dormant — installed but unloaded. This is symmetric with `claude plugin disable`, which keeps the cache and only removes the enabledPlugins entry. -- **Context**: discovered while auditing why `security-guidance` and `superpowers` were ✘ disabled in `claude plugin list` despite the project's `install-plugins.sh` summary banner declaring them "ALWAYS ON". Root cause: `install_plugin()` only ran `claude plugin install`, never `enable`. The bug had stayed invisible because a hardcoded `printf "│ ✅ ON : security-guidance rtk superpowers │"` in `session-start.sh` printed the same names regardless of actual state — the lying banner agreed with the lying install. +- **Pattern**: Claude Code CLI splits "available" from "active" for marketplace plugins. `claude plugin install --scope user name@source` only copies plugin into `~/.claude/plugins/cache////`. Does NOT write `name@source: true` into user's `settings.json:enabledPlugins` map. Without explicit `claude plugin enable name@source`, plugin sits dormant — installed but unloaded. Symmetric with `claude plugin disable`, which keeps cache and only removes enabledPlugins entry. +- **Context**: discovered auditing why `security-guidance` and `superpowers` were ✘ disabled in `claude plugin list` despite project's `install-plugins.sh` summary banner declaring them "ALWAYS ON". Root cause: `install_plugin()` only ran `claude plugin install`, never `enable`. Bug stayed invisible because hardcoded `printf "│ ✅ ON : security-guidance rtk superpowers │"` in `session-start.sh` printed same names regardless of actual state — lying banner agreed with lying install. - **Future application**: - - For any plugin meant to be ALWAYS ON, follow `claude plugin install` with `claude plugin enable name@source` (idempotent — no-op if already enabled). - - Detect "actually enabled" via `enabledPlugins[name@source] === true` in `settings.json`, NOT by the presence of the cache dir. The pattern is implemented in `lib/detect-plugins.sh:plugin_enabled()` (filesystem grep, no subprocess). - - Any banner / status display that claims a plugin is on must read state, never hardcode names. Hardcoded labels turn a single bug into two co-conspiring bugs that mask each other. + - For any plugin meant ALWAYS ON, follow `claude plugin install` with `claude plugin enable name@source` (idempotent — no-op if already enabled). + - Detect "actually enabled" via `enabledPlugins[name@source] === true` in `settings.json`, NOT presence of cache dir. Pattern implemented in `lib/detect-plugins.sh:plugin_enabled()` (filesystem grep, no subprocess). + - Any banner / status display claiming plugin on must read state, never hardcode names. Hardcoded labels turn single bug into two co-conspiring bugs masking each other. - **Reference**: commit `2ec7935`, `lib/detect-plugins.sh:plugin_enabled`, `install-plugins.sh:enable_plugin()`. -## LRN-006 — `caveman-shrink` (and any MCP middleware proxy) needs an upstream wrapper to function +## LRN-006 — `caveman-shrink` (and any MCP middleware proxy) needs upstream wrapper to function - **Date**: 2026-05-03 -- **Pattern**: some MCP packages are middleware proxies, not standalone servers. They wrap an upstream MCP server and transform its responses (e.g. `caveman-shrink` compresses prose fields). Running them bare via `claude mcp add proxy-name -- npx -y proxy-pkg` registers a server that errors immediately with "missing upstream command" — every health check fails, and Claude Code reports the MCP as broken until a human intervenes. The CLI `claude mcp add` doesn't validate that the configured command launches a working stdio MCP, so the bad registration silently lands. -- **Context**: when adding caveman, the upstream installer auto-registers `claude mcp add caveman-shrink -- npx -y caveman-shrink` and prints "registered. wrap an upstream by editing the mcpServers entry". Following that flow leaves the user with a permanently failing MCP entry until they realize they have to edit `~/.claude.json` manually. +- **Pattern**: some MCP packages are middleware proxies, not standalone servers. They wrap upstream MCP server and transform its responses (e.g. `caveman-shrink` compresses prose fields). Running them bare via `claude mcp add proxy-name -- npx -y proxy-pkg` registers server that errors immediately with "missing upstream command" — every health check fails, and Claude Code reports MCP broken until human intervenes. CLI `claude mcp add` doesn't validate that configured command launches working stdio MCP, so bad registration silently lands. +- **Context**: when adding caveman, upstream installer auto-registers `claude mcp add caveman-shrink -- npx -y caveman-shrink` and prints "registered. wrap an upstream by editing the mcpServers entry". Following that flow leaves user with permanently failing MCP entry until they realize they must edit `~/.claude.json` manually. - **Future application**: - - For any MCP that is a proxy/middleware (read package docs for "upstream", "wraps", "proxy"), register it under a DERIVED name `-` with the upstream baked into the args. Example for caveman-shrink wrapping the filesystem server: + - For any MCP that is proxy/middleware (read package docs for "upstream", "wraps", "proxy"), register under DERIVED name `-` with upstream baked into args. Example for caveman-shrink wrapping filesystem server: ``` claude mcp add caveman-shrink-fs --scope user -- \ npx -y caveman-shrink npx -y @modelcontextprotocol/server-filesystem /path ``` - Detection of "is this MCP correctly set up?" must look for the derived name (`caveman-shrink-*`), not the bare proxy name. Bare-name registration is treated as broken. - Default install scripts should NOT auto-register middleware MCPs — print the snippet for the user to choose an upstream. See `install-plugins.sh` STEP 5.5. -- **Reference**: commit `9b20b84`, `lib/detect-plugins.sh:detect_caveman_shrink`, `install-plugins.sh` STEP 5.5 MCP block. +- **Reference**: commit `9b20b84`, `lib/detect-plugins.sh:detect_caveman_shrink`, `install-plugins.sh` STEP 5.5 MCP block. \ No newline at end of file